diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..e8036160ea --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Always check-out / check-in files with LF line endings. +* text=auto eol=lf + +**/zz_generated.*.go linguist-generated=true diff --git a/cmd/ateapi/internal/controlapi/actor.go b/cmd/ateapi/internal/controlapi/actor.go index fd18e3ba88..69bb7c1b31 100644 --- a/cmd/ateapi/internal/controlapi/actor.go +++ b/cmd/ateapi/internal/controlapi/actor.go @@ -26,32 +26,47 @@ import ( "github.com/agent-substrate/substrate/internal/resources" atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + "github.com/google/uuid" "go.opentelemetry.io/otel/attribute" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/operation" "k8s.io/apimachinery/pkg/api/validate/content" "k8s.io/apimachinery/pkg/util/validation/field" ) func (s *Service) CreateActor(ctx context.Context, req *ateapipb.CreateActorRequest) (created *ateapipb.Actor, err error) { - if errs := validateCreateActorRequest(req); len(errs) > 0 { + // First scrub any fields that users are not allowed to set. + inActor := req.Actor + if inActor != nil { // otherwise validation will flag it + scrubActor(inActor) + } + + // Validate the request, including the object within it. + if errs := validateCreateActorRequest(ctx, req); len(errs) > 0 { return nil, toGRPCStatusError(errs) } + + // + // Handle the request + // + start := time.Now() - in := req.GetActor() // Recorded only after validation, so every operation uniformly measures a // validated request; malformed ones stay visible in rpc.server.call.duration. defer func() { s.instruments.recordLifecycleOp(ctx, ateattr.OperationCreate, start, err, - ateattr.TemplateNameKey.String(in.GetActorTemplateName()), - ateattr.TemplateNamespaceKey.String(in.GetActorTemplateNamespace()), + ateattr.TemplateNameKey.String(inActor.GetActorTemplateName()), + ateattr.TemplateNamespaceKey.String(inActor.GetActorTemplateNamespace()), ) }() - templateNamespace := in.GetActorTemplateNamespace() - templateName := in.GetActorTemplateName() + templateNamespace := inActor.GetActorTemplateNamespace() + templateName := inActor.GetActorTemplateName() - setSpanActorRefAttributes(ctx, resources.ActorRefFromActor(in)) + setSpanActorRefAttributes(ctx, resources.ActorRefFromActor(inActor)) template, err := s.actorTemplateLister.ActorTemplates(templateNamespace).Get(templateName) if err != nil { @@ -62,15 +77,15 @@ func (s *Service) CreateActor(ctx context.Context, req *ateapipb.CreateActorRequ } var sourceSnapshotInfo *ateapipb.ActorSnapshotSource - if src := in.GetSourceSnapshot(); src != nil { - sourceSnapshotInfo, err = s.resolveSnapshotSource(ctx, in.GetMetadata().GetAtespace(), src, template) + if src := inActor.GetSourceSnapshot(); src != nil { + sourceSnapshotInfo, err = s.resolveSnapshotSource(ctx, inActor.GetMetadata().GetAtespace(), src, template) if err != nil { return nil, err } } - atespace := in.GetMetadata().GetAtespace() - name := in.GetMetadata().GetName() + atespace := inActor.GetMetadata().GetAtespace() + name := inActor.GetMetadata().GetName() // The atespace must already exist. exists, err := s.persistence.AtespaceExists(ctx, atespace) @@ -87,20 +102,18 @@ func (s *Service) CreateActor(ctx context.Context, req *ateapipb.CreateActorRequ return nil, err } - actor := &ateapipb.Actor{ - Metadata: &ateapipb.ResourceMetadata{ - Atespace: atespace, - Name: name, - }, - Status: ateapipb.Actor_STATUS_SUSPENDED, - ActorTemplateNamespace: templateNamespace, - ActorTemplateName: templateName, - WorkerSelector: in.GetWorkerSelector(), - ActorVolumes: initVols, - LatestSnapshot: sourceSnapshotInfo.GetSnapshot(), - SourceSnapshot: sourceSnapshotInfo, - } - stored, err := s.persistence.CreateActor(ctx, actor) + // Verify that the result is properly valid before storing it. + outActor := proto.CloneOf(inActor) + outActor.Status = ateapipb.Actor_STATUS_SUSPENDED + outActor.ActorVolumes = initVols + outActor.LatestSnapshot = sourceSnapshotInfo.GetSnapshot() + outActor.SourceSnapshot = sourceSnapshotInfo + if errs := validateActorUpdate(ctx, outActor, inActor); len(errs) > 0 { + return nil, toGRPCInternalError(errs) + } + + // Save the data in the storage layer. + stored, err := s.persistence.CreateActor(ctx, outActor) if err != nil { if errors.Is(err, store.ErrAlreadyExists) { return nil, status.Errorf(codes.AlreadyExists, "Actor %s already exists", name) @@ -112,6 +125,40 @@ func (s *Service) CreateActor(ctx context.Context, req *ateapipb.CreateActorRequ return stored, nil } +// scrubActor removes any fields from the request that clients are not allowed +// to set. +func scrubActor(actor *ateapipb.Actor) { + // TODO: find a way to do this automatically - proto tags or codegen or something + //FIXME: this is obviously wrong for update + scrubResourceMetadata(actor.Metadata) + actor.Status = 0 + actor.WorkerAssignment = nil + actor.InProgressSnapshotName = "" + actor.LatestSnapshot = nil + actor.LocalSnapshotInfo = nil + actor.InProgressSnapshotSourceActorVersion = 0 + actor.ActorVolumes = nil + actor.InProgressLocalSnapshotName = "" + // FIXME: SourceSnapshot has mixed input/output fields, so we need smarter scrub +} + +// FIXME: put this in a common place for all resources. +// TODO: find a way to do this automatically - proto tags or codegen or something +func scrubResourceMetadata(in *ateapipb.ResourceMetadata) { + if in == nil { + return // validation will flag it + } + now := timestamppb.Now() + *in = ateapipb.ResourceMetadata{ + Atespace: in.Atespace, + Name: in.Name, + Uid: uuid.NewString(), + Version: 1, + CreateTime: now, + UpdateTime: now, + } +} + // resolveSnapshotSource resolves a CreateActor request's source snapshot tag // and checks that its scope and ActorSnapshot are compatible with creating // an Actor in actorAtespace from template. @@ -161,29 +208,20 @@ func (s *Service) resolveSnapshotSource(ctx context.Context, actorAtespace strin }, nil } -func validateCreateActorRequest(req *ateapipb.CreateActorRequest) field.ErrorList { +func validateCreateActorRequest(ctx context.Context, req *ateapipb.CreateActorRequest) field.ErrorList { var fldPath *field.Path - var errs field.ErrorList + + // Call the generated validation. + op := operation.Operation{Type: operation.Create, Options: map[string]bool{"validateOutput": false}} + errs := Validate_CreateActorRequest(ctx, op, nil, req, nil) actor := req.GetActor() actorPath := fldPath.Child("actor") if actor == nil { - errs = append(errs, field.Required(actorPath, "")) + // handled by DV return errs } - metaPath := actorPath.Child("metadata") - if val, p := actor.GetMetadata().GetAtespace(), metaPath.Child("atespace"); val == "" { - errs = append(errs, field.Required(p, "")) - } else { - errs = append(errs, resources.ValidateResourceName(val, p)...) - } - if val, p := actor.GetMetadata().GetName(), metaPath.Child("name"); val == "" { - errs = append(errs, field.Required(p, "")) - } else { - errs = append(errs, resources.ValidateResourceName(val, p)...) - } - if val, p := actor.GetActorTemplateNamespace(), actorPath.Child("actor_template_namespace"); val == "" { errs = append(errs, field.Required(p, "")) } else { @@ -468,6 +506,16 @@ func validateSuspendActorRequest(req *ateapipb.SuspendActorRequest) field.ErrorL return errs } +func validateActorUpdate(ctx context.Context, newVal, oldVal *ateapipb.Actor) field.ErrorList { + var fldPath *field.Path + + // Call the generated validation. + op := operation.Operation{Type: operation.Update, Options: map[string]bool{"validateOutput": true}} + errs := Validate_Actor(ctx, op, fldPath, newVal, oldVal) + + return errs +} + func validateSelector(sel *ateapipb.Selector, fldPath *field.Path) field.ErrorList { var errs field.ErrorList diff --git a/cmd/ateapi/internal/controlapi/actor_test.go b/cmd/ateapi/internal/controlapi/actor_test.go index c04c86953c..f2c73eac83 100644 --- a/cmd/ateapi/internal/controlapi/actor_test.go +++ b/cmd/ateapi/internal/controlapi/actor_test.go @@ -98,6 +98,10 @@ func TestValidateCreateActorRequest(t *testing.T) { "missing actor", &ateapipb.CreateActorRequest{}, field.ErrorList{field.Required(field.NewPath("actor"), "")}, + }, { + "missing actor.metadata", + validActor(func(a *ateapipb.Actor) { a.Metadata = nil }), + field.ErrorList{field.Required(field.NewPath("actor", "metadata"), "")}, }, { "missing actor.metadata.atespace", validActor(func(a *ateapipb.Actor) { a.Metadata.Atespace = "" }), @@ -105,7 +109,7 @@ func TestValidateCreateActorRequest(t *testing.T) { }, { "invalid actor.metadata.atespace", validActor(func(a *ateapipb.Actor) { a.Metadata.Atespace = "NS1" }), - field.ErrorList{field.Invalid(field.NewPath("actor", "metadata", "atespace"), "NS1", "")}, + field.ErrorList{field.Invalid(field.NewPath("actor", "metadata", "atespace"), nil, "").WithOrigin("format=k8s-short-name")}, }, { "missing actor.metadata.name", validActor(func(a *ateapipb.Actor) { a.Metadata.Name = "" }), @@ -113,7 +117,7 @@ func TestValidateCreateActorRequest(t *testing.T) { }, { "invalid actor.metadata.name", validActor(func(a *ateapipb.Actor) { a.Metadata.Name = "ID1" }), - field.ErrorList{field.Invalid(field.NewPath("actor", "metadata", "name"), "ID1", "")}, + field.ErrorList{field.Invalid(field.NewPath("actor", "metadata", "name"), nil, "").WithOrigin("format=k8s-short-name")}, }, { "missing actor_template_namespace", validActor(func(a *ateapipb.Actor) { a.ActorTemplateNamespace = "" }), @@ -121,7 +125,7 @@ func TestValidateCreateActorRequest(t *testing.T) { }, { "invalid actor_template_namespace", validActor(func(a *ateapipb.Actor) { a.ActorTemplateNamespace = "invalid value" }), - field.ErrorList{field.Invalid(field.NewPath("actor", "actor_template_namespace"), "invalid value", "")}, + field.ErrorList{field.Invalid(field.NewPath("actor", "actor_template_namespace"), nil, "")}, }, { "missing actor_template_name", validActor(func(a *ateapipb.Actor) { a.ActorTemplateName = "" }), @@ -129,7 +133,23 @@ func TestValidateCreateActorRequest(t *testing.T) { }, { "invalid actor_template_name", validActor(func(a *ateapipb.Actor) { a.ActorTemplateName = "invalid value" }), - field.ErrorList{field.Invalid(field.NewPath("actor", "actor_template_name"), "invalid value", "")}, + field.ErrorList{field.Invalid(field.NewPath("actor", "actor_template_name"), nil, "")}, + }, { + "unspecified actor.status", + validActor(func(a *ateapipb.Actor) { a.Status = 0 }), + nil, + }, { + "negative actor.status", + validActor(func(a *ateapipb.Actor) { a.Status = -1 }), + field.ErrorList{field.Forbidden(field.NewPath("actor", "status"), "")}, + }, { + "valid actor.status", + validActor(func(a *ateapipb.Actor) { a.Status = ateapipb.Actor_STATUS_RUNNING }), + field.ErrorList{field.Forbidden(field.NewPath("actor", "status"), "")}, + }, { + "invalid actor.status", + validActor(func(a *ateapipb.Actor) { a.Status = 1234567890 }), + field.ErrorList{field.Forbidden(field.NewPath("actor", "status"), "")}, }, { "worker_selector with nil match_labels", validActor(func(a *ateapipb.Actor) { a.WorkerSelector = &ateapipb.Selector{} }), @@ -167,7 +187,7 @@ func TestValidateCreateActorRequest(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assertValidateErr(t, validateCreateActorRequest(tt.req), tt.want) + assertValidateErr(t, validateCreateActorRequest(context.Background(), tt.req), tt.want) }) } } diff --git a/internal/proto/ateompb/gen.go b/cmd/ateapi/internal/controlapi/doc.go similarity index 63% rename from internal/proto/ateompb/gen.go rename to cmd/ateapi/internal/controlapi/doc.go index 97a9a566e3..98ca7cd808 100644 --- a/internal/proto/ateompb/gen.go +++ b/cmd/ateapi/internal/controlapi/doc.go @@ -12,6 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ateompb +// Kubernetes codegen tools required this to be in doc.go, no other name will +// work. -//go:generate bash -c "../../../hack/protoc.sh --plugin=protoc-gen-go=$(bash ../../../hack/run-tool.sh --print-bin-path protoc-gen-go) --plugin=protoc-gen-go-grpc=$(bash ../../../hack/run-tool.sh --print-bin-path protoc-gen-go-grpc) --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. ateom.proto" +// +k8s:validation-gen=TypesWithSuffix=Request +// +k8s:validation-gen-input=github.com/agent-substrate/substrate/pkg/proto/ateapipb +// +k8s:validation-gen-scheme-registry=nil +// +k8s:validation-gen-deep-equal-func=protoDeepEqual + +package controlapi diff --git a/cmd/ateapi/internal/controlapi/functional_test.go b/cmd/ateapi/internal/controlapi/functional_test.go index 0087b0da1d..52a3bca6a3 100644 --- a/cmd/ateapi/internal/controlapi/functional_test.go +++ b/cmd/ateapi/internal/controlapi/functional_test.go @@ -828,12 +828,8 @@ func TestCreateActor_Success(t *testing.T) { createResp, err := tc.client.CreateActor(context.Background(), &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{ - Atespace: testAtespace, - Name: "id1", - Uid: "caller-supplied-uid", - Version: 999, - CreateTime: timestamppb.New(time.Unix(1, 0)), - UpdateTime: timestamppb.New(time.Unix(1, 0)), + Atespace: testAtespace, + Name: "id1", }, ActorTemplateNamespace: ns, ActorTemplateName: "tmpl1", @@ -3494,7 +3490,7 @@ func TestDeleteAtespace_NotFound(t *testing.T) { func assertValidateErr(t *testing.T, got field.ErrorList, want field.ErrorList) { t.Helper() - field.ErrorMatcher{}.ByType().ByField().ByValue().Test(t, want, got) + field.ErrorMatcher{}.ByType().ByField().ByOrigin().Test(t, want, got) } // TestSuspendActor_FromPaused suspends a PAUSED actor end-to-end: instead of diff --git a/cmd/ateapi/internal/controlapi/validate.go b/cmd/ateapi/internal/controlapi/validate.go index 3aea4a88ad..3fbafb85e2 100644 --- a/cmd/ateapi/internal/controlapi/validate.go +++ b/cmd/ateapi/internal/controlapi/validate.go @@ -17,9 +17,26 @@ package controlapi import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "k8s.io/apimachinery/pkg/util/validation/field" ) func toGRPCStatusError(errs field.ErrorList) error { return status.Error(codes.InvalidArgument, errs.ToAggregate().Error()) } + +func toGRPCInternalError(errs field.ErrorList) error { + return status.Error(codes.Internal, errs.ToAggregate().Error()) +} + +func protoDeepEqual[T any](a, b T) bool { + pa, ok := any(a).(proto.Message) + if !ok { + panic("protoDeepEqual: a is not a proto.Message") + } + pb, ok := any(b).(proto.Message) + if !ok { + panic("protoDeepEqual: b is not a proto.Message") + } + return proto.Equal(pa, pb) +} diff --git a/cmd/ateapi/internal/controlapi/validation_test.go b/cmd/ateapi/internal/controlapi/validation_test.go new file mode 100644 index 0000000000..33f083fbf5 --- /dev/null +++ b/cmd/ateapi/internal/controlapi/validation_test.go @@ -0,0 +1,473 @@ +// Copyright 2026 Google LLC +// +// 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 controlapi + +import ( + "context" + "math" + "strings" + "testing" + + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func validResourceMetadata(mutate ...func(*ateapipb.ResourceMetadata)) *ateapipb.ResourceMetadata { + // This is valid with as many fields populated as possible. + rm := &ateapipb.ResourceMetadata{ + Atespace: "as", + Name: "nm", + Uid: "01234567-89ab-cdef-0123-456789abcdef", + Version: 93, + CreateTime: ×tamppb.Timestamp{Seconds: 867}, + UpdateTime: ×tamppb.Timestamp{Seconds: 5309}, + } + for _, m := range mutate { + m(rm) + } + return rm +} + +func TestValidateResourceMetadataCreate(t *testing.T) { + valid := validResourceMetadata + + // Focus this test on fields other than atespace and name. + tests := []struct { + name string + obj *ateapipb.ResourceMetadata + want field.ErrorList + wantOnInput field.ErrorList // validateOutput = false + wantOnOutput field.ErrorList // validateOutput = true + }{{ + name: "valid", + obj: valid(), + }, { + name: "valid atespace: empty", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "" }), + }, { + name: "missing name", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "" }), + want: field.ErrorList{field.Required(field.NewPath("name"), "")}, + }, { + name: "unspecified uid", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Uid = "" }), + wantOnInput: nil, + wantOnOutput: field.ErrorList{field.Required(field.NewPath("uid"), "")}, + }, { + name: "invalid uid: close but not valid", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Uid = "aaaaaaaa-bbbbcccc-dddd-eeeeeeeeeeee" }), + want: field.ErrorList{field.Invalid(field.NewPath("uid"), nil, "").WithOrigin("format=k8s-uuid")}, + }, { + name: "invalid uid: not even close", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Uid = "not a uid" }), + want: field.ErrorList{field.Invalid(field.NewPath("uid"), nil, "").WithOrigin("format=k8s-uuid")}, + }, { + name: "unspecified version", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = 0 }), + wantOnInput: nil, + wantOnOutput: field.ErrorList{field.Required(field.NewPath("version"), "")}, + }, { + name: "valid version: large", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = math.MaxInt64 }), + }, { + name: "invalid version: negative", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = -1 }), + want: field.ErrorList{field.Invalid(field.NewPath("version"), nil, "").WithOrigin("minimum")}, + }, { + name: "unspecified createTime", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.CreateTime = nil }), + wantOnInput: nil, + wantOnOutput: field.ErrorList{field.Required(field.NewPath("create_time"), "")}, + }, { + name: "unspecified updateTime", + obj: valid(func(rm *ateapipb.ResourceMetadata) { rm.UpdateTime = nil }), + wantOnInput: nil, + wantOnOutput: field.ErrorList{field.Required(field.NewPath("update_time"), "")}, + }} + for _, tt := range tests { + t.Run(tt.name+"_validateOutput_false", func(t *testing.T) { + obj := proto.CloneOf(tt.obj) // avoid internal mutations + want := append(tt.want, tt.wantOnInput...) + op := operation.Operation{Type: operation.Create, Options: map[string]bool{"validateOutput": false}} + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + matcher.Test(t, want, Validate_ResourceMetadata(context.Background(), op, nil, obj, nil)) + }) + t.Run(tt.name+"_validateOutput_true", func(t *testing.T) { + obj := proto.CloneOf(tt.obj) // avoid internal mutations + want := append(tt.want, tt.wantOnOutput...) + op := operation.Operation{Type: operation.Create, Options: map[string]bool{"validateOutput": true}} + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + matcher.Test(t, want, Validate_ResourceMetadata(context.Background(), op, nil, obj, nil)) + }) + } +} + +func TestValidateResourceMetadataUpdate(t *testing.T) { + valid := validResourceMetadata + + // Focus this test on fields other than atespace and name. + tests := []struct { + name string + oldObj *ateapipb.ResourceMetadata // should always be valid + newObj *ateapipb.ResourceMetadata + want field.ErrorList + wantOnInput field.ErrorList // validateOutput = false + wantOnOutput field.ErrorList // validateOutput = true + }{{ + name: "valid", + oldObj: valid(), + newObj: valid(), + }, { + name: "atespace: empty -> non-empty", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "" }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "present" }), + wantOnInput: field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("immutable")}, + wantOnOutput: field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("immutable")}, + }, { + name: "atespace: non-empty -> empty", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "present" }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "" }), + wantOnInput: field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("immutable")}, + wantOnOutput: field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("immutable")}, + }, { + name: "atespace: changed", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "value-1" }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "value-2" }), + wantOnInput: field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("immutable")}, + wantOnOutput: field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("immutable")}, + }, { + name: "name: unset", + oldObj: valid(), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "" }), + wantOnInput: field.ErrorList{ + field.Required(field.NewPath("name"), ""), + field.Invalid(field.NewPath("name"), nil, "").WithOrigin("immutable"), + }, + wantOnOutput: field.ErrorList{ + field.Required(field.NewPath("name"), ""), + field.Invalid(field.NewPath("name"), nil, "").WithOrigin("immutable"), + }, + }, { + name: "name: changed", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "value-1" }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "value-2" }), + wantOnInput: field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("immutable")}, + wantOnOutput: field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("immutable")}, + }, { + name: "uid: unset", + oldObj: valid(), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Uid = "" }), + wantOnInput: nil, // optional on input + wantOnOutput: field.ErrorList{ + field.Required(field.NewPath("uid"), ""), + field.Invalid(field.NewPath("uid"), nil, "").WithOrigin("immutable"), + }, + }, { + name: "uid: changed to valid", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Uid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Uid = "11111111-2222-3333-4444-555555555555" }), + wantOnInput: nil, // is a precondition on input, so can be different + wantOnOutput: field.ErrorList{field.Invalid(field.NewPath("uid"), nil, "").WithOrigin("immutable")}, + }, { + name: "uid: changed to invalid", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Uid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Uid = "not a uid" }), + wantOnInput: field.ErrorList{field.Invalid(field.NewPath("uid"), nil, "").WithOrigin("format=k8s-uuid")}, + wantOnOutput: field.ErrorList{field.Invalid(field.NewPath("uid"), nil, "").WithOrigin("immutable")}, + }, { + name: "version: unset", + oldObj: valid(), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = 0 }), + wantOnInput: nil, // optional on input + wantOnOutput: field.ErrorList{ + field.Required(field.NewPath("version"), ""), + field.Invalid(field.NewPath("version"), nil, "").WithOrigin("update"), + }, + }, { + name: "version: changed to valid", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = 123 }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = 456 }), + wantOnInput: nil, // is a precondition on input, so can be different + wantOnOutput: nil, + }, { + name: "version: changed non-monotonically", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = 456 }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = 123 }), + wantOnInput: nil, + wantOnOutput: field.ErrorList{ + field.Invalid(field.NewPath("version"), nil, "").WithOrigin("monotonic"), + }, + }, { + name: "version: changed to invalid", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = 456 }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.Version = -1 }), + wantOnInput: field.ErrorList{ + field.Invalid(field.NewPath("version"), nil, "").WithOrigin("minimum"), + }, + wantOnOutput: field.ErrorList{ + field.Invalid(field.NewPath("version"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("version"), nil, "").WithOrigin("minimum"), + }, + }, { + name: "create_time: unset", + oldObj: valid(), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.CreateTime = nil }), + wantOnInput: nil, // ignored on input + wantOnOutput: field.ErrorList{ + field.Required(field.NewPath("create_time"), ""), + field.Invalid(field.NewPath("create_time"), nil, "").WithOrigin("immutable"), + }, + }, { + name: "create_time: changed", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.CreateTime.Seconds = 123 }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.CreateTime.Seconds = 456 }), + wantOnInput: nil, // ignored on input + wantOnOutput: field.ErrorList{field.Invalid(field.NewPath("create_time"), nil, "").WithOrigin("immutable")}, + }, { + name: "update_time: unset", + oldObj: valid(), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.UpdateTime = nil }), + wantOnInput: nil, // ignored on input + wantOnOutput: field.ErrorList{ + field.Required(field.NewPath("update_time"), ""), + }, + }, { + name: "update_time: changed to valid", + oldObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.UpdateTime.Seconds = 123 }), + newObj: valid(func(rm *ateapipb.ResourceMetadata) { rm.UpdateTime.Seconds = 456 }), + wantOnInput: nil, // ignored on input + wantOnOutput: nil, + }} + for _, tt := range tests { + t.Run(tt.name+"_validateInput", func(t *testing.T) { + oldObj := proto.CloneOf(tt.oldObj) // avoid internal mutations + newObj := proto.CloneOf(tt.newObj) // avoid internal mutations + want := append(tt.want, tt.wantOnInput...) + op := operation.Operation{Type: operation.Update, Options: map[string]bool{"validateOutput": false}} + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + matcher.Test(t, want, Validate_ResourceMetadata(context.Background(), op, nil, newObj, oldObj)) + }) + t.Run(tt.name+"_validateOutput", func(t *testing.T) { + oldObj := proto.CloneOf(tt.oldObj) // avoid internal mutations + newObj := proto.CloneOf(tt.newObj) // avoid internal mutations + want := append(tt.want, tt.wantOnOutput...) + op := operation.Operation{Type: operation.Update, Options: map[string]bool{"validateOutput": true}} + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + matcher.Test(t, want, Validate_ResourceMetadata(context.Background(), op, nil, newObj, oldObj)) + }) + } +} + +func TestValidateResourceMetadataNameAndAtespaceFormat(t *testing.T) { + valid := validResourceMetadata + + // Focus this test on exhaustive testing of the name and atespace fields. + tests := []struct { + name string + obj *ateapipb.ResourceMetadata + want field.ErrorList + }{{ + "valid", + valid(), + nil, + }, { + "valid atespace: alphabetic", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "myatespace" }), + nil, + }, { + "valid atespace: dashes", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my-ate-space" }), + nil, + }, { + "valid atespace: repeat dashes", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my---ate---space" }), + nil, + }, { + "valid atespace: alphanumeric", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my-123-atespace" }), + nil, + }, { + "valid atespace: leading numeric", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "123-atespace" }), + nil, + }, { + "valid atespace: trailing numeric", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my-123" }), + nil, + }, { + "valid atespace: fully numeric", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "123" }), + nil, + }, { + "valid atespace: long", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = strings.Repeat("x", 63) }), + nil, + }, { + "invalid atespace: uppercase", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "MYATESPACE" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: leading dash", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "-atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: trailing dash", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my-" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: dots", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my.atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: underscores", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my_atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: bang", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my!atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: at", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my@atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: pound", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my#atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: dollar", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my$atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: percent", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my%%atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: caret", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my^atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: ampersand", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my&atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: star", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = "my*atespace" }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid atespace: too long", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Atespace = strings.Repeat("x", 64) }), + field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "valid name: alphabetic", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "myname" }), + nil, + }, { + "valid name: dashes", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my-na-me" }), + nil, + }, { + "valid name: repeat dashes", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my---na---me" }), + nil, + }, { + "valid name: alphanumeric", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my-123-name" }), + nil, + }, { + "invalid name: leading numeric", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "123-name" }), + nil, + }, { + "invalid name: trailing numeric", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my-123" }), + nil, + }, { + "invalid name: fully numeric", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "123" }), + nil, + }, { + "valid name: long", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = strings.Repeat("x", 63) }), + nil, + }, { + "invalid name: uppercase", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "MYNAME" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: leading dash", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "-name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: trailing dash", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my-" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: dots", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my.name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: underscores", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my_name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: bang", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my!name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: at", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my@name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: pound", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my#name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: dollar", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my$name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: percent", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my%%name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: caret", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my^name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: ampersand", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my&name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: star", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = "my*name" }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "invalid name: too long", + valid(func(rm *ateapipb.ResourceMetadata) { rm.Name = strings.Repeat("x", 64) }), + field.ErrorList{field.Invalid(field.NewPath("name"), nil, "").WithOrigin("format=k8s-short-name")}, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + obj := proto.CloneOf(tt.obj) // avoid internal mutations + op := operation.Operation{Type: operation.Create, Options: map[string]bool{"validateOutput": true}} + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + matcher.Test(t, tt.want, Validate_ResourceMetadata(context.Background(), op, nil, obj, nil)) + }) + } +} diff --git a/cmd/ateapi/internal/controlapi/zz_generated.validation.go b/cmd/ateapi/internal/controlapi/zz_generated.validation.go new file mode 100644 index 0000000000..30b5d38556 --- /dev/null +++ b/cmd/ateapi/internal/controlapi/zz_generated.validation.go @@ -0,0 +1,418 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Copyright 2026 Google LLC +// +// 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. + +// Code generated by validation-gen. DO NOT EDIT. + +package controlapi + +import ( + context "context" + + ateapipb "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +// Validate_Actor validates an instance of Actor according +// to declarative validation rules in the API schema. +func Validate_Actor( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ateapipb.Actor) (errs field.ErrorList) { + + { // field ateapipb.Actor.Metadata + fn := func( + fldPath *field.Path, + obj, oldObj *ateapipb.ResourceMetadata, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if protoDeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + func() { // cohort = "atespace" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "atespace", + func(o *ateapipb.ResourceMetadata) *string { return &o.Atespace }, validate.DirectEqual, validate.RequiredValue).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "atespace", + func(o *ateapipb.ResourceMetadata) *string { return &o.Atespace }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "atespace", + func(o *ateapipb.ResourceMetadata) *string { return &o.Atespace }, validate.DirectEqual, validate.OptionalValue).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + }() + // call the type's validation function + errs = append(errs, Validate_ResourceMetadata(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.Actor) *ateapipb.ResourceMetadata { + return oldObj.Metadata + }) + errs = append(errs, fn(fldPath.Child("metadata"), obj.Metadata, oldVal, oldObj != nil)...) + } + + // field ateapipb.Actor.ActorTemplateNamespace has no validation + // field ateapipb.Actor.ActorTemplateName has no validation + // field ateapipb.Actor.ActorTemplateVersion has no validation + + { // field ateapipb.Actor.Status + fn := func( + fldPath *field.Path, + obj, oldObj *ateapipb.Actor_Status, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", false, validate.ForbiddenValue).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", false, validate.OptionalValue).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, validate.RequiredValue).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 8); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.Actor) *ateapipb.Actor_Status { + return &oldObj.Status + }) + errs = append(errs, fn(fldPath.Child("status"), &obj.Status, oldVal, oldObj != nil)...) + } + + // field ateapipb.Actor.WorkerAssignment has no validation + // field ateapipb.Actor.InProgressSnapshotName has no validation + // field ateapipb.Actor.WorkerSelector has no validation + // field ateapipb.Actor.LatestSnapshot has no validation + // field ateapipb.Actor.LocalSnapshotInfo has no validation + // field ateapipb.Actor.InProgressSnapshotSourceActorVersion has no validation + // field ateapipb.Actor.ActorVolumes has no validation + // field ateapipb.Actor.InProgressLocalSnapshotName has no validation + // field ateapipb.Actor.SourceSnapshot has no validation + return errs +} + +// Validate_CreateActorRequest validates an instance of CreateActorRequest according +// to declarative validation rules in the API schema. +func Validate_CreateActorRequest( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ateapipb.CreateActorRequest) (errs field.ErrorList) { + + { // field ateapipb.CreateActorRequest.Actor + fn := func( + fldPath *field.Path, + obj, oldObj *ateapipb.Actor, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if protoDeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Actor(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.CreateActorRequest) *ateapipb.Actor { + return oldObj.Actor + }) + errs = append(errs, fn(fldPath.Child("actor"), obj.Actor, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ResourceMetadata validates an instance of ResourceMetadata according +// to declarative validation rules in the API schema. +func Validate_ResourceMetadata( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ateapipb.ResourceMetadata) (errs field.ErrorList) { + + { // field ateapipb.ResourceMetadata.Atespace + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ResourceMetadata) *string { + return &oldObj.Atespace + }) + errs = append(errs, fn(fldPath.Child("atespace"), &obj.Atespace, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ResourceMetadata.Name + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ResourceMetadata) *string { + return &oldObj.Name + }) + errs = append(errs, fn(fldPath.Child("name"), &obj.Name, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ResourceMetadata.Uid + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", false, validate.OptionalValue).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, validate.RequiredValue).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ResourceMetadata) *string { + return &oldObj.Uid + }) + errs = append(errs, fn(fldPath.Child("uid"), &obj.Uid, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ResourceMetadata.Version + fn := func( + fldPath *field.Path, + obj, oldObj *int64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", false, validate.OptionalValue).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, validate.RequiredValue).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int64) field.ErrorList { + return validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a int64, b int64) bool { return a == b }, validate.NoUnset) + }).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, validate.Monotonic).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ResourceMetadata) *int64 { + return &oldObj.Version + }) + errs = append(errs, fn(fldPath.Child("version"), &obj.Version, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ResourceMetadata.CreateTime + fn := func( + fldPath *field.Path, + obj, oldObj *timestamppb.Timestamp, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if protoDeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", false, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, validate.RequiredPointer).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ResourceMetadata) *timestamppb.Timestamp { + return oldObj.CreateTime + }) + errs = append(errs, fn(fldPath.Child("create_time"), obj.CreateTime, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ResourceMetadata.UpdateTime + fn := func( + fldPath *field.Path, + obj, oldObj *timestamppb.Timestamp, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if protoDeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", false, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "validateOutput", true, validate.RequiredPointer).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ResourceMetadata) *timestamppb.Timestamp { + return oldObj.UpdateTime + }) + errs = append(errs, fn(fldPath.Child("update_time"), obj.UpdateTime, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/cmd/ateapi/internal/store/atepg/atepg.go b/cmd/ateapi/internal/store/atepg/atepg.go index ead0b22f8d..73db914f88 100644 --- a/cmd/ateapi/internal/store/atepg/atepg.go +++ b/cmd/ateapi/internal/store/atepg/atepg.go @@ -644,10 +644,7 @@ func (p *Persistence) CreateActor(ctx context.Context, actor *ateapipb.Actor) (* atespace := actor.GetMetadata().GetAtespace() name := actor.GetMetadata().GetName() - dbActor := proto.Clone(actor).(*ateapipb.Actor) - dbActor.Metadata = newCreateMetadata(atespace, name) - - protoBytes, err := proto.Marshal(dbActor) + protoBytes, err := proto.Marshal(actor) if err != nil { return nil, fmt.Errorf("marshaling actor: %w", err) } @@ -655,7 +652,7 @@ func (p *Persistence) CreateActor(ctx context.Context, actor *ateapipb.Actor) (* _, err = p.pool.Exec(ctx, ` INSERT INTO actors (atespace, name, uid, version, proto) VALUES ($1, $2, $3, $4, $5)`, - atespace, name, dbActor.GetMetadata().GetUid(), dbActor.GetMetadata().GetVersion(), protoBytes) + atespace, name, actor.GetMetadata().GetUid(), actor.GetMetadata().GetVersion(), protoBytes) if err != nil { if isUniqueViolation(err) { return nil, store.ErrAlreadyExists @@ -667,7 +664,7 @@ func (p *Persistence) CreateActor(ctx context.Context, actor *ateapipb.Actor) (* } return nil, fmt.Errorf("inserting actor %s/%s: %w", atespace, name, err) } - return dbActor, nil + return actor, nil } func getActorRow(ctx context.Context, q querier, atespace, name string) (*ateapipb.Actor, error) { diff --git a/cmd/ateapi/internal/store/ateredis/ateredis.go b/cmd/ateapi/internal/store/ateredis/ateredis.go index a279627543..b03c6a7f25 100644 --- a/cmd/ateapi/internal/store/ateredis/ateredis.go +++ b/cmd/ateapi/internal/store/ateredis/ateredis.go @@ -727,17 +727,12 @@ func (s *Persistence) GetActor(ctx context.Context, actorRef resources.ActorRef) func (s *Persistence) CreateActor(ctx context.Context, actor *ateapipb.Actor) (*ateapipb.Actor, error) { dbKey := actorDBKey(resources.ActorRefFromActor(actor)) - // Clone so we don't stomp the caller's copy, then attach fresh server-owned - // metadata carrying the caller-specified identity. - dbActor := proto.Clone(actor).(*ateapipb.Actor) - dbActor.Metadata = newCreateMetadata(actor.GetMetadata().GetAtespace(), actor.GetMetadata().GetName()) - - dbActorBytes, err := protojson.Marshal(dbActor) + actorBytes, err := protojson.Marshal(actor) if err != nil { return nil, fmt.Errorf("in protojson.Marshal: %w", err) } - ok, err := s.rdb.SetNX(ctx, dbKey, dbActorBytes, 0).Result() + ok, err := s.rdb.SetNX(ctx, dbKey, actorBytes, 0).Result() if err != nil { return nil, fmt.Errorf("while executing redis set: %w", err) } @@ -745,7 +740,7 @@ func (s *Persistence) CreateActor(ctx context.Context, actor *ateapipb.Actor) (* return nil, store.ErrAlreadyExists } - return dbActor, nil + return actor, nil } func (s *Persistence) CreateActorSnapshot(ctx context.Context, snapshot *ateapipb.ActorSnapshot) (*ateapipb.ActorSnapshot, error) { diff --git a/go.mod b/go.mod index cc6d8d97de..0d97629d28 100644 --- a/go.mod +++ b/go.mod @@ -57,12 +57,12 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a google.golang.org/grpc v1.83.0 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af - k8s.io/api v0.36.1 + k8s.io/api v0.37.0-rc.0 k8s.io/apiextensions-apiserver v0.36.1 - k8s.io/apimachinery v0.36.1 - k8s.io/client-go v0.36.1 + k8s.io/apimachinery v0.37.0-rc.0 + k8s.io/client-go v0.37.0-rc.0 k8s.io/metrics v0.36.1 - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 + k8s.io/utils v0.0.0-20260626114624-be93311217bd sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/yaml v1.6.0 ) @@ -121,24 +121,24 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/jsonpointer v0.22.4 // indirect - github.com/go-openapi/jsonreference v0.21.4 // indirect - github.com/go-openapi/swag v0.25.4 // indirect - github.com/go-openapi/swag/cmdutils v0.25.4 // indirect - github.com/go-openapi/swag/conv v0.25.4 // indirect - github.com/go-openapi/swag/fileutils v0.25.4 // indirect - github.com/go-openapi/swag/jsonname v0.25.4 // indirect - github.com/go-openapi/swag/jsonutils v0.25.4 // indirect - github.com/go-openapi/swag/loading v0.25.4 // indirect - github.com/go-openapi/swag/mangling v0.25.4 // indirect - github.com/go-openapi/swag/netutils v0.25.4 // indirect - github.com/go-openapi/swag/stringutils v0.25.4 // indirect - github.com/go-openapi/swag/typeutils v0.25.4 // indirect - github.com/go-openapi/swag/yamlutils v0.25.4 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/swag v0.27.1 // indirect + github.com/go-openapi/swag/cmdutils v0.27.1 // indirect + github.com/go-openapi/swag/conv v0.27.1 // indirect + github.com/go-openapi/swag/fileutils v0.27.1 // indirect + github.com/go-openapi/swag/jsonutils v0.27.1 // indirect + github.com/go-openapi/swag/loading v0.27.1 // indirect + github.com/go-openapi/swag/mangling v0.27.1 // indirect + github.com/go-openapi/swag/netutils v0.27.1 // indirect + github.com/go-openapi/swag/pools v0.27.1 // indirect + github.com/go-openapi/swag/stringutils v0.27.1 // indirect + github.com/go-openapi/swag/typeutils v0.27.1 // indirect + github.com/go-openapi/swag/yamlutils v0.27.1 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/pprof v0.0.0-20250602020802-c6617b811d0e // indirect github.com/google/s2a-go v0.1.9 // indirect @@ -216,9 +216,11 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect - k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/streaming v0.36.1 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/streaming v0.37.0-rc.0 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect ) + +replace k8s.io/apimachinery => ./third_party/k8s.io/apimachinery diff --git a/go.sum b/go.sum index c29b78a08a..b0426d7a1f 100644 --- a/go.sum +++ b/go.sum @@ -157,8 +157,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -170,40 +170,40 @@ github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= -github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= -github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= -github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= -github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= -github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= -github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= -github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= -github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= -github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= -github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= -github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= -github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= -github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= -github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= -github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= -github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= -github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= -github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= -github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= -github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= -github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= -github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= -github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= -github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= -github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= -github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= -github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= -github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= -github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= -github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= -github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg= +github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE= +github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE= +github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU= +github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs= +github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM= +github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU= +github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY= +github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE= +github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA= +github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU= +github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E= +github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8= +github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc= +github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA= +github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -504,24 +504,22 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= -k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/api v0.37.0-rc.0 h1:CgvGMEmo+Y37oJ7KfUr+ExMDU1isvQwmdgtz8q3ZxTM= +k8s.io/api v0.37.0-rc.0/go.mod h1:T5puuXyM+NMzZo8BRm9d+AW9siY2BqHiw8duWKswpiQ= k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks= k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8= -k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= -k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= -k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= -k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/client-go v0.37.0-rc.0 h1:ZK5uYpvA/R5F69IKVONNEBAk5Ctkovee6Gw/QODCEZI= +k8s.io/client-go v0.37.0-rc.0/go.mod h1:z6ybzfQXKJ6qJIIa0lniOiYtL9a6pCMUo8bN+UpU2uA= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= -k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= k8s.io/metrics v0.36.1 h1:MQPb+G4RhrKEpt8NETPssbW8QgGUc4Jbqu1jx+kPqGk= k8s.io/metrics v0.36.1/go.mod h1:xqS8XcWLjDzo6E7DJm/GfjKpRKdN5/MtJAQFuV6nLUc= -k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4= -k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/streaming v0.37.0-rc.0 h1:zDBjrKCnSLQyznohS8DyT1vmiD1s7XEcj5B9kyfN1WQ= +k8s.io/streaming v0.37.0-rc.0/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= @@ -530,7 +528,7 @@ sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5E sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/hack/run-tool.sh b/hack/run-tool.sh index 8a030425d1..b1f89e4025 100755 --- a/hack/run-tool.sh +++ b/hack/run-tool.sh @@ -30,9 +30,9 @@ fi TOOL_NAME="$1" shift -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ROOT="$(git rev-parse --show-toplevel)" case "${TOOL_NAME}" in - "client-gen"|"informer-gen"|"lister-gen") + "client-gen"|"informer-gen"|"lister-gen"|"validation-gen") TOOL_DIR="${ROOT}/hack/tools/code-generator" ;; *) diff --git a/hack/tools/code-generator/go.mod b/hack/tools/code-generator/go.mod index f41aa538c9..cff84f9145 100644 --- a/hack/tools/code-generator/go.mod +++ b/hack/tools/code-generator/go.mod @@ -3,19 +3,58 @@ module github.com/agent-substrate/substrate/hack/tools/code-generator go 1.26.1 require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/spf13/pflag v1.0.9 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/text v0.31.0 // indirect - golang.org/x/tools v0.38.0 // indirect - k8s.io/code-generator v0.35.0 // indirect - k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect - k8s.io/klog/v2 v2.130.1 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.2 // indirect + github.com/go-logr/logr v1.4.4 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/swag v0.28.0 // indirect + github.com/go-openapi/swag/cmdutils v0.28.0 // indirect + github.com/go-openapi/swag/conv v0.28.0 // indirect + github.com/go-openapi/swag/fileutils v0.28.0 // indirect + github.com/go-openapi/swag/jsonutils v0.28.0 // indirect + github.com/go-openapi/swag/loading v0.28.0 // indirect + github.com/go-openapi/swag/mangling v0.28.0 // indirect + github.com/go-openapi/swag/netutils v0.28.0 // indirect + github.com/go-openapi/swag/pools v0.28.0 // indirect + github.com/go-openapi/swag/stringutils v0.28.0 // indirect + github.com/go-openapi/swag/typeutils v0.28.0 // indirect + github.com/go-openapi/swag/yamlutils v0.28.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.40.0 // indirect + golang.org/x/net v0.58.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/tools v0.49.0 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + k8s.io/apimachinery v0.37.0-rc.0 // indirect + k8s.io/code-generator v0.37.0-rc.0 // indirect + k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3 // indirect + k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/streaming v0.37.0-rc.0 // indirect + k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) tool ( k8s.io/code-generator/cmd/client-gen k8s.io/code-generator/cmd/informer-gen k8s.io/code-generator/cmd/lister-gen + k8s.io/code-generator/cmd/validation-gen ) + +replace k8s.io/code-generator => ./third_party/k8s.io/code-generator + +replace k8s.io/apimachinery => ./third_party/k8s.io/apimachinery diff --git a/hack/tools/code-generator/go.sum b/hack/tools/code-generator/go.sum index 1979b5d1b0..ad13ce3bb2 100644 --- a/hack/tools/code-generator/go.sum +++ b/hack/tools/code-generator/go.sum @@ -1,20 +1,114 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= +github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw= +github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg= +github.com/go-openapi/swag/cmdutils v0.28.0 h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q= +github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= +github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= +github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= +github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= +github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= +github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= +github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= +github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= +github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.28.0 h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k= +github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= +github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= +github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= +github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= +github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +k8s.io/apimachinery v0.37.0-rc.0 h1:z92lapcEJUiMb38pzUIp81kEXT6lIWXhs6auvm8+/s4= +k8s.io/apimachinery v0.37.0-rc.0/go.mod h1:mhq6CPCzI6XJNHSiek+w7Ws9/rP9qL5s+7aBrh5ODSI= k8s.io/code-generator v0.35.0 h1:TvrtfKYZTm9oDF2z+veFKSCcgZE3Igv0svY+ehCmjHQ= k8s.io/code-generator v0.35.0/go.mod h1:iS1gvVf3c/T71N5DOGYO+Gt3PdJ6B9LYSvIyQ4FHzgc= +k8s.io/code-generator v0.37.0-rc.0 h1:d0i65nsz5LS2HUl3CgJcjxFr3IvLBGs82IZth2UuQEk= +k8s.io/code-generator v0.37.0-rc.0/go.mod h1:gwljmdmKalNet8U3+3tu8dyV0+etqGStU2uXJy+NgD8= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= +k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3 h1:3L6PNkMLXkU/pz3jWzaaIUz0Rs2V9h+5O51AeRC7poc= +k8s.io/gengo/v2 v2.0.0-20260408192533-25e2208e0dc3/go.mod h1:yvyl3l9E+UxlqOMUULdKTAYB0rEhsmjr7+2Vb/1pCSo= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/streaming v0.37.0-rc.0 h1:zDBjrKCnSLQyznohS8DyT1vmiD1s7XEcj5B9kyfN1WQ= +k8s.io/streaming v0.37.0-rc.0/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/.github/PULL_REQUEST_TEMPLATE.md b/hack/tools/code-generator/third_party/k8s.io/apimachinery/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..e7e5eb834b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,2 @@ +Sorry, we do not accept changes directly against this repository. Please see +CONTRIBUTING.md for information on where and how to contribute instead. diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/CONTRIBUTING.md b/hack/tools/code-generator/third_party/k8s.io/apimachinery/CONTRIBUTING.md new file mode 100644 index 0000000000..4bcf54520d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# Contributing guidelines + +Do not open pull requests directly against this repository, they will be ignored. Instead, please open pull requests against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/). Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes. + +This repository is published from [kubernetes/kubernetes/staging/src/k8s.io/apimachinery](https://git.k8s.io/kubernetes/staging/src/k8s.io/apimachinery) by the [kubernetes publishing-bot](https://git.k8s.io/publishing-bot). + +Please see [Staging Directory and Publishing](https://git.k8s.io/community/contributors/devel/sig-architecture/staging.md) for more information diff --git a/vendor/github.com/go-openapi/swag/jsonname/LICENSE b/hack/tools/code-generator/third_party/k8s.io/apimachinery/LICENSE similarity index 100% rename from vendor/github.com/go-openapi/swag/jsonname/LICENSE rename to hack/tools/code-generator/third_party/k8s.io/apimachinery/LICENSE diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/OWNERS new file mode 100644 index 0000000000..08b30b3ebc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/OWNERS @@ -0,0 +1,28 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - smarterclayton + - deads2k + - sttts + - liggitt + - caesarxuchao + - jpbetz +reviewers: + - apelisse + - thockin + - smarterclayton + - wojtek-t + - deads2k + - derekwaynecarr + - caesarxuchao + - cheftako + - mikedanese + - liggitt + - sttts + - jpbetz +labels: + - sig/api-machinery +emeritus_approvers: + - lavalamp +emeritus_reviewers: + - ncdc diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/README.md b/hack/tools/code-generator/third_party/k8s.io/apimachinery/README.md new file mode 100644 index 0000000000..420e9fda12 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/README.md @@ -0,0 +1,34 @@ +> ⚠️ **This is an automatically published [staged repository](https://git.k8s.io/kubernetes/staging#external-repository-staging-area) for Kubernetes**. +> Contributions, including issues and pull requests, should be made to the main Kubernetes repository: [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes). +> This repository is read-only for importing, and not used for direct contributions. +> See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. +# apimachinery + +Scheme, typing, encoding, decoding, and conversion packages for Kubernetes and Kubernetes-like API objects. + + +## Purpose + +This library is a shared dependency for servers and clients to work with Kubernetes API infrastructure without direct +type dependencies. Its first consumers are `k8s.io/kubernetes`, `k8s.io/client-go`, and `k8s.io/apiserver`. + + +## Compatibility + +There are *NO compatibility guarantees* for this repository. It is in direct support of Kubernetes, so branches +will track Kubernetes and be compatible with that repo. As we more cleanly separate the layers, we will review the +compatibility guarantee. + + +## Where does it come from? + +`apimachinery` is synced from https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery. +Code changes are made in that location, merged into `k8s.io/kubernetes` and later synced here. + + +## Things you should *NOT* do + + 1. Add API types to this repo. This is for the machinery, not for the types. + 2. Directly modify any files under `pkg` in this repo. Those are driven from `k8s.io/kubernetes/staging/src/k8s.io/apimachinery`. + 3. Expect compatibility. This repo is direct support of Kubernetes and the API isn't yet stable enough for API guarantees. + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/SECURITY_CONTACTS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/SECURITY_CONTACTS new file mode 100644 index 0000000000..f6003980fc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/SECURITY_CONTACTS @@ -0,0 +1,16 @@ +# 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/ + +cheftako +deads2k +lavalamp +sttts diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/code-of-conduct.md b/hack/tools/code-generator/third_party/k8s.io/apimachinery/code-of-conduct.md new file mode 100644 index 0000000000..0d15c00cf3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/code-of-conduct.md @@ -0,0 +1,3 @@ +# 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/hack/tools/code-generator/third_party/k8s.io/apimachinery/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/doc.go new file mode 100644 index 0000000000..1659a0fa6e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/doc.go @@ -0,0 +1,17 @@ +/* +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 apimachinery diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/go.mod b/hack/tools/code-generator/third_party/k8s.io/apimachinery/go.mod new file mode 100644 index 0000000000..c99bb415a7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/go.mod @@ -0,0 +1,62 @@ +// This is a generated file. Do not edit directly. + +module k8s.io/apimachinery + +go 1.26.0 + +godebug default=go1.26 + +require ( + github.com/fxamacker/cbor/v2 v2.9.1 + github.com/google/gnostic-models v0.7.0 + github.com/google/go-cmp v0.7.0 + github.com/google/uuid v1.6.0 + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 + github.com/spf13/pflag v1.0.10 + github.com/stretchr/testify v1.11.1 + golang.org/x/net v0.57.0 + golang.org/x/time v0.15.0 + gopkg.in/evanphx/json-patch.v4 v4.13.0 + gopkg.in/inf.v0 v0.9.1 + k8s.io/klog/v2 v2.140.0 + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad + k8s.io/streaming v0.37.0-rc.0 + k8s.io/utils v0.0.0-20260626114624-be93311217bd + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 + sigs.k8s.io/randfill v1.0.0 + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 + sigs.k8s.io/yaml v1.6.0 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/swag v0.27.1 // indirect + github.com/go-openapi/swag/cmdutils v0.27.1 // indirect + github.com/go-openapi/swag/conv v0.27.1 // indirect + github.com/go-openapi/swag/fileutils v0.27.1 // indirect + github.com/go-openapi/swag/jsonutils v0.27.1 // indirect + github.com/go-openapi/swag/loading v0.27.1 // indirect + github.com/go-openapi/swag/mangling v0.27.1 // indirect + github.com/go-openapi/swag/netutils v0.27.1 // indirect + github.com/go-openapi/swag/pools v0.27.1 // indirect + github.com/go-openapi/swag/stringutils v0.27.1 // indirect + github.com/go-openapi/swag/typeutils v0.27.1 // indirect + github.com/go-openapi/swag/yamlutils v0.27.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/moby/spdystream v0.5.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/text v0.40.0 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/go.sum b/hack/tools/code-generator/third_party/k8s.io/apimachinery/go.sum new file mode 100644 index 0000000000..99df51e773 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/go.sum @@ -0,0 +1,123 @@ +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg= +github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE= +github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE= +github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU= +github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs= +github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM= +github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU= +github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY= +github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE= +github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA= +github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU= +github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E= +github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8= +github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc= +github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA= +github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/streaming v0.37.0-rc.0 h1:zDBjrKCnSLQyznohS8DyT1vmiD1s7XEcj5B9kyfN1WQ= +k8s.io/streaming v0.37.0-rc.0/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/OWNERS new file mode 100644 index 0000000000..18c387cc97 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/OWNERS @@ -0,0 +1,9 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - caesarxuchao + - deads2k + - smarterclayton + - liggitt +emeritus_approvers: + - lavalamp diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/close.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/close.go new file mode 100644 index 0000000000..dfb5657eb9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/close.go @@ -0,0 +1,54 @@ +/* +Copyright 2025 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 apitesting + +import ( + "io" + "testing" +) + +// Close and fail the test if it returns an error. +func Close(t TestingT, c io.Closer) { + t.Helper() + assertNoError(t, c.Close()) +} + +// CloseNoOp does nothing. Use as a replacement for Close when you +// need to disable a defer. +func CloseNoOp(TestingT, io.Closer) {} + +// TestingT simulates assert.TestingT and assert.tHelper without adding +// testify as a non-test dependency. +type TestingT interface { + Errorf(format string, args ...interface{}) + Helper() +} + +// Ensure that testing T & B satisfy the TestingT interface +var _ TestingT = &testing.T{} +var _ TestingT = &testing.B{} + +// assertNoError simulates assert.NoError without adding testify as a +// non-test dependency. +// +// In test files, use github.com/stretchr/testify/assert instead. +func assertNoError(t TestingT, err error) { + t.Helper() + if err != nil { + t.Errorf("Received unexpected error:\n%+v", err) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/codec.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/codec.go new file mode 100644 index 0000000000..542b0aa275 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/codec.go @@ -0,0 +1,116 @@ +/* +Copyright 2017 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 apitesting + +import ( + "fmt" + "mime" + "os" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" +) + +var ( + testCodecMediaType string + testStorageCodecMediaType string +) + +// TestCodec returns the codec for the API version to test against, as set by the +// KUBE_TEST_API_TYPE env var. +func TestCodec(codecs runtimeserializer.CodecFactory, gvs ...schema.GroupVersion) runtime.Codec { + if len(testCodecMediaType) != 0 { + serializerInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), testCodecMediaType) + if !ok { + panic(fmt.Sprintf("no serializer for %s", testCodecMediaType)) + } + return codecs.CodecForVersions(serializerInfo.Serializer, codecs.UniversalDeserializer(), schema.GroupVersions(gvs), nil) + } + return codecs.LegacyCodec(gvs...) +} + +// TestStorageCodec returns the codec for the API version to test against used in storage, as set by the +// KUBE_TEST_API_STORAGE_TYPE env var. +func TestStorageCodec(codecs runtimeserializer.CodecFactory, gvs ...schema.GroupVersion) runtime.Codec { + if len(testStorageCodecMediaType) != 0 { + serializerInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), testStorageCodecMediaType) + if !ok { + panic(fmt.Sprintf("no serializer for %s", testStorageCodecMediaType)) + } + + // etcd2 only supports string data - we must wrap any result before returning + // TODO: remove for etcd3 / make parameterizable + serializer := serializerInfo.Serializer + if !serializerInfo.EncodesAsText { + serializer = runtime.NewBase64Serializer(serializer, serializer) + } + + decoder := recognizer.NewDecoder(serializer, codecs.UniversalDeserializer()) + return codecs.CodecForVersions(serializer, decoder, schema.GroupVersions(gvs), nil) + + } + return codecs.LegacyCodec(gvs...) +} + +func init() { + var err error + if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 { + testCodecMediaType, _, err = mime.ParseMediaType(apiMediaType) + if err != nil { + panic(err) + } + } + + if storageMediaType := os.Getenv("KUBE_TEST_API_STORAGE_TYPE"); len(storageMediaType) > 0 { + testStorageCodecMediaType, _, err = mime.ParseMediaType(storageMediaType) + if err != nil { + panic(err) + } + } +} + +// InstallOrDieFunc mirrors install functions that require success +type InstallOrDieFunc func(scheme *runtime.Scheme) + +// SchemeForInstallOrDie builds a simple test scheme and codecfactory pair for easy unit testing from higher level install methods +func SchemeForInstallOrDie(installFns ...InstallOrDieFunc) (*runtime.Scheme, runtimeserializer.CodecFactory) { + scheme := runtime.NewScheme() + codecFactory := runtimeserializer.NewCodecFactory(scheme) + for _, installFn := range installFns { + installFn(scheme) + } + + return scheme, codecFactory +} + +// InstallFunc mirrors install functions that can return an error +type InstallFunc func(scheme *runtime.Scheme) error + +// SchemeForOrDie builds a simple test scheme and codecfactory pair for easy unit testing from the bare registration methods. +func SchemeForOrDie(installFns ...InstallFunc) (*runtime.Scheme, runtimeserializer.CodecFactory) { + scheme := runtime.NewScheme() + codecFactory := runtimeserializer.NewCodecFactory(scheme) + for _, installFn := range installFns { + if err := installFn(scheme); err != nil { + panic(err) + } + } + + return scheme, codecFactory +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/fuzzer.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/fuzzer.go new file mode 100644 index 0000000000..a12370886f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/fuzzer.go @@ -0,0 +1,73 @@ +/* +Copyright 2017 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 fuzzer + +import ( + "encoding/json" + "fmt" + "math/rand" + + "sigs.k8s.io/randfill" + + "k8s.io/apimachinery/pkg/runtime" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + kjson "k8s.io/apimachinery/pkg/util/json" +) + +// FuzzerFuncs returns a list of func(*SomeType, c randfill.Continue) functions. +type FuzzerFuncs func(codecs runtimeserializer.CodecFactory) []interface{} + +// FuzzerFor can randomly populate api objects that are destined for version. +func FuzzerFor(funcs FuzzerFuncs, src rand.Source, codecs runtimeserializer.CodecFactory) *randfill.Filler { + f := randfill.New().NilChance(.5).NumElements(0, 1) + if src != nil { + f.RandSource(src) + } + f.Funcs(funcs(codecs)...) + return f +} + +// MergeFuzzerFuncs will merge the given funcLists, overriding early funcs with later ones if there first +// argument has the same type. +func MergeFuzzerFuncs(funcs ...FuzzerFuncs) FuzzerFuncs { + return FuzzerFuncs(func(codecs runtimeserializer.CodecFactory) []interface{} { + result := []interface{}{} + for _, f := range funcs { + if f != nil { + result = append(result, f(codecs)...) + } + } + return result + }) +} + +func NormalizeJSONRawExtension(ext *runtime.RawExtension) { + if json.Valid(ext.Raw) { + // RawExtension->JSON encodes struct fields in field index order while map[string]interface{}->JSON encodes + // struct fields (i.e. keys in the map) lexicographically. We have to sort the fields here to ensure the + // JSON in the (RawExtension->)JSON->map[string]interface{}->JSON round trip results in identical JSON. + var u any + err := kjson.Unmarshal(ext.Raw, &u) + if err != nil { + panic(fmt.Sprintf("Failed to encode object: %v", err)) + } + ext.Raw, err = kjson.Marshal(&u) + if err != nil { + panic(fmt.Sprintf("Failed to encode object: %v", err)) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/valuefuzz.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/valuefuzz.go new file mode 100644 index 0000000000..facff57bba --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/valuefuzz.go @@ -0,0 +1,86 @@ +/* +Copyright 2017 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 fuzzer + +import ( + "reflect" +) + +// ValueFuzz recursively changes all basic type values in an object. Any kind of references will not +// be touch, i.e. the addresses of slices, maps, pointers will stay unchanged. +func ValueFuzz(obj interface{}) { + valueFuzz(reflect.ValueOf(obj)) +} + +func valueFuzz(obj reflect.Value) { + switch obj.Kind() { + case reflect.Array: + for i := 0; i < obj.Len(); i++ { + valueFuzz(obj.Index(i)) + } + case reflect.Slice: + if obj.IsNil() { + // TODO: set non-nil value + } else { + for i := 0; i < obj.Len(); i++ { + valueFuzz(obj.Index(i)) + } + } + case reflect.Interface, reflect.Pointer: + if obj.IsNil() { + // TODO: set non-nil value + } else { + valueFuzz(obj.Elem()) + } + case reflect.Struct: + for i, n := 0, obj.NumField(); i < n; i++ { + valueFuzz(obj.Field(i)) + } + case reflect.Map: + if obj.IsNil() { + // TODO: set non-nil value + } else { + for _, k := range obj.MapKeys() { + // map values are not addressable. We need a copy. + v := obj.MapIndex(k) + copy := reflect.New(v.Type()) + copy.Elem().Set(v) + valueFuzz(copy.Elem()) + obj.SetMapIndex(k, copy.Elem()) + } + // TODO: set some new value + } + case reflect.Func: // ignore, we don't have function types in our API + default: + if !obj.CanSet() { + return + } + switch obj.Kind() { + case reflect.String: + obj.SetString(obj.String() + "x") + case reflect.Bool: + obj.SetBool(!obj.Bool()) + case reflect.Float32, reflect.Float64: + obj.SetFloat(obj.Float()*2.0 + 1.0) + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + obj.SetInt(obj.Int() + 1) + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + obj.SetUint(obj.Uint() + 1) + default: + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/valuefuzz_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/valuefuzz_test.go new file mode 100644 index 0000000000..a935aa40c7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/fuzzer/valuefuzz_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2017 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 fuzzer + +import "testing" + +func TestValueFuzz(t *testing.T) { + type ( + Y struct { + I int + B bool + F float32 + U uint + } + X struct { + Ptr *X + Y Y + Map map[string]int + Slice []int + } + ) + + x := X{ + Ptr: &X{}, + Map: map[string]int{"foo": 42}, + Slice: []int{1, 2, 3}, + } + + p := x.Ptr + m := x.Map + s := x.Slice + + ValueFuzz(x) + + if x.Ptr.Y.I == 0 { + t.Errorf("x.Ptr.Y.I should have changed") + } + + if x.Map["foo"] == 42 { + t.Errorf("x.Map[foo] should have changed") + } + + if x.Slice[0] == 1 { + t.Errorf("x.Slice[0] should have changed") + } + + if x.Ptr != p { + t.Errorf("x.Ptr changed") + } + + m["foo"] = 7 + if x.Map["foo"] != m["foo"] { + t.Errorf("x.Map changed") + } + s[0] = 7 + if x.Slice[0] != s[0] { + t.Errorf("x.Slice changed") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/naming/naming.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/naming/naming.go new file mode 100644 index 0000000000..d089b9f53b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/naming/naming.go @@ -0,0 +1,168 @@ +/* +Copyright 2019 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 naming + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" +) + +var ( + marshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem() + unmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() +) + +// VerifyGroupNames ensures that all groups in the scheme ends with the k8s.io suffix. +// Exceptions can be tolerated using the legacyUnsuffixedGroups parameter +func VerifyGroupNames(scheme *runtime.Scheme, legacyUnsuffixedGroups sets.String) error { + errs := []error{} + for _, gv := range scheme.PrioritizedVersionsAllGroups() { + if !strings.HasSuffix(gv.Group, ".k8s.io") && !legacyUnsuffixedGroups.Has(gv.Group) { + errs = append(errs, fmt.Errorf("group %s does not have the standard kubernetes API group suffix of .k8s.io", gv.Group)) + } + } + return errors.NewAggregate(errs) +} + +// VerifyTagNaming ensures that all types in the scheme have JSON tags set on external types, and JSON tags not set on internal types. +// Exceptions can be tolerated using the typesAllowedTags and allowedNonstandardJSONNames parameters +func VerifyTagNaming(scheme *runtime.Scheme, typesAllowedTags map[reflect.Type]bool, allowedNonstandardJSONNames map[reflect.Type]string) error { + errs := []error{} + for gvk, knownType := range scheme.AllKnownTypes() { + var err error + if gvk.Version == runtime.APIVersionInternal { + err = errors.NewAggregate(ensureNoTags(gvk, knownType, nil, typesAllowedTags)) + } else { + err = errors.NewAggregate(ensureTags(gvk, knownType, nil, allowedNonstandardJSONNames)) + } + if err != nil { + errs = append(errs, err) + } + } + return errors.NewAggregate(errs) +} + +func ensureNoTags(gvk schema.GroupVersionKind, tp reflect.Type, parents []reflect.Type, typesAllowedTags map[reflect.Type]bool) []error { + errs := []error{} + if _, ok := typesAllowedTags[tp]; ok { + return errs + } + + // Don't look at the same type multiple times + if containsType(parents, tp) { + return nil + } + parents = append(parents, tp) + + switch tp.Kind() { + case reflect.Map, reflect.Slice, reflect.Pointer: + errs = append(errs, ensureNoTags(gvk, tp.Elem(), parents, typesAllowedTags)...) + + case reflect.String, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Interface: + // no-op + + case reflect.Struct: + for i := 0; i < tp.NumField(); i++ { + f := tp.Field(i) + if f.PkgPath != "" { + continue // Ignore unexported fields + } + _, jsonTagExists := f.Tag.Lookup("json") + protoTag := f.Tag.Get("protobuf") + if jsonTagExists || len(protoTag) > 0 { + errs = append(errs, fmt.Errorf("internal types should not have json or protobuf tags. %#v has tag on field %v: %v.\n%s", gvk, f.Name, f.Tag, fmtParentString(parents))) + } + + errs = append(errs, ensureNoTags(gvk, f.Type, parents, typesAllowedTags)...) + } + + default: + errs = append(errs, fmt.Errorf("unexpected type %v in %#v.\n%s", tp.Kind(), gvk, fmtParentString(parents))) + } + return errs +} + +func ensureTags(gvk schema.GroupVersionKind, tp reflect.Type, parents []reflect.Type, allowedNonstandardJSONNames map[reflect.Type]string) []error { + errs := []error{} + // This type handles its own encoding/decoding and doesn't need json tags + if tp.Implements(marshalerType) && (tp.Implements(unmarshalerType) || reflect.PointerTo(tp).Implements(unmarshalerType)) { + return errs + } + + // Don't look at the same type multiple times + if containsType(parents, tp) { + return nil + } + parents = append(parents, tp) + + switch tp.Kind() { + case reflect.Map, reflect.Slice, reflect.Pointer: + errs = append(errs, ensureTags(gvk, tp.Elem(), parents, allowedNonstandardJSONNames)...) + + case reflect.String, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Interface: + // no-op + + case reflect.Struct: + for i := 0; i < tp.NumField(); i++ { + f := tp.Field(i) + jsonTag, jsonTagExists := f.Tag.Lookup("json") + if len(jsonTag) == 0 { + if f.Anonymous && jsonTagExists { + // allow json:"" on embedded fields + } else { + errs = append(errs, fmt.Errorf("external types should have json tags. %#v tags on field %v are: %s.\n%s", gvk, f.Name, f.Tag, fmtParentString(parents))) + } + } + + jsonTagName := strings.Split(jsonTag, ",")[0] + if len(jsonTagName) > 0 && (jsonTagName[0] < 'a' || jsonTagName[0] > 'z') && jsonTagName != "-" && allowedNonstandardJSONNames[tp] != jsonTagName { + errs = append(errs, fmt.Errorf("external types should have json names starting with lowercase letter. %#v has json tag on field %v with name %s.\n%s", gvk, f.Name, jsonTagName, fmtParentString(parents))) + } + + errs = append(errs, ensureTags(gvk, f.Type, parents, allowedNonstandardJSONNames)...) + } + + default: + errs = append(errs, fmt.Errorf("unexpected type %v in %#v.\n%s", tp.Kind(), gvk, fmtParentString(parents))) + } + return errs +} + +func fmtParentString(parents []reflect.Type) string { + str := "Type parents:\n" + for i, tp := range parents { + str += fmt.Sprintf("%s%v\n", strings.Repeat(" ", i), tp) + } + return str +} + +// containsType returns true if s contains t, false otherwise +func containsType(s []reflect.Type, t reflect.Type) bool { + for _, u := range s { + if t == u { + return true + } + } + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/compatibility.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/compatibility.go new file mode 100644 index 0000000000..deba9b456b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/compatibility.go @@ -0,0 +1,530 @@ +/* +Copyright 2019 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 roundtrip + +import ( + "bytes" + gojson "encoding/json" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" + "k8s.io/apimachinery/pkg/util/sets" +) + +// CompatibilityTestOptions holds configuration for running a compatibility test using in-memory objects +// and serialized files on disk representing the current code and serialized data from previous versions. +// +// Example use: `NewCompatibilityTestOptions(scheme).Complete(t).Run(t)` +type CompatibilityTestOptions struct { + // Scheme is used to create new objects for filling, decoding, and for constructing serializers. + // Required. + Scheme *runtime.Scheme + + // TestDataDir points to a directory containing compatibility test data. + // Complete() populates this with "testdata" if unset. + TestDataDir string + + // TestDataDirCurrentVersion points to a directory containing compatibility test data for the current version. + // Complete() populates this with "/HEAD" if unset. + // Within this directory, `...[json|yaml|pb]` files are required to exist, and are: + // * verified to match serialized FilledObjects[GVK] + // * verified to decode without error + // * verified to round-trip byte-for-byte when re-encoded + // * verified to be semantically equal when decoded into memory + TestDataDirCurrentVersion string + + // TestDataDirsPreviousVersions is a list of directories containing compatibility test data for previous versions. + // Complete() populates this with "/v*" directories if nil. + // Within these directories, `...[json|yaml|pb]` files are optional. If present, they are: + // * verified to decode without error + // * verified to round-trip byte-for-byte when re-encoded (or to match a `...[json|yaml|pb].after_roundtrip.[json|yaml|pb]` file if it exists) + // * verified to be semantically equal when decoded into memory + TestDataDirsPreviousVersions []string + + // Kinds is a list of fully qualified kinds to test. + // Complete() populates this with Scheme.AllKnownTypes() if unset. + Kinds []schema.GroupVersionKind + + // FilledObjects is an optional set of pre-filled objects to use for verifying HEAD fixtures. + // Complete() populates this with the result of CompatibilityTestObject(Kinds[*], Scheme, FillFuncs) for any missing kinds. + // Objects must deterministically populate every field and be identical on every invocation. + FilledObjects map[schema.GroupVersionKind]runtime.Object + + // FillFuncs is an optional map of custom functions to use to fill instances of particular types. + FillFuncs map[reflect.Type]FillFunc + + JSON runtime.Serializer + YAML runtime.Serializer + Proto runtime.Serializer +} + +// FillFunc is a function that populates all serializable fields in obj. +// s and i are string and integer values relevant to the object being populated +// (for example, the json key or protobuf tag containing the object) +// that can be used when filling the object to make the object content identifiable +type FillFunc func(s string, i int, obj interface{}) + +func NewCompatibilityTestOptions(scheme *runtime.Scheme) *CompatibilityTestOptions { + return &CompatibilityTestOptions{Scheme: scheme} +} + +// coreKinds includes kinds that typically only need to be tested in a single API group +var coreKinds = sets.NewString( + "CreateOptions", "UpdateOptions", "PatchOptions", "DeleteOptions", + "GetOptions", "ListOptions", "ExportOptions", + "WatchEvent", +) + +func (c *CompatibilityTestOptions) Complete(t *testing.T) *CompatibilityTestOptions { + t.Helper() + + // Verify scheme + if c.Scheme == nil { + t.Fatal("scheme is required") + } + + // Populate testdata dirs + if c.TestDataDir == "" { + c.TestDataDir = "testdata" + } + if c.TestDataDirCurrentVersion == "" { + c.TestDataDirCurrentVersion = filepath.Join(c.TestDataDir, http.MethodHead) + } + if c.TestDataDirsPreviousVersions == nil { + dirs, err := filepath.Glob(filepath.Join(c.TestDataDir, "v*")) + if err != nil { + t.Fatal(err) + } + sort.Strings(dirs) + c.TestDataDirsPreviousVersions = dirs + } + + // Populate kinds + if len(c.Kinds) == 0 { + gvks := []schema.GroupVersionKind{} + for gvk := range c.Scheme.AllKnownTypes() { + if gvk.Version == "" || gvk.Version == runtime.APIVersionInternal { + // only test external types + continue + } + if strings.HasSuffix(gvk.Kind, "List") { + // omit list types + continue + } + if gvk.Group != "" && coreKinds.Has(gvk.Kind) { + // only test options types in the core API group + continue + } + gvks = append(gvks, gvk) + } + c.Kinds = gvks + } + + // Sort kinds to get deterministic test order + sort.Slice(c.Kinds, func(i, j int) bool { + if c.Kinds[i].Group != c.Kinds[j].Group { + return c.Kinds[i].Group < c.Kinds[j].Group + } + if c.Kinds[i].Version != c.Kinds[j].Version { + return c.Kinds[i].Version < c.Kinds[j].Version + } + if c.Kinds[i].Kind != c.Kinds[j].Kind { + return c.Kinds[i].Kind < c.Kinds[j].Kind + } + return false + }) + + // Fill any missing objects + if c.FilledObjects == nil { + c.FilledObjects = map[schema.GroupVersionKind]runtime.Object{} + } + fillFuncs := defaultFillFuncs() + for k, v := range c.FillFuncs { + fillFuncs[k] = v + } + for _, gvk := range c.Kinds { + if _, ok := c.FilledObjects[gvk]; ok { + continue + } + obj, err := CompatibilityTestObject(c.Scheme, gvk, fillFuncs) + if err != nil { + t.Fatal(err) + } + c.FilledObjects[gvk] = obj + } + + if c.JSON == nil { + c.JSON = json.NewSerializerWithOptions(json.DefaultMetaFactory, c.Scheme, c.Scheme, json.SerializerOptions{Pretty: true}) + } + if c.YAML == nil { + c.YAML = json.NewSerializerWithOptions(json.DefaultMetaFactory, c.Scheme, c.Scheme, json.SerializerOptions{Yaml: true}) + } + if c.Proto == nil { + c.Proto = protobuf.NewSerializer(c.Scheme, c.Scheme) + } + + return c +} + +func (c *CompatibilityTestOptions) Run(t *testing.T) { + usedHEADFixtures := sets.NewString() + + for _, gvk := range c.Kinds { + t.Run(makeName(gvk), func(t *testing.T) { + + t.Run(http.MethodHead, func(t *testing.T) { + c.runCurrentVersionTest(t, gvk, usedHEADFixtures) + }) + + for _, previousVersionDir := range c.TestDataDirsPreviousVersions { + t.Run(filepath.Base(previousVersionDir), func(t *testing.T) { + c.runPreviousVersionTest(t, gvk, previousVersionDir, nil) + }) + } + + }) + } + + // Check for unused HEAD fixtures + t.Run("unused_fixtures", func(t *testing.T) { + files, err := os.ReadDir(c.TestDataDirCurrentVersion) + if err != nil { + t.Fatal(err) + } + allFixtures := sets.NewString() + for _, file := range files { + allFixtures.Insert(file.Name()) + } + + if unused := allFixtures.Difference(usedHEADFixtures); len(unused) > 0 { + t.Fatalf("remove unused fixtures from %s:\n%s", c.TestDataDirCurrentVersion, strings.Join(unused.List(), "\n")) + } + }) +} + +func (c *CompatibilityTestOptions) runCurrentVersionTest(t *testing.T, gvk schema.GroupVersionKind, usedFiles sets.String) { + expectedObject := c.FilledObjects[gvk] + expectedJSON, expectedYAML, expectedProto := c.encode(t, expectedObject) + + actualJSON, actualYAML, actualProto, err := read(c.TestDataDirCurrentVersion, gvk, "", usedFiles) + if err != nil && !os.IsNotExist(err) { + t.Fatal(err) + } + + needsUpdate := false + if os.IsNotExist(err) { + t.Errorf("current version compatibility files did not exist: %v", err) + needsUpdate = true + } else { + if !bytes.Equal(expectedJSON, actualJSON) { + t.Errorf("json differs") + t.Log(cmp.Diff(string(actualJSON), string(expectedJSON))) + needsUpdate = true + } + + if !bytes.Equal(expectedYAML, actualYAML) { + t.Errorf("yaml differs") + t.Log(cmp.Diff(string(actualYAML), string(expectedYAML))) + needsUpdate = true + } + + if !bytes.Equal(expectedProto, actualProto) { + t.Errorf("proto differs") + needsUpdate = true + t.Log(cmp.Diff(dumpProto(t, actualProto[4:]), dumpProto(t, expectedProto[4:]))) + // t.Logf("json (for locating the offending field based on surrounding data): %s", string(expectedJSON)) + } + } + + if needsUpdate { + const updateEnvVar = "UPDATE_COMPATIBILITY_FIXTURE_DATA" + if os.Getenv(updateEnvVar) == "true" { + writeFile(t, c.TestDataDirCurrentVersion, gvk, "", "json", expectedJSON) + writeFile(t, c.TestDataDirCurrentVersion, gvk, "", "yaml", expectedYAML) + writeFile(t, c.TestDataDirCurrentVersion, gvk, "", "pb", expectedProto) + t.Logf("wrote expected compatibility data... verify, commit, and rerun tests") + } else { + t.Logf("if the diff is expected because of a new type or a new field, re-run with %s=true to update the compatibility data", updateEnvVar) + } + return + } + + emptyObj, err := c.Scheme.New(gvk) + if err != nil { + t.Fatal(err) + } + { + // compact before decoding since embedded RawExtension fields retain indenting + compacted := &bytes.Buffer{} + if err := gojson.Compact(compacted, actualJSON); err != nil { + t.Error(err) + } + + jsonDecoded := emptyObj.DeepCopyObject() + jsonDecoded, _, err = c.JSON.Decode(compacted.Bytes(), &gvk, jsonDecoded) + if err != nil { + t.Error(err) + } else if !apiequality.Semantic.DeepEqual(expectedObject, jsonDecoded) { + t.Errorf("expected and decoded json objects differed:\n%s", cmp.Diff(expectedObject, jsonDecoded)) + } + } + { + yamlDecoded := emptyObj.DeepCopyObject() + yamlDecoded, _, err = c.YAML.Decode(actualYAML, &gvk, yamlDecoded) + if err != nil { + t.Error(err) + } else if !apiequality.Semantic.DeepEqual(expectedObject, yamlDecoded) { + t.Errorf("expected and decoded yaml objects differed:\n%s", cmp.Diff(expectedObject, yamlDecoded)) + } + } + { + protoDecoded := emptyObj.DeepCopyObject() + protoDecoded, _, err = c.Proto.Decode(actualProto, &gvk, protoDecoded) + if err != nil { + t.Error(err) + } else if !apiequality.Semantic.DeepEqual(expectedObject, protoDecoded) { + t.Errorf("expected and decoded proto objects differed:\n%s", cmp.Diff(expectedObject, protoDecoded)) + } + } +} + +func (c *CompatibilityTestOptions) encode(t *testing.T, obj runtime.Object) (json, yaml, proto []byte) { + jsonBytes := bytes.NewBuffer(nil) + if err := c.JSON.Encode(obj, jsonBytes); err != nil { + t.Fatalf("error encoding json: %v", err) + } + yamlBytes := bytes.NewBuffer(nil) + if err := c.YAML.Encode(obj, yamlBytes); err != nil { + t.Fatalf("error encoding yaml: %v", err) + } + protoBytes := bytes.NewBuffer(nil) + if err := c.Proto.Encode(obj, protoBytes); err != nil { + t.Fatalf("error encoding proto: %v", err) + } + return jsonBytes.Bytes(), yamlBytes.Bytes(), protoBytes.Bytes() +} + +func read(dir string, gvk schema.GroupVersionKind, suffix string, usedFiles sets.String) (json, yaml, proto []byte, err error) { + jsonFilename := makeName(gvk) + suffix + ".json" + actualJSON, jsonErr := ioutil.ReadFile(filepath.Join(dir, jsonFilename)) + yamlFilename := makeName(gvk) + suffix + ".yaml" + actualYAML, yamlErr := ioutil.ReadFile(filepath.Join(dir, yamlFilename)) + protoFilename := makeName(gvk) + suffix + ".pb" + actualProto, protoErr := ioutil.ReadFile(filepath.Join(dir, protoFilename)) + if usedFiles != nil { + usedFiles.Insert(jsonFilename) + usedFiles.Insert(yamlFilename) + usedFiles.Insert(protoFilename) + } + if jsonErr != nil { + return actualJSON, actualYAML, actualProto, jsonErr + } + if yamlErr != nil { + return actualJSON, actualYAML, actualProto, yamlErr + } + if protoErr != nil { + return actualJSON, actualYAML, actualProto, protoErr + } + return actualJSON, actualYAML, actualProto, nil +} + +func writeFile(t *testing.T, dir string, gvk schema.GroupVersionKind, suffix, extension string, data []byte) { + if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil { + t.Fatal("error making directory", err) + } + if err := ioutil.WriteFile(filepath.Join(dir, makeName(gvk)+suffix+"."+extension), data, os.FileMode(0644)); err != nil { + t.Fatalf("error writing %s: %v", extension, err) + } +} + +func deleteFile(t *testing.T, dir string, gvk schema.GroupVersionKind, suffix, extension string) { + if err := os.Remove(filepath.Join(dir, makeName(gvk)+suffix+"."+extension)); err != nil { + t.Fatalf("error removing %s: %v", extension, err) + } +} + +func (c *CompatibilityTestOptions) runPreviousVersionTest(t *testing.T, gvk schema.GroupVersionKind, previousVersionDir string, usedFiles sets.String) { + jsonBeforeRoundTrip, yamlBeforeRoundTrip, protoBeforeRoundTrip, err := read(previousVersionDir, gvk, "", usedFiles) + if os.IsNotExist(err) || (len(jsonBeforeRoundTrip) == 0 && len(yamlBeforeRoundTrip) == 0 && len(protoBeforeRoundTrip) == 0) { + t.SkipNow() + return + } + if err != nil { + t.Fatal(err) + } + + emptyObj, err := c.Scheme.New(gvk) + if err != nil { + t.Fatal(err) + } + + // compact before decoding since embedded RawExtension fields retain indenting + compacted := &bytes.Buffer{} + if err := gojson.Compact(compacted, jsonBeforeRoundTrip); err != nil { + t.Fatal(err) + } + + jsonDecoded := emptyObj.DeepCopyObject() + jsonDecoded, _, err = c.JSON.Decode(compacted.Bytes(), &gvk, jsonDecoded) + if err != nil { + t.Fatal(err) + } + jsonBytes := bytes.NewBuffer(nil) + if err := c.JSON.Encode(jsonDecoded, jsonBytes); err != nil { + t.Fatalf("error encoding json: %v", err) + } + jsonAfterRoundTrip := jsonBytes.Bytes() + + yamlDecoded := emptyObj.DeepCopyObject() + yamlDecoded, _, err = c.YAML.Decode(yamlBeforeRoundTrip, &gvk, yamlDecoded) + if err != nil { + t.Fatal(err) + } else if !apiequality.Semantic.DeepEqual(jsonDecoded, yamlDecoded) { + t.Errorf("decoded json and yaml objects differ:\n%s", cmp.Diff(jsonDecoded, yamlDecoded)) + } + yamlBytes := bytes.NewBuffer(nil) + if err := c.YAML.Encode(yamlDecoded, yamlBytes); err != nil { + t.Fatalf("error encoding yaml: %v", err) + } + yamlAfterRoundTrip := yamlBytes.Bytes() + + protoDecoded := emptyObj.DeepCopyObject() + protoDecoded, _, err = c.Proto.Decode(protoBeforeRoundTrip, &gvk, protoDecoded) + if err != nil { + t.Fatal(err) + } else if !apiequality.Semantic.DeepEqual(jsonDecoded, protoDecoded) { + t.Errorf("decoded json and proto objects differ:\n%s", cmp.Diff(jsonDecoded, protoDecoded)) + } + protoBytes := bytes.NewBuffer(nil) + if err := c.Proto.Encode(protoDecoded, protoBytes); err != nil { + t.Fatalf("error encoding proto: %v", err) + } + protoAfterRoundTrip := protoBytes.Bytes() + + jsonNeedsRemove := false + yamlNeedsRemove := false + protoNeedsRemove := false + + expectedJSONAfterRoundTrip, expectedYAMLAfterRoundTrip, expectedProtoAfterRoundTrip, _ := read(previousVersionDir, gvk, ".after_roundtrip", usedFiles) + if len(expectedJSONAfterRoundTrip) == 0 { + expectedJSONAfterRoundTrip = jsonBeforeRoundTrip + } else if bytes.Equal(jsonBeforeRoundTrip, expectedJSONAfterRoundTrip) { + t.Errorf("JSON after_roundtrip file is identical and should be removed") + jsonNeedsRemove = true + } + if len(expectedYAMLAfterRoundTrip) == 0 { + expectedYAMLAfterRoundTrip = yamlBeforeRoundTrip + } else if bytes.Equal(yamlBeforeRoundTrip, expectedYAMLAfterRoundTrip) { + t.Errorf("YAML after_roundtrip file is identical and should be removed") + yamlNeedsRemove = true + } + if len(expectedProtoAfterRoundTrip) == 0 { + expectedProtoAfterRoundTrip = protoBeforeRoundTrip + } else if bytes.Equal(protoBeforeRoundTrip, expectedProtoAfterRoundTrip) { + t.Errorf("Proto after_roundtrip file is identical and should be removed") + protoNeedsRemove = true + } + + jsonNeedsUpdate := false + yamlNeedsUpdate := false + protoNeedsUpdate := false + + if !bytes.Equal(expectedJSONAfterRoundTrip, jsonAfterRoundTrip) { + t.Errorf("json differs") + t.Log(cmp.Diff(string(expectedJSONAfterRoundTrip), string(jsonAfterRoundTrip))) + jsonNeedsUpdate = true + } + + if !bytes.Equal(expectedYAMLAfterRoundTrip, yamlAfterRoundTrip) { + t.Errorf("yaml differs") + t.Log(cmp.Diff(string(expectedYAMLAfterRoundTrip), string(yamlAfterRoundTrip))) + yamlNeedsUpdate = true + } + + if !bytes.Equal(expectedProtoAfterRoundTrip, protoAfterRoundTrip) { + t.Errorf("proto differs") + protoNeedsUpdate = true + t.Log(cmp.Diff(dumpProto(t, expectedProtoAfterRoundTrip[4:]), dumpProto(t, protoAfterRoundTrip[4:]))) + // t.Logf("json (for locating the offending field based on surrounding data): %s", string(expectedJSON)) + } + + if jsonNeedsUpdate || yamlNeedsUpdate || protoNeedsUpdate || jsonNeedsRemove || yamlNeedsRemove || protoNeedsRemove { + const updateEnvVar = "UPDATE_COMPATIBILITY_FIXTURE_DATA" + if os.Getenv(updateEnvVar) == "true" { + if jsonNeedsUpdate { + writeFile(t, previousVersionDir, gvk, ".after_roundtrip", "json", jsonAfterRoundTrip) + } else if jsonNeedsRemove { + deleteFile(t, previousVersionDir, gvk, ".after_roundtrip", "json") + } + + if yamlNeedsUpdate { + writeFile(t, previousVersionDir, gvk, ".after_roundtrip", "yaml", yamlAfterRoundTrip) + } else if yamlNeedsRemove { + deleteFile(t, previousVersionDir, gvk, ".after_roundtrip", "yaml") + } + + if protoNeedsUpdate { + writeFile(t, previousVersionDir, gvk, ".after_roundtrip", "pb", protoAfterRoundTrip) + } else if protoNeedsRemove { + deleteFile(t, previousVersionDir, gvk, ".after_roundtrip", "pb") + } + t.Logf("wrote expected compatibility data... verify, commit, and rerun tests") + } else { + t.Logf("if the diff is expected because of a new type or a new field, re-run with %s=true to update the compatibility data", updateEnvVar) + } + return + } +} + +func makeName(gvk schema.GroupVersionKind) string { + g := gvk.Group + if g == "" { + g = "core" + } + return g + "." + gvk.Version + "." + gvk.Kind +} + +func dumpProto(t *testing.T, data []byte) string { + t.Helper() + protoc, err := exec.LookPath("protoc") + if err != nil { + t.Log(err) + return "" + } + cmd := exec.Command(protoc, "--decode_raw") + cmd.Stdin = bytes.NewBuffer(data) + d, err := cmd.CombinedOutput() + if err != nil { + t.Log(err) + return "" + } + return string(d) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/construct.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/construct.go new file mode 100644 index 0000000000..da334a373b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/construct.go @@ -0,0 +1,192 @@ +/* +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 roundtrip + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "time" + + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func defaultFillFuncs() map[reflect.Type]FillFunc { + funcs := map[reflect.Type]FillFunc{} + funcs[reflect.TypeOf(&runtime.RawExtension{})] = func(s string, i int, obj interface{}) { + // generate a raw object in normalized form + // TODO: test non-normalized round-tripping... YAMLToJSON normalizes and makes exact comparisons fail + obj.(*runtime.RawExtension).Raw = []byte(`{"apiVersion":"example.com/v1","kind":"CustomType","spec":{"replicas":1},"status":{"available":1}}`) + } + funcs[reflect.TypeOf(&metav1.TypeMeta{})] = func(s string, i int, obj interface{}) { + // APIVersion and Kind are not serialized in all formats (notably protobuf), so clear by default for cross-format checking. + obj.(*metav1.TypeMeta).APIVersion = "" + obj.(*metav1.TypeMeta).Kind = "" + } + funcs[reflect.TypeOf(&metav1.FieldsV1{})] = func(s string, i int, obj interface{}) { + obj.(*metav1.FieldsV1).SetRawString(`{}`) + } + funcs[reflect.TypeOf(&metav1.Time{})] = func(s string, i int, obj interface{}) { + // use the integer as an offset from the year + obj.(*metav1.Time).Time = time.Date(2000+i, 1, 1, 1, 1, 1, 0, time.UTC) + } + funcs[reflect.TypeOf(&metav1.MicroTime{})] = func(s string, i int, obj interface{}) { + // use the integer as an offset from the year, and as a microsecond + obj.(*metav1.MicroTime).Time = time.Date(2000+i, 1, 1, 1, 1, 1, i*int(time.Microsecond), time.UTC) + } + funcs[reflect.TypeOf(&intstr.IntOrString{})] = func(s string, i int, obj interface{}) { + // use the string as a string value + obj.(*intstr.IntOrString).Type = intstr.String + obj.(*intstr.IntOrString).StrVal = s + "Value" + } + return funcs +} + +// CompatibilityTestObject returns a deterministically filled object for the specified GVK +func CompatibilityTestObject(scheme *runtime.Scheme, gvk schema.GroupVersionKind, fillFuncs map[reflect.Type]FillFunc) (runtime.Object, error) { + // Construct the object + obj, err := scheme.New(gvk) + if err != nil { + return nil, err + } + + fill("", 0, reflect.TypeOf(obj), reflect.ValueOf(obj), fillFuncs, map[reflect.Type]bool{}) + + // Set the kind and apiVersion + if typeAcc, err := apimeta.TypeAccessor(obj); err != nil { + return nil, err + } else { + typeAcc.SetKind(gvk.Kind) + typeAcc.SetAPIVersion(gvk.GroupVersion().String()) + } + + return obj, nil +} + +func fill(dataString string, dataInt int, t reflect.Type, v reflect.Value, fillFuncs map[reflect.Type]FillFunc, filledTypes map[reflect.Type]bool) { + if filledTypes[t] { + // we already filled this type, avoid recursing infinitely + return + } + filledTypes[t] = true + defer delete(filledTypes, t) + + // if nil, populate pointers with a zero-value instance of the underlying type + if t.Kind() == reflect.Pointer && v.IsNil() { + if v.CanSet() { + v.Set(reflect.New(t.Elem())) + } else if v.IsNil() { + panic(fmt.Errorf("unsettable nil pointer of type %v in field %s", t, dataString)) + } + } + + if f, ok := fillFuncs[t]; ok { + // use the custom fill function for this type + f(dataString, dataInt, v.Interface()) + return + } + + switch t.Kind() { + case reflect.Slice: + // populate with a single-item slice + v.Set(reflect.MakeSlice(t, 1, 1)) + // recurse to populate the item, preserving the data context + if t.Elem().Kind() == reflect.Pointer { + fill(dataString, dataInt, t.Elem(), v.Index(0), fillFuncs, filledTypes) + } else { + fill(dataString, dataInt, reflect.PointerTo(t.Elem()), v.Index(0).Addr(), fillFuncs, filledTypes) + } + + case reflect.Map: + // construct the key, which must be a string type, possibly converted to a type alias of string + key := reflect.ValueOf(dataString + "Key").Convert(t.Key()) + // construct a zero-value item + item := reflect.New(t.Elem()) + // recurse to populate the item, preserving the data context + fill(dataString, dataInt, t.Elem(), item.Elem(), fillFuncs, filledTypes) + // store in the map + v.Set(reflect.MakeMap(t)) + v.SetMapIndex(key, item.Elem()) + + case reflect.Struct: + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + if !field.IsExported() { + continue + } + + // use the json field name, which must be stable + jsonTag, _ := field.Tag.Lookup("json") + dataString = strings.Split(jsonTag, ",")[0] + if dataString == "-" { + // unserialized field, no need to fill it + continue + } + if len(dataString) == 0 { + // fall back to the struct field name if there is no json field name + dataString = " " + field.Name + } + + // use the protobuf tag, which must be stable + dataInt := 0 + if protobufTagParts := strings.Split(field.Tag.Get("protobuf"), ","); len(protobufTagParts) > 1 { + if tag, err := strconv.Atoi(protobufTagParts[1]); err != nil { + panic(err) + } else { + dataInt = tag + } + } + if dataInt == 0 { + // fall back to the length of dataString as a backup + dataInt = -len(dataString) + } + + fieldType := field.Type + fieldValue := v.Field(i) + + fill(dataString, dataInt, reflect.PointerTo(fieldType), fieldValue.Addr(), fillFuncs, filledTypes) + } + + case reflect.Pointer: + fill(dataString, dataInt, t.Elem(), v.Elem(), fillFuncs, filledTypes) + + case reflect.String: + // use Convert to set into string alias types correctly + v.Set(reflect.ValueOf(dataString + "Value").Convert(t)) + + case reflect.Bool: + // set to true to ensure we serialize omitempty fields + v.Set(reflect.ValueOf(true).Convert(t)) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // use Convert to set into int alias types and different int widths correctly + v.Set(reflect.ValueOf(dataInt).Convert(t)) + + case reflect.Float32, reflect.Float64: + // use Convert to set into float types + v.Set(reflect.ValueOf(float32(dataInt) + 0.5).Convert(t)) + + default: + panic(fmt.Errorf("unhandled type %v in field %s", t, dataString)) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_norace.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_norace.go new file mode 100644 index 0000000000..23057619b1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_norace.go @@ -0,0 +1,22 @@ +//go:build !race + +/* +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 roundtrip + +// in non-race-detection mode, a higher number of iterations is reasonable +const defaultFuzzIters = 20 diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_race.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_race.go new file mode 100644 index 0000000000..f7ebb8c170 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/fuzz_race.go @@ -0,0 +1,22 @@ +//go:build race + +/* +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 roundtrip + +// in race-detection mode, lower the number of iterations to keep reasonable runtimes in CI +const defaultFuzzIters = 5 diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/roundtrip.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/roundtrip.go new file mode 100644 index 0000000000..7a6cb93f5e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/roundtrip.go @@ -0,0 +1,434 @@ +/* +Copyright 2017 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 roundtrip + +import ( + "bytes" + "encoding/hex" + "math/rand" + "reflect" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + flag "github.com/spf13/pflag" + "sigs.k8s.io/randfill" + + apitesting "k8s.io/apimachinery/pkg/api/apitesting" + "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" + apiequality "k8s.io/apimachinery/pkg/api/equality" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/dump" +) + +type InstallFunc func(scheme *runtime.Scheme) + +// RoundTripTestForAPIGroup is convenient to call from your install package to make sure that a "bare" install of your group provides +// enough information to round trip +func RoundTripTestForAPIGroup(t *testing.T, installFn InstallFunc, fuzzingFuncs fuzzer.FuzzerFuncs) { + scheme := runtime.NewScheme() + installFn(scheme) + + RoundTripTestForScheme(t, scheme, fuzzingFuncs) +} + +// RoundTripTestForScheme is convenient to call if you already have a scheme and want to make sure that its well-formed +func RoundTripTestForScheme(t *testing.T, scheme *runtime.Scheme, fuzzingFuncs fuzzer.FuzzerFuncs) { + codecFactory := runtimeserializer.NewCodecFactory(scheme) + f := fuzzer.FuzzerFor( + fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, fuzzingFuncs), + rand.NewSource(rand.Int63()), + codecFactory, + ) + RoundTripTypesWithoutProtobuf(t, scheme, codecFactory, f, nil) +} + +// RoundTripProtobufTestForAPIGroup is convenient to call from your install package to make sure that a "bare" install of your group provides +// enough information to round trip +func RoundTripProtobufTestForAPIGroup(t *testing.T, installFn InstallFunc, fuzzingFuncs fuzzer.FuzzerFuncs) { + scheme := runtime.NewScheme() + installFn(scheme) + + RoundTripProtobufTestForScheme(t, scheme, fuzzingFuncs) +} + +// RoundTripProtobufTestForScheme is convenient to call if you already have a scheme and want to make sure that its well-formed +func RoundTripProtobufTestForScheme(t *testing.T, scheme *runtime.Scheme, fuzzingFuncs fuzzer.FuzzerFuncs) { + codecFactory := runtimeserializer.NewCodecFactory(scheme) + fuzzer := fuzzer.FuzzerFor( + fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, fuzzingFuncs), + rand.NewSource(rand.Int63()), + codecFactory, + ) + RoundTripTypes(t, scheme, codecFactory, fuzzer, nil) +} + +var FuzzIters = flag.Int("fuzz-iters", defaultFuzzIters, "How many fuzzing iterations to do.") + +// globalNonRoundTrippableTypes are kinds that are effectively reserved across all GroupVersions +// They don't roundtrip +var globalNonRoundTrippableTypes = sets.NewString( + "ExportOptions", + "GetOptions", + // WatchEvent does not include kind and version and can only be deserialized + // implicitly (if the caller expects the specific object). The watch call defines + // the schema by content type, rather than via kind/version included in each + // object. + "WatchEvent", + // ListOptions is now part of the meta group + "ListOptions", + // Delete options is only read in metav1 + "DeleteOptions", +) + +// GlobalNonRoundTrippableTypes returns the kinds that are effectively reserved across all GroupVersions. +// They don't roundtrip and thus can be excluded in any custom/downstream roundtrip tests +// +// kinds := scheme.AllKnownTypes() +// for gvk := range kinds { +// if roundtrip.GlobalNonRoundTrippableTypes().Has(gvk.Kind) { +// continue +// } +// t.Run(gvk.Group+"."+gvk.Version+"."+gvk.Kind, func(t *testing.T) { +// // roundtrip test +// }) +// } +func GlobalNonRoundTrippableTypes() sets.String { + return sets.NewString(globalNonRoundTrippableTypes.List()...) +} + +// RoundTripTypesWithoutProtobuf applies the round-trip test to all round-trippable Kinds +// in the scheme. It will skip all the GroupVersionKinds in the skip list. +func RoundTripTypesWithoutProtobuf(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) { + roundTripTypes(t, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, true) +} + +func RoundTripTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) { + roundTripTypes(t, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, false) +} + +func roundTripTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) { + for _, group := range groupsFromScheme(scheme) { + t.Logf("starting group %q", group) + internalVersion := schema.GroupVersion{Group: group, Version: runtime.APIVersionInternal} + internalKindToGoType := scheme.KnownTypes(internalVersion) + + for kind := range internalKindToGoType { + if globalNonRoundTrippableTypes.Has(kind) { + continue + } + + internalGVK := internalVersion.WithKind(kind) + roundTripSpecificKind(t, internalGVK, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, skipProtobuf) + } + + t.Logf("finished group %q", group) + } +} + +// RoundTripExternalTypes applies the round-trip test to all external round-trippable Kinds +// in the scheme. It will skip all the GroupVersionKinds in the nonRoundTripExternalTypes list . +func RoundTripExternalTypes(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) { + kinds := scheme.AllKnownTypes() + for gvk := range kinds { + if gvk.Version == runtime.APIVersionInternal || globalNonRoundTrippableTypes.Has(gvk.Kind) { + continue + } + t.Run(gvk.Group+"."+gvk.Version+"."+gvk.Kind, func(t *testing.T) { + roundTripSpecificKind(t, gvk, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, false) + }) + } +} + +// RoundTripExternalTypesWithoutProtobuf applies the round-trip test to all external round-trippable Kinds +// in the scheme. It will skip all the GroupVersionKinds in the nonRoundTripExternalTypes list. +func RoundTripExternalTypesWithoutProtobuf(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) { + kinds := scheme.AllKnownTypes() + for gvk := range kinds { + if gvk.Version == runtime.APIVersionInternal || globalNonRoundTrippableTypes.Has(gvk.Kind) { + continue + } + t.Run(gvk.Group+"."+gvk.Version+"."+gvk.Kind, func(t *testing.T) { + roundTripSpecificKind(t, gvk, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, true) + }) + } +} + +func RoundTripSpecificKindWithoutProtobuf(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) { + roundTripSpecificKind(t, gvk, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, true) +} + +func RoundTripSpecificKind(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool) { + roundTripSpecificKind(t, gvk, scheme, codecFactory, fuzzer, nonRoundTrippableTypes, false) +} + +func roundTripSpecificKind(t *testing.T, gvk schema.GroupVersionKind, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) { + if nonRoundTrippableTypes[gvk] { + t.Logf("skipping %v", gvk) + return + } + + // Try a few times, since runTest uses random values. + for i := 0; i < *FuzzIters; i++ { + if gvk.Version == runtime.APIVersionInternal { + roundTripToAllExternalVersions(t, scheme, codecFactory, fuzzer, gvk, nonRoundTrippableTypes, skipProtobuf) + } else { + roundTripOfExternalType(t, scheme, codecFactory, fuzzer, gvk, skipProtobuf) + } + if t.Failed() { + break + } + } +} + +// fuzzInternalObject fuzzes an arbitrary runtime object using the appropriate +// fuzzer registered with the apitesting package. +func fuzzInternalObject(t *testing.T, fuzzer *randfill.Filler, object runtime.Object) runtime.Object { + fuzzer.Fill(object) + + j, err := apimeta.TypeAccessor(object) + if err != nil { + t.Fatalf("Unexpected error %v for %#v", err, object) + } + j.SetKind("") + j.SetAPIVersion("") + + return object +} + +func groupsFromScheme(scheme *runtime.Scheme) []string { + ret := sets.String{} + for gvk := range scheme.AllKnownTypes() { + ret.Insert(gvk.Group) + } + return ret.List() +} + +func roundTripToAllExternalVersions(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, internalGVK schema.GroupVersionKind, nonRoundTrippableTypes map[schema.GroupVersionKind]bool, skipProtobuf bool) { + object, err := scheme.New(internalGVK) + if err != nil { + t.Fatalf("Couldn't make a %v? %v", internalGVK, err) + } + if _, err := apimeta.TypeAccessor(object); err != nil { + t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableInternalTypes: %v", internalGVK, err) + } + + fuzzInternalObject(t, fuzzer, object) + + // find all potential serializations in the scheme. + // TODO fix this up to handle kinds that cross registered with different names. + for externalGVK, externalGoType := range scheme.AllKnownTypes() { + if externalGVK.Version == runtime.APIVersionInternal { + continue + } + if externalGVK.GroupKind() != internalGVK.GroupKind() { + continue + } + if nonRoundTrippableTypes[externalGVK] { + t.Logf("\tskipping %v %v", externalGVK, externalGoType) + continue + } + t.Logf("\tround tripping to %v %v", externalGVK, externalGoType) + + roundTrip(t, scheme, apitesting.TestCodec(codecFactory, externalGVK.GroupVersion()), object) + + // TODO remove this hack after we're past the intermediate steps + if !skipProtobuf && externalGVK.Group != "kubeadm.k8s.io" { + s := protobuf.NewSerializer(scheme, scheme) + protobufCodec := codecFactory.CodecForVersions(s, s, externalGVK.GroupVersion(), nil) + roundTrip(t, scheme, protobufCodec, object) + } + } +} + +func roundTripOfExternalType(t *testing.T, scheme *runtime.Scheme, codecFactory runtimeserializer.CodecFactory, fuzzer *randfill.Filler, externalGVK schema.GroupVersionKind, skipProtobuf bool) { + object, err := scheme.New(externalGVK) + if err != nil { + t.Fatalf("Couldn't make a %v? %v", externalGVK, err) + } + typeAcc, err := apimeta.TypeAccessor(object) + if err != nil { + t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableInternalTypes: %v", externalGVK, err) + } + + fuzzInternalObject(t, fuzzer, object) + + typeAcc.SetKind(externalGVK.Kind) + typeAcc.SetAPIVersion(externalGVK.GroupVersion().String()) + + roundTrip(t, scheme, json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, json.SerializerOptions{}), object) + + // TODO remove this hack after we're past the intermediate steps + if !skipProtobuf { + roundTrip(t, scheme, protobuf.NewSerializer(scheme, scheme), object) + } +} + +// roundTrip applies a single round-trip test to the given runtime object +// using the given codec. The round-trip test ensures that an object can be +// deep-copied, converted, marshaled and back without loss of data. +// +// For internal types this means +// +// internal -> external -> json/protobuf -> external -> internal. +// +// For external types this means +// +// external -> json/protobuf -> external. +func roundTrip(t *testing.T, scheme *runtime.Scheme, codec runtime.Codec, object runtime.Object) { + original := object + + // deep copy the original object + object = object.DeepCopyObject() + name := reflect.TypeOf(object).Elem().Name() + if !apiequality.Semantic.DeepEqual(original, object) { + t.Errorf("%v: DeepCopy altered the object, diff: %v", name, cmp.Diff(original, object)) + t.Errorf("%s", dump.Pretty(original)) + t.Errorf("%s", dump.Pretty(object)) + return + } + + // encode (serialize) the deep copy using the provided codec + data, err := runtime.Encode(codec, object) + if err != nil { + if runtime.IsNotRegisteredError(err) { + t.Logf("%v: not registered: %v (%s)", name, err, dump.Pretty(object)) + } else { + t.Errorf("%v: %v (%s)", name, err, dump.Pretty(object)) + } + return + } + + // ensure that the deep copy is equal to the original; neither the deep + // copy or conversion should alter the object + // TODO eliminate this global + if !apiequality.Semantic.DeepEqual(original, object) { + t.Errorf("%v: encode altered the object, diff: %v", name, cmp.Diff(original, object)) + return + } + + // encode (serialize) a second time to verify that it was not varying + secondData, err := runtime.Encode(codec, object) + if err != nil { + if runtime.IsNotRegisteredError(err) { + t.Logf("%v: not registered: %v (%s)", name, err, dump.Pretty(object)) + } else { + t.Errorf("%v: %v (%s)", name, err, dump.Pretty(object)) + } + return + } + + // serialization to the wire must be stable to ensure that we don't write twice to the DB + // when the object hasn't changed. + if !bytes.Equal(data, secondData) { + t.Errorf("%v: serialization is not stable: %s", name, dump.Pretty(object)) + } + + // decode (deserialize) the encoded data back into an object + obj2, err := runtime.Decode(codec, data) + if err != nil { + t.Errorf("%v: %v\nCodec: %#v\nData: %s\nSource: %s", name, err, codec, dataAsString(data), dump.Pretty(object)) + panic("failed") + } + + // ensure that the object produced from decoding the encoded data is equal + // to the original object + if !apiequality.Semantic.DeepEqual(original, obj2) { + t.Errorf("%v: diff: %v\nCodec: %#v\nSource:\n\n%s\n\nEncoded:\n\n%s\n\nFinal:\n\n%s", name, cmp.Diff(original, obj2), codec, dump.Pretty(original), dataAsString(data), dump.Pretty(obj2)) + return + } + + // decode the encoded data into a new object (instead of letting the codec + // create a new object) + obj3 := reflect.New(reflect.TypeOf(object).Elem()).Interface().(runtime.Object) + if err := runtime.DecodeInto(codec, data, obj3); err != nil { + t.Errorf("%v: %v", name, err) + return + } + + // special case for kinds which are internal and external at the same time (many in meta.k8s.io are). For those + // runtime.DecodeInto above will return the external variant and set the APIVersion and kind, while the input + // object might be internal. Hence, we clear those values for obj3 for that case to correctly compare. + intAndExt, err := internalAndExternalKind(scheme, object) + if err != nil { + t.Errorf("%v: %v", name, err) + return + } + if intAndExt { + typeAcc, err := apimeta.TypeAccessor(object) + if err != nil { + t.Fatalf("%v: error accessing TypeMeta: %v", name, err) + } + if len(typeAcc.GetAPIVersion()) == 0 { + typeAcc, err := apimeta.TypeAccessor(obj3) + if err != nil { + t.Fatalf("%v: error accessing TypeMeta: %v", name, err) + } + typeAcc.SetAPIVersion("") + typeAcc.SetKind("") + } + } + + // ensure that the new runtime object is equal to the original after being + // decoded into + if !apiequality.Semantic.DeepEqual(object, obj3) { + t.Errorf("%v: diff: %v\nCodec: %#v", name, cmp.Diff(object, obj3), codec) + return + } + + // do structure-preserving fuzzing of the deep-copied object. If it shares anything with the original, + // the deep-copy was actually only a shallow copy. Then original and obj3 will be different after fuzzing. + // NOTE: we use the encoding+decoding here as an alternative, guaranteed deep-copy to compare against. + fuzzer.ValueFuzz(object) + if !apiequality.Semantic.DeepEqual(original, obj3) { + t.Errorf("%v: fuzzing a copy altered the original, diff: %v", name, cmp.Diff(original, obj3)) + return + } +} + +func internalAndExternalKind(scheme *runtime.Scheme, object runtime.Object) (bool, error) { + kinds, _, err := scheme.ObjectKinds(object) + if err != nil { + return false, err + } + internal, external := false, false + for _, k := range kinds { + if k.Version == runtime.APIVersionInternal { + internal = true + } else { + external = true + } + } + return internal && external, nil +} + +// dataAsString returns the given byte array as a string; handles detecting +// protocol buffers. +func dataAsString(data []byte) string { + dataString := string(data) + if !strings.HasPrefix(dataString, "{") { + dataString = "\n" + hex.Dump(data) + } + return dataString +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/unstructured.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/unstructured.go new file mode 100644 index 0000000000..f1794943d2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/unstructured.go @@ -0,0 +1,247 @@ +/* +Copyright 2024 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 roundtrip + +import ( + "bytes" + "fmt" + "math/rand" + "os" + "strconv" + "testing" + "time" + + "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" + apiequality "k8s.io/apimachinery/pkg/api/equality" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + cborserializer "k8s.io/apimachinery/pkg/runtime/serializer/cbor" + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/google/go-cmp/cmp" +) + +// RoundtripToUnstructured verifies the roundtrip faithfulness of all external types in a scheme +// from native to unstructured and back using both the JSON and CBOR serializers. The intermediate +// unstructured objects produced by both encodings must be identical and be themselves +// roundtrippable to JSON and CBOR. +// +// Values for all external types in the scheme are generated by fuzzing the a value of the +// corresponding internal type and converting it, except for types whose registered GVK appears in +// the "nointernal" set, which are fuzzed directly. +func RoundtripToUnstructured(t *testing.T, scheme *runtime.Scheme, funcs fuzzer.FuzzerFuncs, skipped sets.Set[schema.GroupVersionKind], nointernal sets.Set[schema.GroupVersionKind]) { + codecs := serializer.NewCodecFactory(scheme) + + seed := int64(time.Now().Nanosecond()) + if override := os.Getenv("TEST_RAND_SEED"); len(override) > 0 { + overrideSeed, err := strconv.ParseInt(override, 10, 64) + if err != nil { + t.Fatal(err) + } + seed = overrideSeed + t.Logf("using overridden seed: %d", seed) + } else { + t.Logf("seed (override with TEST_RAND_SEED if desired): %d", seed) + } + + var buf bytes.Buffer + for gvk := range scheme.AllKnownTypes() { + if globalNonRoundTrippableTypes.Has(gvk.Kind) { + continue + } + + if gvk.Version == runtime.APIVersionInternal { + continue + } + + subtestName := fmt.Sprintf("%s.%s/%s", gvk.Version, gvk.Group, gvk.Kind) + if gvk.Group == "" { + subtestName = fmt.Sprintf("%s/%s", gvk.Version, gvk.Kind) + } + + t.Run(subtestName, func(t *testing.T) { + if skipped.Has(gvk) { + t.SkipNow() + } + + fuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, funcs), rand.NewSource(seed), codecs) + + for i := 0; i < *FuzzIters; i++ { + item, err := scheme.New(gvk) + if err != nil { + t.Fatalf("couldn't create external object %v: %v", gvk.Kind, err) + } + + if nointernal.Has(gvk) { + fuzzer.Fill(item) + } else { + internalObj, err := scheme.New(gvk.GroupKind().WithVersion(runtime.APIVersionInternal)) + if err != nil { + t.Fatalf("couldn't create internal object %v: %v", gvk.Kind, err) + } + fuzzer.Fill(internalObj) + + if err := scheme.Convert(internalObj, item, nil); err != nil { + t.Fatalf("conversion for %v failed: %v", gvk.Kind, err) + } + } + + // Decoding into Unstructured requires that apiVersion and kind be + // serialized, so populate TypeMeta. + item.GetObjectKind().SetGroupVersionKind(gvk) + + jsonSerializer := jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, scheme, scheme, jsonserializer.SerializerOptions{}) + cborSerializer := cborserializer.NewSerializer(scheme, scheme) + + // original->JSON->Unstructured + buf.Reset() + if err := jsonSerializer.Encode(item, &buf); err != nil { + t.Fatalf("error encoding native to json: %v", err) + } + var uJSON runtime.Object = &unstructured.Unstructured{} + uJSON, _, err = jsonSerializer.Decode(buf.Bytes(), &gvk, uJSON) + if err != nil { + t.Fatalf("error decoding json to unstructured: %v", err) + } + + // original->CBOR->Unstructured + buf.Reset() + if err := cborSerializer.Encode(item, &buf); err != nil { + t.Fatalf("error encoding native to cbor: %v", err) + } + var uCBOR runtime.Object = &unstructured.Unstructured{} + uCBOR, _, err = cborSerializer.Decode(buf.Bytes(), &gvk, uCBOR) + if err != nil { + diag, _ := cbor.Diagnose(buf.Bytes()) + t.Fatalf("error decoding cbor to unstructured: %v, diag: %s", err, diag) + } + + // original->JSON->Unstructured == original->CBOR->Unstructured + if !apiequality.Semantic.DeepEqual(uJSON, uCBOR) { + t.Fatalf("unstructured via json differed from unstructured via cbor: %v", cmp.Diff(uJSON, uCBOR)) + } + + // original->CBOR(nondeterministic)->Unstructured + buf.Reset() + if err := cborSerializer.EncodeNondeterministic(item, &buf); err != nil { + t.Fatalf("error encoding native to cbor: %v", err) + } + var uCBORNondeterministic runtime.Object = &unstructured.Unstructured{} + uCBORNondeterministic, _, err = cborSerializer.Decode(buf.Bytes(), &gvk, uCBORNondeterministic) + if err != nil { + diag, _ := cbor.Diagnose(buf.Bytes()) + t.Fatalf("error decoding cbor to unstructured: %v, diag: %s", err, diag) + } + + // original->CBOR->Unstructured == original->CBOR(nondeterministic)->Unstructured + if !apiequality.Semantic.DeepEqual(uCBOR, uCBORNondeterministic) { + t.Fatalf("unstructured via nondeterministic cbor differed from unstructured via cbor: %v", cmp.Diff(uCBOR, uCBORNondeterministic)) + } + + // original->JSON/CBOR->Unstructured == original->JSON/CBOR->Unstructured->JSON->Unstructured + buf.Reset() + if err := jsonSerializer.Encode(uJSON, &buf); err != nil { + t.Fatalf("error encoding unstructured to json: %v", err) + } + var uJSON2 runtime.Object = &unstructured.Unstructured{} + uJSON2, _, err = jsonSerializer.Decode(buf.Bytes(), &gvk, uJSON2) + if err != nil { + t.Fatalf("error decoding json to unstructured: %v", err) + } + if !apiequality.Semantic.DeepEqual(uJSON, uJSON2) { + t.Errorf("object changed during native-json-unstructured-json-unstructured roundtrip, diff: %s", cmp.Diff(uJSON, uJSON2)) + } + + // original->JSON/CBOR->Unstructured == original->JSON/CBOR->Unstructured->CBOR->Unstructured + buf.Reset() + if err := cborSerializer.Encode(uCBOR, &buf); err != nil { + t.Fatalf("error encoding unstructured to cbor: %v", err) + } + var uCBOR2 runtime.Object = &unstructured.Unstructured{} + uCBOR2, _, err = cborSerializer.Decode(buf.Bytes(), &gvk, uCBOR2) + if err != nil { + diag, _ := cbor.Diagnose(buf.Bytes()) + t.Fatalf("error decoding cbor to unstructured: %v, diag: %s", err, diag) + } + if !apiequality.Semantic.DeepEqual(uCBOR, uCBOR2) { + t.Errorf("object changed during native-cbor-unstructured-cbor-unstructured roundtrip, diff: %s", cmp.Diff(uCBOR, uCBOR2)) + } + + // original->JSON/CBOR->Unstructured->CBOR->Unstructured == original->JSON/CBOR->Unstructured->CBOR(nondeterministic)->Unstructured + buf.Reset() + if err := cborSerializer.EncodeNondeterministic(uCBOR, &buf); err != nil { + t.Fatalf("error encoding unstructured to cbor: %v", err) + } + var uCBOR2Nondeterministic runtime.Object = &unstructured.Unstructured{} + uCBOR2Nondeterministic, _, err = cborSerializer.Decode(buf.Bytes(), &gvk, uCBOR2Nondeterministic) + if err != nil { + diag, _ := cbor.Diagnose(buf.Bytes()) + t.Fatalf("error decoding cbor to unstructured: %v, diag: %s", err, diag) + } + if !apiequality.Semantic.DeepEqual(uCBOR, uCBOR2Nondeterministic) { + t.Errorf("object changed during native-cbor-unstructured-cbor(nondeterministic)-unstructured roundtrip, diff: %s", cmp.Diff(uCBOR, uCBOR2Nondeterministic)) + } + + // original->JSON/CBOR->Unstructured->JSON->final == original + buf.Reset() + if err := jsonSerializer.Encode(uJSON, &buf); err != nil { + t.Fatalf("error encoding unstructured to json: %v", err) + } + finalJSON, _, err := jsonSerializer.Decode(buf.Bytes(), &gvk, nil) + if err != nil { + t.Fatalf("error decoding json to native: %v", err) + } + if !apiequality.Semantic.DeepEqual(item, finalJSON) { + t.Errorf("object changed during native-json-unstructured-json-native roundtrip, diff: %s", cmp.Diff(item, finalJSON)) + } + + // original->JSON/CBOR->Unstructured->CBOR->final == original + buf.Reset() + if err := cborSerializer.Encode(uCBOR, &buf); err != nil { + t.Fatalf("error encoding unstructured to cbor: %v", err) + } + finalCBOR, _, err := cborSerializer.Decode(buf.Bytes(), &gvk, nil) + if err != nil { + diag, _ := cbor.Diagnose(buf.Bytes()) + t.Fatalf("error decoding cbor to native: %v, diag: %s", err, diag) + } + if !apiequality.Semantic.DeepEqual(item, finalCBOR) { + t.Errorf("object changed during native-cbor-unstructured-cbor-native roundtrip, diff: %s", cmp.Diff(item, finalCBOR)) + } + + // original->JSON/CBOR->Unstructured->CBOR(nondeterministic)->final == original + buf.Reset() + if err := cborSerializer.EncodeNondeterministic(uCBOR, &buf); err != nil { + t.Fatalf("error encoding unstructured to cbor: %v", err) + } + finalCBORNondeterministic, _, err := cborSerializer.Decode(buf.Bytes(), &gvk, nil) + if err != nil { + diag, _ := cbor.Diagnose(buf.Bytes()) + t.Fatalf("error decoding cbor to native: %v, diag: %s", err, diag) + } + if !apiequality.Semantic.DeepEqual(item, finalCBORNondeterministic) { + t.Errorf("object changed during native-cbor-unstructured-cbor-native roundtrip, diff: %s", cmp.Diff(item, finalCBORNondeterministic)) + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/equality/semantic.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/equality/semantic.go new file mode 100644 index 0000000000..cd78c38416 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/equality/semantic.go @@ -0,0 +1,52 @@ +/* +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 equality + +import ( + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" +) + +// Semantic can do semantic deep equality checks for api objects. +// Example: apiequality.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true +var Semantic = conversion.EqualitiesOrDie( + func(a, b resource.Quantity) bool { + // Ignore formatting, only care that numeric value stayed the same. + // TODO: if we decide it's important, it should be safe to start comparing the format. + // + // Uninitialized quantities are equivalent to 0 quantities. + return a.Cmp(b) == 0 + }, + func(a, b metav1.MicroTime) bool { + return a.UTC() == b.UTC() + }, + func(a, b metav1.Time) bool { + return a.UTC() == b.UTC() + }, + func(a, b metav1.FieldsV1) bool { + return a.Equal(b) + }, + func(a, b labels.Selector) bool { + return a.String() == b.String() + }, + func(a, b fields.Selector) bool { + return a.String() == b.String() + }, +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/OWNERS new file mode 100644 index 0000000000..1a9f5e7706 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/OWNERS @@ -0,0 +1,16 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +reviewers: + - thockin + - smarterclayton + - wojtek-t + - deads2k + - derekwaynecarr + - caesarxuchao + - mikedanese + - liggitt + - saad-ali + - janetkuo + - tallclair + - dims + - cjcullen diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/doc.go new file mode 100644 index 0000000000..58751ed0ec --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/doc.go @@ -0,0 +1,18 @@ +/* +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 errors provides detailed error types for api field validation. +package errors diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/errors.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/errors.go new file mode 100644 index 0000000000..7b57a9eb6c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/errors.go @@ -0,0 +1,865 @@ +/* +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 errors + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "reflect" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// StatusError is an error intended for consumption by a REST API server; it can also be +// reconstructed by clients from a REST response. Public to allow easy type switches. +type StatusError struct { + ErrStatus metav1.Status +} + +// APIStatus is exposed by errors that can be converted to an api.Status object +// for finer grained details. +type APIStatus interface { + Status() metav1.Status +} + +var _ error = &StatusError{} + +var knownReasons = map[metav1.StatusReason]struct{}{ + // metav1.StatusReasonUnknown : {} + metav1.StatusReasonUnauthorized: {}, + metav1.StatusReasonForbidden: {}, + metav1.StatusReasonNotFound: {}, + metav1.StatusReasonAlreadyExists: {}, + metav1.StatusReasonConflict: {}, + metav1.StatusReasonGone: {}, + metav1.StatusReasonInvalid: {}, + metav1.StatusReasonServerTimeout: {}, + metav1.StatusReasonStoreReadError: {}, + metav1.StatusReasonTimeout: {}, + metav1.StatusReasonTooManyRequests: {}, + metav1.StatusReasonBadRequest: {}, + metav1.StatusReasonMethodNotAllowed: {}, + metav1.StatusReasonNotAcceptable: {}, + metav1.StatusReasonRequestEntityTooLarge: {}, + metav1.StatusReasonUnsupportedMediaType: {}, + metav1.StatusReasonInternalError: {}, + metav1.StatusReasonExpired: {}, + metav1.StatusReasonServiceUnavailable: {}, +} + +// Error implements the Error interface. +func (e *StatusError) Error() string { + return e.ErrStatus.Message +} + +// Status allows access to e's status without having to know the detailed workings +// of StatusError. +func (e *StatusError) Status() metav1.Status { + return e.ErrStatus +} + +// DebugError reports extended info about the error to debug output. +func (e *StatusError) DebugError() (string, []interface{}) { + if out, err := json.MarshalIndent(e.ErrStatus, "", " "); err == nil { + return "server response object: %s", []interface{}{string(out)} + } + return "server response object: %#v", []interface{}{e.ErrStatus} +} + +// HasStatusCause returns true if the provided error has a details cause +// with the provided type name. +// It supports wrapped errors and returns false when the error is nil. +func HasStatusCause(err error, name metav1.CauseType) bool { + _, ok := StatusCause(err, name) + return ok +} + +// StatusCause returns the named cause from the provided error if it exists and +// the error unwraps to the type APIStatus. Otherwise it returns false. +func StatusCause(err error, name metav1.CauseType) (metav1.StatusCause, bool) { + status, ok := err.(APIStatus) + if (ok || errors.As(err, &status)) && status.Status().Details != nil { + for _, cause := range status.Status().Details.Causes { + if cause.Type == name { + return cause, true + } + } + } + return metav1.StatusCause{}, false +} + +// UnexpectedObjectError can be returned by FromObject if it's passed a non-status object. +type UnexpectedObjectError struct { + Object runtime.Object +} + +// Error returns an error message describing 'u'. +func (u *UnexpectedObjectError) Error() string { + return fmt.Sprintf("unexpected object: %v", u.Object) +} + +// FromObject generates an StatusError from an metav1.Status, if that is the type of obj; otherwise, +// returns an UnexpecteObjectError. +func FromObject(obj runtime.Object) error { + switch t := obj.(type) { + case *metav1.Status: + return &StatusError{ErrStatus: *t} + case runtime.Unstructured: + var status metav1.Status + obj := t.UnstructuredContent() + if !reflect.DeepEqual(obj["kind"], "Status") { + break + } + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(t.UnstructuredContent(), &status); err != nil { + return err + } + if status.APIVersion != "v1" && status.APIVersion != "meta.k8s.io/v1" { + break + } + return &StatusError{ErrStatus: status} + } + return &UnexpectedObjectError{obj} +} + +// NewNotFound returns a new error which indicates that the resource of the kind and the name was not found. +func NewNotFound(qualifiedResource schema.GroupResource, name string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusNotFound, + Reason: metav1.StatusReasonNotFound, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + Name: name, + }, + Message: fmt.Sprintf("%s %q not found", qualifiedResource.String(), name), + }} +} + +// NewAlreadyExists returns an error indicating the item requested exists by that identifier. +func NewAlreadyExists(qualifiedResource schema.GroupResource, name string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusConflict, + Reason: metav1.StatusReasonAlreadyExists, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + Name: name, + }, + Message: fmt.Sprintf("%s %q already exists", qualifiedResource.String(), name), + }} +} + +// NewGenerateNameConflict returns an error indicating the server +// was not able to generate a valid name for a resource. +func NewGenerateNameConflict(qualifiedResource schema.GroupResource, name string, retryAfterSeconds int) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusConflict, + Reason: metav1.StatusReasonAlreadyExists, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + Name: name, + RetryAfterSeconds: int32(retryAfterSeconds), + }, + Message: fmt.Sprintf( + "%s %q already exists, the server was not able to generate a unique name for the object", + qualifiedResource.String(), name), + }} +} + +// NewUnauthorized returns an error indicating the client is not authorized to perform the requested +// action. +func NewUnauthorized(reason string) *StatusError { + message := reason + if len(message) == 0 { + message = "not authorized" + } + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusUnauthorized, + Reason: metav1.StatusReasonUnauthorized, + Message: message, + }} +} + +// NewForbidden returns an error indicating the requested action was forbidden +func NewForbidden(qualifiedResource schema.GroupResource, name string, err error) *StatusError { + var message string + if qualifiedResource.Empty() { + message = fmt.Sprintf("forbidden: %v", err) + } else if name == "" { + message = fmt.Sprintf("%s is forbidden: %v", qualifiedResource.String(), err) + } else { + message = fmt.Sprintf("%s %q is forbidden: %v", qualifiedResource.String(), name, err) + } + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusForbidden, + Reason: metav1.StatusReasonForbidden, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + Name: name, + }, + Message: message, + }} +} + +// NewConflict returns an error indicating the item can't be updated as provided. +func NewConflict(qualifiedResource schema.GroupResource, name string, err error) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusConflict, + Reason: metav1.StatusReasonConflict, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + Name: name, + }, + Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err), + }} +} + +// NewApplyConflict returns an error including details on the requests apply conflicts +func NewApplyConflict(causes []metav1.StatusCause, message string) *StatusError { + return &StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusConflict, + Reason: metav1.StatusReasonConflict, + Details: &metav1.StatusDetails{ + // TODO: Get obj details here? + Causes: causes, + }, + Message: message, + }} +} + +// NewGone returns an error indicating the item no longer available at the server and no forwarding address is known. +// +// Deprecated: Please use NewResourceExpired instead. +func NewGone(message string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusGone, + Reason: metav1.StatusReasonGone, + Message: message, + }} +} + +// NewResourceExpired creates an error that indicates that the requested resource content has expired from +// the server (usually due to a resourceVersion that is too old). +func NewResourceExpired(message string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusGone, + Reason: metav1.StatusReasonExpired, + Message: message, + }} +} + +// NewInvalid returns an error indicating the item is invalid and cannot be processed. +func NewInvalid(qualifiedKind schema.GroupKind, name string, errs field.ErrorList) *StatusError { + causes := make([]metav1.StatusCause, 0, len(errs)) + for i := range errs { + err := errs[i] + causes = append(causes, metav1.StatusCause{ + Type: metav1.CauseType(err.Type), + Message: err.ErrorBody(), + Field: err.Field, + }) + } + err := &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusUnprocessableEntity, + Reason: metav1.StatusReasonInvalid, + Details: &metav1.StatusDetails{ + Group: qualifiedKind.Group, + Kind: qualifiedKind.Kind, + Name: name, + Causes: causes, + }, + }} + aggregatedErrs := errs.ToAggregate() + if aggregatedErrs == nil { + err.ErrStatus.Message = fmt.Sprintf("%s %q is invalid", qualifiedKind.String(), name) + } else { + err.ErrStatus.Message = fmt.Sprintf("%s %q is invalid: %v", qualifiedKind.String(), name, aggregatedErrs) + } + return err +} + +// NewBadRequest creates an error that indicates that the request is invalid and can not be processed. +func NewBadRequest(reason string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusBadRequest, + Reason: metav1.StatusReasonBadRequest, + Message: reason, + }} +} + +// NewTooManyRequests creates an error that indicates that the client must try again later because +// the specified endpoint is not accepting requests. More specific details should be provided +// if client should know why the failure was limited. +func NewTooManyRequests(message string, retryAfterSeconds int) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusTooManyRequests, + Reason: metav1.StatusReasonTooManyRequests, + Message: message, + Details: &metav1.StatusDetails{ + RetryAfterSeconds: int32(retryAfterSeconds), + }, + }} +} + +// NewServiceUnavailable creates an error that indicates that the requested service is unavailable. +func NewServiceUnavailable(reason string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusServiceUnavailable, + Reason: metav1.StatusReasonServiceUnavailable, + Message: reason, + }} +} + +// NewMethodNotSupported returns an error indicating the requested action is not supported on this kind. +func NewMethodNotSupported(qualifiedResource schema.GroupResource, action string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusMethodNotAllowed, + Reason: metav1.StatusReasonMethodNotAllowed, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + }, + Message: fmt.Sprintf("%s is not supported on resources of kind %q", action, qualifiedResource.String()), + }} +} + +// NewServerTimeout returns an error indicating the requested action could not be completed due to a +// transient error, and the client should try again. +func NewServerTimeout(qualifiedResource schema.GroupResource, operation string, retryAfterSeconds int) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusInternalServerError, + Reason: metav1.StatusReasonServerTimeout, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + Name: operation, + RetryAfterSeconds: int32(retryAfterSeconds), + }, + Message: fmt.Sprintf("The %s operation against %s could not be completed at this time, please try again.", operation, qualifiedResource.String()), + }} +} + +// NewServerTimeoutForKind should not exist. Server timeouts happen when accessing resources, the Kind is just what we +// happened to be looking at when the request failed. This delegates to keep code sane, but we should work towards removing this. +func NewServerTimeoutForKind(qualifiedKind schema.GroupKind, operation string, retryAfterSeconds int) *StatusError { + return NewServerTimeout(schema.GroupResource{Group: qualifiedKind.Group, Resource: qualifiedKind.Kind}, operation, retryAfterSeconds) +} + +// NewInternalError returns an error indicating the item is invalid and cannot be processed. +func NewInternalError(err error) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusInternalServerError, + Reason: metav1.StatusReasonInternalError, + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{{Message: err.Error()}}, + }, + Message: fmt.Sprintf("Internal error occurred: %v", err), + }} +} + +// NewTimeoutError returns an error indicating that a timeout occurred before the request +// could be completed. Clients may retry, but the operation may still complete. +func NewTimeoutError(message string, retryAfterSeconds int) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusGatewayTimeout, + Reason: metav1.StatusReasonTimeout, + Message: fmt.Sprintf("Timeout: %s", message), + Details: &metav1.StatusDetails{ + RetryAfterSeconds: int32(retryAfterSeconds), + }, + }} +} + +// NewTooManyRequestsError returns an error indicating that the request was rejected because +// the server has received too many requests. Client should wait and retry. But if the request +// is perishable, then the client should not retry the request. +func NewTooManyRequestsError(message string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusTooManyRequests, + Reason: metav1.StatusReasonTooManyRequests, + Message: fmt.Sprintf("Too many requests: %s", message), + }} +} + +// NewRequestEntityTooLargeError returns an error indicating that the request +// entity was too large. +func NewRequestEntityTooLargeError(message string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusRequestEntityTooLarge, + Reason: metav1.StatusReasonRequestEntityTooLarge, + Message: fmt.Sprintf("Request entity too large: %s", message), + }} +} + +// NewGenericServerResponse returns a new error for server responses that are not in a recognizable form. +func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError { + reason := metav1.StatusReasonUnknown + message := fmt.Sprintf("the server responded with the status code %d but did not return more information", code) + switch code { + case http.StatusConflict: + if verb == http.MethodPost { + reason = metav1.StatusReasonAlreadyExists + } else { + reason = metav1.StatusReasonConflict + } + message = "the server reported a conflict" + case http.StatusNotFound: + reason = metav1.StatusReasonNotFound + message = "the server could not find the requested resource" + case http.StatusBadRequest: + reason = metav1.StatusReasonBadRequest + message = "the server rejected our request for an unknown reason" + case http.StatusUnauthorized: + reason = metav1.StatusReasonUnauthorized + message = "the server has asked for the client to provide credentials" + case http.StatusForbidden: + reason = metav1.StatusReasonForbidden + // the server message has details about who is trying to perform what action. Keep its message. + message = serverMessage + case http.StatusNotAcceptable: + reason = metav1.StatusReasonNotAcceptable + // the server message has details about what types are acceptable + if len(serverMessage) == 0 || serverMessage == "unknown" { + message = "the server was unable to respond with a content type that the client supports" + } else { + message = serverMessage + } + case http.StatusUnsupportedMediaType: + reason = metav1.StatusReasonUnsupportedMediaType + // the server message has details about what types are acceptable + message = serverMessage + case http.StatusMethodNotAllowed: + reason = metav1.StatusReasonMethodNotAllowed + message = "the server does not allow this method on the requested resource" + case http.StatusUnprocessableEntity: + reason = metav1.StatusReasonInvalid + message = "the server rejected our request due to an error in our request" + case http.StatusServiceUnavailable: + reason = metav1.StatusReasonServiceUnavailable + message = "the server is currently unable to handle the request" + case http.StatusGatewayTimeout: + reason = metav1.StatusReasonTimeout + message = "the server was unable to return a response in the time allotted, but may still be processing the request" + case http.StatusTooManyRequests: + reason = metav1.StatusReasonTooManyRequests + message = "the server has received too many requests and has asked us to try again later" + default: + if code >= 500 { + reason = metav1.StatusReasonInternalError + message = fmt.Sprintf("an error on the server (%q) has prevented the request from succeeding", serverMessage) + } + } + switch { + case !qualifiedResource.Empty() && len(name) > 0: + message = fmt.Sprintf("%s (%s %s %s)", message, strings.ToLower(verb), qualifiedResource.String(), name) + case !qualifiedResource.Empty(): + message = fmt.Sprintf("%s (%s %s)", message, strings.ToLower(verb), qualifiedResource.String()) + } + var causes []metav1.StatusCause + if isUnexpectedResponse { + causes = []metav1.StatusCause{ + { + Type: metav1.CauseTypeUnexpectedServerResponse, + Message: serverMessage, + }, + } + } else { + causes = nil + } + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: int32(code), + Reason: reason, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + Name: name, + + Causes: causes, + RetryAfterSeconds: int32(retryAfterSeconds), + }, + Message: message, + }} +} + +// IsNotFound returns true if the specified error was created by NewNotFound. +// It supports wrapped errors and returns false when the error is nil. +func IsNotFound(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonNotFound { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusNotFound { + return true + } + return false +} + +// IsAlreadyExists determines if the err is an error which indicates that a specified resource already exists. +// It supports wrapped errors and returns false when the error is nil. +func IsAlreadyExists(err error) bool { + return ReasonForError(err) == metav1.StatusReasonAlreadyExists +} + +// IsConflict determines if the err is an error which indicates the provided update conflicts. +// It supports wrapped errors and returns false when the error is nil. +func IsConflict(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonConflict { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusConflict { + return true + } + return false +} + +// IsInvalid determines if the err is an error which indicates the provided resource is not valid. +// It supports wrapped errors and returns false when the error is nil. +func IsInvalid(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonInvalid { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusUnprocessableEntity { + return true + } + return false +} + +// IsGone is true if the error indicates the requested resource is no longer available. +// It supports wrapped errors and returns false when the error is nil. +func IsGone(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonGone { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusGone { + return true + } + return false +} + +// IsResourceExpired is true if the error indicates the resource has expired and the current action is +// no longer possible. +// It supports wrapped errors and returns false when the error is nil. +func IsResourceExpired(err error) bool { + return ReasonForError(err) == metav1.StatusReasonExpired +} + +// IsNotAcceptable determines if err is an error which indicates that the request failed due to an invalid Accept header +// It supports wrapped errors and returns false when the error is nil. +func IsNotAcceptable(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonNotAcceptable { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusNotAcceptable { + return true + } + return false +} + +// IsUnsupportedMediaType determines if err is an error which indicates that the request failed due to an invalid Content-Type header +// It supports wrapped errors and returns false when the error is nil. +func IsUnsupportedMediaType(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonUnsupportedMediaType { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusUnsupportedMediaType { + return true + } + return false +} + +// IsMethodNotSupported determines if the err is an error which indicates the provided action could not +// be performed because it is not supported by the server. +// It supports wrapped errors and returns false when the error is nil. +func IsMethodNotSupported(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonMethodNotAllowed { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusMethodNotAllowed { + return true + } + return false +} + +// IsServiceUnavailable is true if the error indicates the underlying service is no longer available. +// It supports wrapped errors and returns false when the error is nil. +func IsServiceUnavailable(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonServiceUnavailable { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusServiceUnavailable { + return true + } + return false +} + +// IsBadRequest determines if err is an error which indicates that the request is invalid. +// It supports wrapped errors and returns false when the error is nil. +func IsBadRequest(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonBadRequest { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusBadRequest { + return true + } + return false +} + +// IsUnauthorized determines if err is an error which indicates that the request is unauthorized and +// requires authentication by the user. +// It supports wrapped errors and returns false when the error is nil. +func IsUnauthorized(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonUnauthorized { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusUnauthorized { + return true + } + return false +} + +// IsForbidden determines if err is an error which indicates that the request is forbidden and cannot +// be completed as requested. +// It supports wrapped errors and returns false when the error is nil. +func IsForbidden(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonForbidden { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusForbidden { + return true + } + return false +} + +// IsTimeout determines if err is an error which indicates that request times out due to long +// processing. +// It supports wrapped errors and returns false when the error is nil. +func IsTimeout(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonTimeout { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusGatewayTimeout { + return true + } + return false +} + +// IsServerTimeout determines if err is an error which indicates that the request needs to be retried +// by the client. +// It supports wrapped errors and returns false when the error is nil. +func IsServerTimeout(err error) bool { + // do not check the status code, because no https status code exists that can + // be scoped to retryable timeouts. + return ReasonForError(err) == metav1.StatusReasonServerTimeout +} + +// IsInternalError determines if err is an error which indicates an internal server error. +// It supports wrapped errors and returns false when the error is nil. +func IsInternalError(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonInternalError { + return true + } + if _, ok := knownReasons[reason]; !ok && code == http.StatusInternalServerError { + return true + } + return false +} + +// IsTooManyRequests determines if err is an error which indicates that there are too many requests +// that the server cannot handle. +// It supports wrapped errors and returns false when the error is nil. +func IsTooManyRequests(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonTooManyRequests { + return true + } + + // IsTooManyRequests' checking of code predates the checking of the code in + // the other Is* functions. In order to maintain backward compatibility, this + // does not check that the reason is unknown. + if code == http.StatusTooManyRequests { + return true + } + return false +} + +// IsRequestEntityTooLargeError determines if err is an error which indicates +// the request entity is too large. +// It supports wrapped errors and returns false when the error is nil. +func IsRequestEntityTooLargeError(err error) bool { + reason, code := reasonAndCodeForError(err) + if reason == metav1.StatusReasonRequestEntityTooLarge { + return true + } + + // IsRequestEntityTooLargeError's checking of code predates the checking of + // the code in the other Is* functions. In order to maintain backward + // compatibility, this does not check that the reason is unknown. + if code == http.StatusRequestEntityTooLarge { + return true + } + return false +} + +// IsUnexpectedServerError returns true if the server response was not in the expected API format, +// and may be the result of another HTTP actor. +// It supports wrapped errors and returns false when the error is nil. +func IsUnexpectedServerError(err error) bool { + status, ok := err.(APIStatus) + if (ok || errors.As(err, &status)) && status.Status().Details != nil { + for _, cause := range status.Status().Details.Causes { + if cause.Type == metav1.CauseTypeUnexpectedServerResponse { + return true + } + } + } + return false +} + +// IsUnexpectedObjectError determines if err is due to an unexpected object from the master. +// It supports wrapped errors and returns false when the error is nil. +func IsUnexpectedObjectError(err error) bool { + uoe, ok := err.(*UnexpectedObjectError) + return err != nil && (ok || errors.As(err, &uoe)) +} + +// IsStoreReadError determines if err is due to either failure to transform the +// data from the storage, or failure to decode the object appropriately. +func IsStoreReadError(err error) bool { + return ReasonForError(err) == metav1.StatusReasonStoreReadError +} + +// SuggestsClientDelay returns true if this error suggests a client delay as well as the +// suggested seconds to wait, or false if the error does not imply a wait. It does not +// address whether the error *should* be retried, since some errors (like a 3xx) may +// request delay without retry. +// It supports wrapped errors and returns false when the error is nil. +func SuggestsClientDelay(err error) (int, bool) { + t, ok := err.(APIStatus) + if (ok || errors.As(err, &t)) && t.Status().Details != nil { + switch t.Status().Reason { + // this StatusReason explicitly requests the caller to delay the action + case metav1.StatusReasonServerTimeout: + return int(t.Status().Details.RetryAfterSeconds), true + } + // If the client requests that we retry after a certain number of seconds + if t.Status().Details.RetryAfterSeconds > 0 { + return int(t.Status().Details.RetryAfterSeconds), true + } + } + return 0, false +} + +// ReasonForError returns the HTTP status for a particular error. +// It supports wrapped errors and returns StatusReasonUnknown when +// the error is nil or doesn't have a status. +func ReasonForError(err error) metav1.StatusReason { + if status, ok := err.(APIStatus); ok || errors.As(err, &status) { + return status.Status().Reason + } + return metav1.StatusReasonUnknown +} + +func reasonAndCodeForError(err error) (metav1.StatusReason, int32) { + if status, ok := err.(APIStatus); ok || errors.As(err, &status) { + return status.Status().Reason, status.Status().Code + } + return metav1.StatusReasonUnknown, 0 +} + +// ErrorReporter converts generic errors into runtime.Object errors without +// requiring the caller to take a dependency on meta/v1 (where Status lives). +// This prevents circular dependencies in core watch code. +type ErrorReporter struct { + code int + verb string + reason string +} + +// NewClientErrorReporter will respond with valid v1.Status objects that report +// unexpected server responses. Primarily used by watch to report errors when +// we attempt to decode a response from the server and it is not in the form +// we expect. Because watch is a dependency of the core api, we can't return +// meta/v1.Status in that package and so much inject this interface to convert a +// generic error as appropriate. The reason is passed as a unique status cause +// on the returned status, otherwise the generic "ClientError" is returned. +func NewClientErrorReporter(code int, verb string, reason string) *ErrorReporter { + return &ErrorReporter{ + code: code, + verb: verb, + reason: reason, + } +} + +// AsObject returns a valid error runtime.Object (a v1.Status) for the given +// error, using the code and verb of the reporter type. The error is set to +// indicate that this was an unexpected server response. +func (r *ErrorReporter) AsObject(err error) runtime.Object { + status := NewGenericServerResponse(r.code, r.verb, schema.GroupResource{}, "", err.Error(), 0, true) + if status.ErrStatus.Details == nil { + status.ErrStatus.Details = &metav1.StatusDetails{} + } + reason := r.reason + if len(reason) == 0 { + reason = "ClientError" + } + status.ErrStatus.Details.Causes = append(status.ErrStatus.Details.Causes, metav1.StatusCause{ + Type: metav1.CauseType(reason), + Message: err.Error(), + }) + return &status.ErrStatus +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/errors_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/errors_test.go new file mode 100644 index 0000000000..2268d652f6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/errors/errors_test.go @@ -0,0 +1,705 @@ +/* +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 errors + +import ( + "errors" + "fmt" + "net/http" + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func resource(resource string) schema.GroupResource { + return schema.GroupResource{Group: "", Resource: resource} +} +func kind(kind string) schema.GroupKind { + return schema.GroupKind{Group: "", Kind: kind} +} + +func TestErrorNew(t *testing.T) { + err := NewAlreadyExists(resource("tests"), "1") + if !IsAlreadyExists(err) { + t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) + } + if IsConflict(err) { + t.Errorf("expected to not be %s", metav1.StatusReasonConflict) + } + if IsNotFound(err) { + t.Errorf("expected to not be %s", metav1.StatusReasonNotFound) + } + if IsInvalid(err) { + t.Errorf("expected to not be %s", metav1.StatusReasonInvalid) + } + if IsBadRequest(err) { + t.Errorf("expected to not be %s", metav1.StatusReasonBadRequest) + } + if IsForbidden(err) { + t.Errorf("expected to not be %s", metav1.StatusReasonForbidden) + } + if IsServerTimeout(err) { + t.Errorf("expected to not be %s", metav1.StatusReasonServerTimeout) + } + if IsMethodNotSupported(err) { + t.Errorf("expected to not be %s", metav1.StatusReasonMethodNotAllowed) + } + + if !IsConflict(NewConflict(resource("tests"), "2", errors.New("message"))) { + t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) + } + if !IsNotFound(NewNotFound(resource("tests"), "3")) { + t.Errorf("expected to be %s", metav1.StatusReasonNotFound) + } + if !IsInvalid(NewInvalid(kind("Test"), "2", nil)) { + t.Errorf("expected to be %s", metav1.StatusReasonInvalid) + } + if !IsBadRequest(NewBadRequest("reason")) { + t.Errorf("expected to be %s", metav1.StatusReasonBadRequest) + } + if !IsForbidden(NewForbidden(resource("tests"), "2", errors.New("reason"))) { + t.Errorf("expected to be %s", metav1.StatusReasonForbidden) + } + if !IsUnauthorized(NewUnauthorized("reason")) { + t.Errorf("expected to be %s", metav1.StatusReasonUnauthorized) + } + if !IsServerTimeout(NewServerTimeout(resource("tests"), "reason", 0)) { + t.Errorf("expected to be %s", metav1.StatusReasonServerTimeout) + } + if !IsMethodNotSupported(NewMethodNotSupported(resource("foos"), "delete")) { + t.Errorf("expected to be %s", metav1.StatusReasonMethodNotAllowed) + } + + if !IsAlreadyExists(NewGenerateNameConflict(resource("tests"), "3", 1)) { + t.Errorf("expected to be %s", metav1.StatusReasonAlreadyExists) + } + if time, ok := SuggestsClientDelay(NewGenerateNameConflict(resource("tests"), "3", 1)); time != 1 || !ok { + t.Errorf("unexpected %d", time) + } + + if time, ok := SuggestsClientDelay(NewServerTimeout(resource("tests"), "doing something", 10)); time != 10 || !ok { + t.Errorf("unexpected %d", time) + } + if time, ok := SuggestsClientDelay(NewServerTimeout(resource("tests"), "doing something", 0)); time != 0 || !ok { + t.Errorf("unexpected %d", time) + } + if time, ok := SuggestsClientDelay(NewTimeoutError("test reason", 10)); time != 10 || !ok { + t.Errorf("unexpected %d", time) + } + if time, ok := SuggestsClientDelay(NewTooManyRequests("doing something", 10)); time != 10 || !ok { + t.Errorf("unexpected %d", time) + } + if time, ok := SuggestsClientDelay(NewTooManyRequests("doing something", 1)); time != 1 || !ok { + t.Errorf("unexpected %d", time) + } + if time, ok := SuggestsClientDelay(NewGenericServerResponse(429, http.MethodGet, resource("tests"), "test", "doing something", 10, true)); time != 10 || !ok { + t.Errorf("unexpected %d", time) + } + if time, ok := SuggestsClientDelay(NewGenericServerResponse(500, http.MethodGet, resource("tests"), "test", "doing something", 10, true)); time != 10 || !ok { + t.Errorf("unexpected %d", time) + } + if time, ok := SuggestsClientDelay(NewGenericServerResponse(429, http.MethodGet, resource("tests"), "test", "doing something", 0, true)); time != 0 || ok { + t.Errorf("unexpected %d", time) + } +} + +func TestNewInvalid(t *testing.T) { + testCases := []struct { + Err *field.Error + Details *metav1.StatusDetails + Msg string + }{ + { + field.Duplicate(field.NewPath("field[0].name"), "bar"), + &metav1.StatusDetails{ + Kind: "Kind", + Name: "name", + Causes: []metav1.StatusCause{{ + Type: metav1.CauseTypeFieldValueDuplicate, + Field: "field[0].name", + }}, + }, + `Kind "name" is invalid: field[0].name: Duplicate value: "bar"`, + }, + { + field.Invalid(field.NewPath("field[0].name"), "bar", "detail"), + &metav1.StatusDetails{ + Kind: "Kind", + Name: "name", + Causes: []metav1.StatusCause{{ + Type: metav1.CauseTypeFieldValueInvalid, + Field: "field[0].name", + }}, + }, + `Kind "name" is invalid: field[0].name: Invalid value: "bar": detail`, + }, + { + field.NotFound(field.NewPath("field[0].name"), "bar"), + &metav1.StatusDetails{ + Kind: "Kind", + Name: "name", + Causes: []metav1.StatusCause{{ + Type: metav1.CauseTypeFieldValueNotFound, + Field: "field[0].name", + }}, + }, + `Kind "name" is invalid: field[0].name: Not found: "bar"`, + }, + { + field.NotSupported[string](field.NewPath("field[0].name"), "bar", nil), + &metav1.StatusDetails{ + Kind: "Kind", + Name: "name", + Causes: []metav1.StatusCause{{ + Type: metav1.CauseTypeFieldValueNotSupported, + Field: "field[0].name", + }}, + }, + `Kind "name" is invalid: field[0].name: Unsupported value: "bar"`, + }, + { + field.Required(field.NewPath("field[0].name"), ""), + &metav1.StatusDetails{ + Kind: "Kind", + Name: "name", + Causes: []metav1.StatusCause{{ + Type: metav1.CauseTypeFieldValueRequired, + Field: "field[0].name", + }}, + }, + `Kind "name" is invalid: field[0].name: Required value`, + }, + { + nil, + &metav1.StatusDetails{ + Kind: "Kind", + Name: "name", + Causes: []metav1.StatusCause{}, + }, + `Kind "name" is invalid`, + }, + } + for i, testCase := range testCases { + vErr, expected := testCase.Err, testCase.Details + if vErr != nil && expected != nil { + expected.Causes[0].Message = vErr.ErrorBody() + } + var errList field.ErrorList + if vErr != nil { + errList = append(errList, vErr) + } + err := NewInvalid(kind("Kind"), "name", errList) + status := err.ErrStatus + if status.Code != 422 || status.Reason != metav1.StatusReasonInvalid { + t.Errorf("%d: unexpected status: %#v", i, status) + } + if !reflect.DeepEqual(expected, status.Details) { + t.Errorf("%d: expected %#v, got %#v", i, expected, status.Details) + } + if testCase.Msg != status.Message { + t.Errorf("%d: expected\n%s\ngot\n%s", i, testCase.Msg, status.Message) + } + } +} + +func TestReasonForError(t *testing.T) { + if e, a := metav1.StatusReasonUnknown, ReasonForError(nil); e != a { + t.Errorf("unexpected reason type: %#v", a) + } +} + +type TestType struct{} + +func (obj *TestType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (obj *TestType) DeepCopyObject() runtime.Object { + if obj == nil { + return nil + } + clone := *obj + return &clone +} + +func TestFromObject(t *testing.T) { + table := []struct { + obj runtime.Object + message string + }{ + {&metav1.Status{Message: "foobar"}, "foobar"}, + {&TestType{}, "unexpected object: &{}"}, + } + + for _, item := range table { + if e, a := item.message, FromObject(item.obj).Error(); e != a { + t.Errorf("Expected %v, got %v", e, a) + } + } +} + +func TestReasonForErrorSupportsWrappedErrors(t *testing.T) { + testCases := []struct { + name string + err error + expectedReason metav1.StatusReason + }{ + { + name: "Direct match", + err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonUnauthorized}}, + expectedReason: metav1.StatusReasonUnauthorized, + }, + { + name: "No match", + err: errors.New("some other error"), + expectedReason: metav1.StatusReasonUnknown, + }, + { + name: "Nested match", + err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonAlreadyExists}})), + expectedReason: metav1.StatusReasonAlreadyExists, + }, + { + name: "Nested, no match", + err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), + expectedReason: metav1.StatusReasonUnknown, + }, + { + name: "Nil", + expectedReason: metav1.StatusReasonUnknown, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if result := ReasonForError(tc.err); result != tc.expectedReason { + t.Errorf("expected reason: %q, but got known reason: %q", tc.expectedReason, result) + } + }) + } +} + +func TestIsTooManyRequestsSupportsWrappedErrors(t *testing.T) { + testCases := []struct { + name string + err error + expectMatch bool + }{ + { + name: "Direct match via status reason", + err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonTooManyRequests}}, + expectMatch: true, + }, + { + name: "Direct match via status code", + err: &StatusError{ErrStatus: metav1.Status{Code: http.StatusTooManyRequests}}, + expectMatch: true, + }, + { + name: "No match", + err: &StatusError{}, + expectMatch: false, + }, + { + name: "Nested match via status reason", + err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonTooManyRequests}}), + expectMatch: true, + }, + { + name: "Nested match via status code", + err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusTooManyRequests}}), + expectMatch: true, + }, + { + name: "Nested,no match", + err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusNotFound}}), + expectMatch: false, + }, + { + name: "Nil", + expectMatch: false, + }, + } + + for _, tc := range testCases { + if result := IsTooManyRequests(tc.err); result != tc.expectMatch { + t.Errorf("Expect match %t, got match %t", tc.expectMatch, result) + } + } +} +func TestIsRequestEntityTooLargeErrorSupportsWrappedErrors(t *testing.T) { + testCases := []struct { + name string + err error + expectMatch bool + }{ + { + name: "Direct match via status reason", + err: &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonRequestEntityTooLarge}}, + expectMatch: true, + }, + { + name: "Direct match via status code", + err: &StatusError{ErrStatus: metav1.Status{Code: http.StatusRequestEntityTooLarge}}, + expectMatch: true, + }, + { + name: "No match", + err: &StatusError{}, + expectMatch: false, + }, + { + name: "Nested match via status reason", + err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Reason: metav1.StatusReasonRequestEntityTooLarge}}), + expectMatch: true, + }, + { + name: "Nested match via status code", + err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusRequestEntityTooLarge}}), + expectMatch: true, + }, + { + name: "Nested,no match", + err: fmt.Errorf("Wrapping: %w", &StatusError{ErrStatus: metav1.Status{Code: http.StatusNotFound}}), + expectMatch: false, + }, + { + name: "Nil", + expectMatch: false, + }, + } + + for _, tc := range testCases { + if result := IsRequestEntityTooLargeError(tc.err); result != tc.expectMatch { + t.Errorf("Expect match %t, got match %t", tc.expectMatch, result) + } + } +} + +func TestIsUnexpectedServerError(t *testing.T) { + unexpectedServerErr := func() error { + return &StatusError{ + ErrStatus: metav1.Status{ + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{{Type: metav1.CauseTypeUnexpectedServerResponse}}, + }, + }, + } + } + testCases := []struct { + name string + err error + expectMatch bool + }{ + { + name: "Direct match", + err: unexpectedServerErr(), + expectMatch: true, + }, + { + name: "No match", + err: errors.New("some other error"), + expectMatch: false, + }, + { + name: "Nested match", + err: fmt.Errorf("wrapping: %w", unexpectedServerErr()), + expectMatch: true, + }, + { + name: "Nested, no match", + err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), + expectMatch: false, + }, + { + name: "Nil", + expectMatch: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if result := IsUnexpectedServerError(tc.err); result != tc.expectMatch { + t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) + } + }) + } +} + +func TestIsUnexpectedObjectError(t *testing.T) { + unexpectedObjectErr := func() error { + return &UnexpectedObjectError{} + } + testCases := []struct { + name string + err error + expectMatch bool + }{ + { + name: "Direct match", + err: unexpectedObjectErr(), + expectMatch: true, + }, + { + name: "No match", + err: errors.New("some other error"), + expectMatch: false, + }, + { + name: "Nested match", + err: fmt.Errorf("wrapping: %w", unexpectedObjectErr()), + expectMatch: true, + }, + { + name: "Nested, no match", + err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), + expectMatch: false, + }, + { + name: "Nil", + expectMatch: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if result := IsUnexpectedObjectError(tc.err); result != tc.expectMatch { + t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) + } + }) + } +} + +func TestSuggestsClientDelaySupportsWrapping(t *testing.T) { + suggestsClientDelayErr := func() error { + return &StatusError{ + ErrStatus: metav1.Status{ + Reason: metav1.StatusReasonServerTimeout, + Details: &metav1.StatusDetails{}, + }, + } + } + testCases := []struct { + name string + err error + expectMatch bool + }{ + { + name: "Direct match", + err: suggestsClientDelayErr(), + expectMatch: true, + }, + { + name: "No match", + err: errors.New("some other error"), + expectMatch: false, + }, + { + name: "Nested match", + err: fmt.Errorf("wrapping: %w", suggestsClientDelayErr()), + expectMatch: true, + }, + { + name: "Nested, no match", + err: fmt.Errorf("wrapping: %w", fmt.Errorf("some more: %w", errors.New("hello"))), + expectMatch: false, + }, + { + name: "Nil", + expectMatch: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if _, result := SuggestsClientDelay(tc.err); result != tc.expectMatch { + t.Errorf("expected match: %t, but got match: %t", tc.expectMatch, result) + } + }) + } +} + +func TestIsErrorTypesByReasonAndCode(t *testing.T) { + testCases := []struct { + name string + knownReason metav1.StatusReason + otherReason metav1.StatusReason + otherReasonConsidered bool + code int32 + fn func(error) bool + }{ + { + name: "IsRequestEntityTooLarge", + knownReason: metav1.StatusReasonRequestEntityTooLarge, + otherReason: metav1.StatusReasonForbidden, + otherReasonConsidered: false, + code: http.StatusRequestEntityTooLarge, + fn: IsRequestEntityTooLargeError, + }, { + name: "TooManyRequests", + knownReason: metav1.StatusReasonTooManyRequests, + otherReason: metav1.StatusReasonForbidden, + otherReasonConsidered: false, + code: http.StatusTooManyRequests, + fn: IsTooManyRequests, + }, { + name: "Forbidden", + knownReason: metav1.StatusReasonForbidden, + otherReason: metav1.StatusReasonNotFound, + otherReasonConsidered: true, + code: http.StatusForbidden, + fn: IsForbidden, + }, { + name: "NotFound", + knownReason: metav1.StatusReasonNotFound, + otherReason: metav1.StatusReasonForbidden, + otherReasonConsidered: true, + code: http.StatusNotFound, + fn: IsNotFound, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Run("by known reason", func(t *testing.T) { + err := &StatusError{ + metav1.Status{ + Reason: tc.knownReason, + }, + } + + got := tc.fn(err) + if !got { + t.Errorf("expected reason %s to match", tc.knownReason) + } + }) + + t.Run("by code and unknown reason", func(t *testing.T) { + err := &StatusError{ + metav1.Status{ + Reason: metav1.StatusReasonUnknown, // this could be _any_ reason that isn't in knownReasons. + Code: tc.code, + }, + } + + got := tc.fn(err) + if !got { + t.Errorf("expected code %d with reason %s to match", tc.code, tc.otherReason) + } + }) + + if !tc.otherReasonConsidered { + return + } + + t.Run("by code and other known reason", func(t *testing.T) { + err := &StatusError{ + metav1.Status{ + Reason: tc.otherReason, + Code: tc.code, + }, + } + + got := tc.fn(err) + if got { + t.Errorf("expected code %d with reason %s to not match", tc.code, tc.otherReason) + } + }) + + }) + + } +} + +func TestStatusCauseSupportsWrappedErrors(t *testing.T) { + err := &StatusError{ErrStatus: metav1.Status{ + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{{Type: "SomeCause"}}, + }, + }} + + if cause, ok := StatusCause(nil, "SomeCause"); ok { + t.Errorf("expected no cause for nil, got %v: %#v", ok, cause) + } + if cause, ok := StatusCause(errors.New("boom"), "SomeCause"); ok { + t.Errorf("expected no cause for wrong type, got %v: %#v", ok, cause) + } + + if cause, ok := StatusCause(err, "Other"); ok { + t.Errorf("expected no cause for wrong name, got %v: %#v", ok, cause) + } + if cause, ok := StatusCause(err, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { + t.Errorf("expected cause, got %v: %#v", ok, cause) + } + + wrapped := fmt.Errorf("once: %w", err) + if cause, ok := StatusCause(wrapped, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { + t.Errorf("expected cause when wrapped, got %v: %#v", ok, cause) + } + + nested := fmt.Errorf("twice: %w", wrapped) + if cause, ok := StatusCause(nested, "SomeCause"); !ok || cause != err.ErrStatus.Details.Causes[0] { + t.Errorf("expected cause when nested, got %v: %#v", ok, cause) + } +} + +func BenchmarkIsAlreadyExistsWrappedErrors(b *testing.B) { + err := NewAlreadyExists(schema.GroupResource{}, "") + wrapped := fmt.Errorf("once: %w", err) + + b.Run("Nil", func(b *testing.B) { + for i := 0; i < b.N; i++ { + IsAlreadyExists(nil) + } + }) + + b.Run("Bare", func(b *testing.B) { + for i := 0; i < b.N; i++ { + IsAlreadyExists(err) + } + }) + + b.Run("Wrapped", func(b *testing.B) { + for i := 0; i < b.N; i++ { + IsAlreadyExists(wrapped) + } + }) +} + +func BenchmarkIsNotFoundWrappedErrors(b *testing.B) { + err := NewNotFound(schema.GroupResource{}, "") + wrapped := fmt.Errorf("once: %w", err) + + b.Run("Nil", func(b *testing.B) { + for i := 0; i < b.N; i++ { + IsNotFound(nil) + } + }) + + b.Run("Bare", func(b *testing.B) { + for i := 0; i < b.N; i++ { + IsNotFound(err) + } + }) + + b.Run("Wrapped", func(b *testing.B) { + for i := 0; i < b.N; i++ { + IsNotFound(wrapped) + } + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/OWNERS new file mode 100644 index 0000000000..3bd8bf535e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/OWNERS @@ -0,0 +1,15 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +reviewers: + - thockin + - smarterclayton + - wojtek-t + - deads2k + - derekwaynecarr + - caesarxuchao + - mikedanese + - liggitt + - janetkuo + - dims +emeritus_reviewers: + - ncdc diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/conditions.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/conditions.go new file mode 100644 index 0000000000..cbdf2eeb83 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/conditions.go @@ -0,0 +1,119 @@ +/* +Copyright 2020 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 meta + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SetStatusCondition sets the corresponding condition in conditions to newCondition and returns true +// if the conditions are changed by this call. +// conditions must be non-nil. +// 1. if the condition of the specified type already exists (all fields of the existing condition are updated to +// newCondition, LastTransitionTime is set to now if the new status differs from the old status) +// 2. if a condition of the specified type does not exist (LastTransitionTime is set to now() if unset, and newCondition is appended) +func SetStatusCondition(conditions *[]metav1.Condition, newCondition metav1.Condition) (changed bool) { + if conditions == nil { + return false + } + existingCondition := FindStatusCondition(*conditions, newCondition.Type) + if existingCondition == nil { + if newCondition.LastTransitionTime.IsZero() { + newCondition.LastTransitionTime = metav1.NewTime(time.Now()) + } + *conditions = append(*conditions, newCondition) + return true + } + + if existingCondition.Status != newCondition.Status { + existingCondition.Status = newCondition.Status + if !newCondition.LastTransitionTime.IsZero() { + existingCondition.LastTransitionTime = newCondition.LastTransitionTime + } else { + existingCondition.LastTransitionTime = metav1.NewTime(time.Now()) + } + changed = true + } + + if existingCondition.Reason != newCondition.Reason { + existingCondition.Reason = newCondition.Reason + changed = true + } + if existingCondition.Message != newCondition.Message { + existingCondition.Message = newCondition.Message + changed = true + } + if existingCondition.ObservedGeneration != newCondition.ObservedGeneration { + existingCondition.ObservedGeneration = newCondition.ObservedGeneration + changed = true + } + + return changed +} + +// RemoveStatusCondition removes the corresponding conditionType from conditions if present. Returns +// true if it was present and got removed. +// conditions must be non-nil. +func RemoveStatusCondition(conditions *[]metav1.Condition, conditionType string) (removed bool) { + if conditions == nil || len(*conditions) == 0 { + return false + } + newConditions := make([]metav1.Condition, 0, len(*conditions)-1) + for _, condition := range *conditions { + if condition.Type != conditionType { + newConditions = append(newConditions, condition) + } + } + + removed = len(*conditions) != len(newConditions) + *conditions = newConditions + + return removed +} + +// FindStatusCondition finds the conditionType in conditions. +func FindStatusCondition(conditions []metav1.Condition, conditionType string) *metav1.Condition { + for i := range conditions { + if conditions[i].Type == conditionType { + return &conditions[i] + } + } + + return nil +} + +// IsStatusConditionTrue returns true when the conditionType is present and set to `metav1.ConditionTrue` +func IsStatusConditionTrue(conditions []metav1.Condition, conditionType string) bool { + return IsStatusConditionPresentAndEqual(conditions, conditionType, metav1.ConditionTrue) +} + +// IsStatusConditionFalse returns true when the conditionType is present and set to `metav1.ConditionFalse` +func IsStatusConditionFalse(conditions []metav1.Condition, conditionType string) bool { + return IsStatusConditionPresentAndEqual(conditions, conditionType, metav1.ConditionFalse) +} + +// IsStatusConditionPresentAndEqual returns true when conditionType is present and equal to status. +func IsStatusConditionPresentAndEqual(conditions []metav1.Condition, conditionType string, status metav1.ConditionStatus) bool { + for _, condition := range conditions { + if condition.Type == conditionType { + return condition.Status == status + } + } + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/conditions_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/conditions_test.go new file mode 100644 index 0000000000..248c885800 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/conditions_test.go @@ -0,0 +1,250 @@ +/* +Copyright 2020 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 meta + +import ( + "reflect" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestSetStatusCondition(t *testing.T) { + oneHourBefore := time.Now().Add(-1 * time.Hour) + oneHourAfter := time.Now().Add(1 * time.Hour) + + tests := []struct { + name string + conditions []metav1.Condition + toAdd metav1.Condition + expectChanged bool + expected []metav1.Condition + }{ + { + name: "should-add", + conditions: []metav1.Condition{ + {Type: "first"}, + {Type: "third"}, + }, + toAdd: metav1.Condition{Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message"}, + expectChanged: true, + expected: []metav1.Condition{ + {Type: "first"}, + {Type: "third"}, + {Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message"}, + }, + }, + { + name: "use-supplied-time", + conditions: []metav1.Condition{ + {Type: "first"}, + {Type: "second", Status: metav1.ConditionFalse}, + {Type: "third"}, + }, + toAdd: metav1.Condition{Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message"}, + expectChanged: true, + expected: []metav1.Condition{ + {Type: "first"}, + {Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message"}, + {Type: "third"}, + }, + }, + { + name: "update-fields", + conditions: []metav1.Condition{ + {Type: "first"}, + {Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}}, + {Type: "third"}, + }, + toAdd: metav1.Condition{Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourAfter}, Reason: "reason", Message: "message", ObservedGeneration: 3}, + expectChanged: true, + expected: []metav1.Condition{ + {Type: "first"}, + {Type: "second", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}, Reason: "reason", Message: "message", ObservedGeneration: 3}, + {Type: "third"}, + }, + }, + { + name: "nothing changes", + conditions: []metav1.Condition{{ + Type: "type", + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: oneHourBefore}, + }}, + toAdd: metav1.Condition{Type: "type", Status: metav1.ConditionTrue, LastTransitionTime: metav1.Time{Time: oneHourBefore}}, + expected: []metav1.Condition{{ + Type: "type", + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Time{Time: oneHourBefore}, + }}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + changed := SetStatusCondition(&test.conditions, test.toAdd) + if test.expectChanged != changed { + t.Errorf("expectChanged=%t != changed=%t", test.expectChanged, changed) + } + if !reflect.DeepEqual(test.conditions, test.expected) { + t.Error(test.conditions) + } + }) + } +} + +func TestRemoveStatusCondition(t *testing.T) { + tests := []struct { + name string + conditions []metav1.Condition + conditionType string + expectRemoval bool + expected []metav1.Condition + }{ + { + name: "present", + conditions: []metav1.Condition{ + {Type: "first"}, + {Type: "second"}, + {Type: "third"}, + }, + conditionType: "second", + expectRemoval: true, + expected: []metav1.Condition{ + {Type: "first"}, + {Type: "third"}, + }, + }, + { + name: "not-present", + conditions: []metav1.Condition{ + {Type: "first"}, + {Type: "second"}, + {Type: "third"}, + }, + conditionType: "fourth", + expected: []metav1.Condition{ + {Type: "first"}, + {Type: "second"}, + {Type: "third"}, + }, + }, + { + name: "empty_conditions", + conditions: []metav1.Condition{}, + conditionType: "Foo", + expected: []metav1.Condition{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + removed := RemoveStatusCondition(&test.conditions, test.conditionType) + if test.expectRemoval != removed { + t.Errorf("expectRemoval=%t != removal=%t", test.expectRemoval, removed) + } + if !reflect.DeepEqual(test.conditions, test.expected) { + t.Error(test.conditions) + } + }) + } +} + +func TestFindStatusCondition(t *testing.T) { + tests := []struct { + name string + conditions []metav1.Condition + conditionType string + expected *metav1.Condition + }{ + { + name: "not-present", + conditions: []metav1.Condition{ + {Type: "first"}, + }, + conditionType: "second", + expected: nil, + }, + { + name: "present", + conditions: []metav1.Condition{ + {Type: "first"}, + {Type: "second"}, + }, + conditionType: "second", + expected: &metav1.Condition{Type: "second"}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := FindStatusCondition(test.conditions, test.conditionType) + if !reflect.DeepEqual(actual, test.expected) { + t.Error(actual) + } + }) + } +} + +func TestIsStatusConditionPresentAndEqual(t *testing.T) { + tests := []struct { + name string + conditions []metav1.Condition + conditionType string + conditionStatus metav1.ConditionStatus + expected bool + }{ + { + name: "doesnt-match-true", + conditions: []metav1.Condition{ + {Type: "first", Status: metav1.ConditionUnknown}, + }, + conditionType: "first", + conditionStatus: metav1.ConditionTrue, + expected: false, + }, + { + name: "does-match-true", + conditions: []metav1.Condition{ + {Type: "first", Status: metav1.ConditionTrue}, + }, + conditionType: "first", + conditionStatus: metav1.ConditionTrue, + expected: true, + }, + { + name: "does-match-false", + conditions: []metav1.Condition{ + {Type: "first", Status: metav1.ConditionFalse}, + }, + conditionType: "first", + conditionStatus: metav1.ConditionFalse, + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := IsStatusConditionPresentAndEqual(test.conditions, test.conditionType, test.conditionStatus) + if actual != test.expected { + t.Error(actual) + } + + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/doc.go new file mode 100644 index 0000000000..a3b18a5c9a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/doc.go @@ -0,0 +1,19 @@ +/* +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 meta provides functions for retrieving API metadata from objects +// belonging to the Kubernetes API +package meta diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/errors.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/errors.go new file mode 100644 index 0000000000..f36aa4ec22 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/errors.go @@ -0,0 +1,132 @@ +/* +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 meta + +import ( + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" +) + +// AmbiguousResourceError is returned if the RESTMapper finds multiple matches for a resource +type AmbiguousResourceError struct { + PartialResource schema.GroupVersionResource + + MatchingResources []schema.GroupVersionResource + MatchingKinds []schema.GroupVersionKind +} + +func (e *AmbiguousResourceError) Error() string { + switch { + case len(e.MatchingKinds) > 0 && len(e.MatchingResources) > 0: + return fmt.Sprintf("%v matches multiple resources %v and kinds %v", e.PartialResource, e.MatchingResources, e.MatchingKinds) + case len(e.MatchingKinds) > 0: + return fmt.Sprintf("%v matches multiple kinds %v", e.PartialResource, e.MatchingKinds) + case len(e.MatchingResources) > 0: + return fmt.Sprintf("%v matches multiple resources %v", e.PartialResource, e.MatchingResources) + } + return fmt.Sprintf("%v matches multiple resources or kinds", e.PartialResource) +} + +func (*AmbiguousResourceError) Is(target error) bool { + _, ok := target.(*AmbiguousResourceError) + return ok +} + +// AmbiguousKindError is returned if the RESTMapper finds multiple matches for a kind +type AmbiguousKindError struct { + PartialKind schema.GroupVersionKind + + MatchingResources []schema.GroupVersionResource + MatchingKinds []schema.GroupVersionKind +} + +func (e *AmbiguousKindError) Error() string { + switch { + case len(e.MatchingKinds) > 0 && len(e.MatchingResources) > 0: + return fmt.Sprintf("%v matches multiple resources %v and kinds %v", e.PartialKind, e.MatchingResources, e.MatchingKinds) + case len(e.MatchingKinds) > 0: + return fmt.Sprintf("%v matches multiple kinds %v", e.PartialKind, e.MatchingKinds) + case len(e.MatchingResources) > 0: + return fmt.Sprintf("%v matches multiple resources %v", e.PartialKind, e.MatchingResources) + } + return fmt.Sprintf("%v matches multiple resources or kinds", e.PartialKind) +} + +func (*AmbiguousKindError) Is(target error) bool { + _, ok := target.(*AmbiguousKindError) + return ok +} + +func IsAmbiguousError(err error) bool { + if err == nil { + return false + } + return errors.Is(err, &AmbiguousResourceError{}) || errors.Is(err, &AmbiguousKindError{}) +} + +// NoResourceMatchError is returned if the RESTMapper can't find any match for a resource +type NoResourceMatchError struct { + PartialResource schema.GroupVersionResource +} + +func (e *NoResourceMatchError) Error() string { + return fmt.Sprintf("no matches for %v", e.PartialResource) +} + +func (*NoResourceMatchError) Is(target error) bool { + _, ok := target.(*NoResourceMatchError) + return ok +} + +// NoKindMatchError is returned if the RESTMapper can't find any match for a kind +type NoKindMatchError struct { + // GroupKind is the API group and kind that was searched + GroupKind schema.GroupKind + // SearchedVersions is the optional list of versions the search was restricted to + SearchedVersions []string +} + +func (e *NoKindMatchError) Error() string { + searchedVersions := sets.NewString() + for _, v := range e.SearchedVersions { + searchedVersions.Insert(schema.GroupVersion{Group: e.GroupKind.Group, Version: v}.String()) + } + + switch len(searchedVersions) { + case 0: + return fmt.Sprintf("no matches for kind %q in group %q", e.GroupKind.Kind, e.GroupKind.Group) + case 1: + return fmt.Sprintf("no matches for kind %q in version %q", e.GroupKind.Kind, searchedVersions.List()[0]) + default: + return fmt.Sprintf("no matches for kind %q in versions %q", e.GroupKind.Kind, searchedVersions.List()) + } +} + +func (*NoKindMatchError) Is(target error) bool { + _, ok := target.(*NoKindMatchError) + return ok +} + +func IsNoMatchError(err error) bool { + if err == nil { + return false + } + return errors.Is(err, &NoResourceMatchError{}) || errors.Is(err, &NoKindMatchError{}) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/errors_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/errors_test.go new file mode 100644 index 0000000000..56e5d030b4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/errors_test.go @@ -0,0 +1,79 @@ +/* +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 meta + +import ( + "errors" + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestErrorMatching(t *testing.T) { + testCases := []struct { + name string + // input should contain an error that is _not_ empty, otherwise the naive reflectlite.DeepEqual matching of + // the errors lib will always succeed, but for all of these we want to verify that the matching is based on + // type. + input error + new func() error + matcherFunc func(error) bool + }{ + { + name: "AmbiguousResourceError", + input: &AmbiguousResourceError{MatchingResources: []schema.GroupVersionResource{{}}}, + new: func() error { return &AmbiguousResourceError{} }, + matcherFunc: IsAmbiguousError, + }, + { + name: "AmbiguousKindError", + input: &AmbiguousKindError{MatchingResources: []schema.GroupVersionResource{{}}}, + new: func() error { return &AmbiguousKindError{} }, + matcherFunc: IsAmbiguousError, + }, + { + name: "NoResourceMatchError", + input: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: "foo"}}, + new: func() error { return &NoResourceMatchError{} }, + matcherFunc: IsNoMatchError, + }, + { + name: "NoKindMatchError", + input: &NoKindMatchError{SearchedVersions: []string{"foo"}}, + new: func() error { return &NoKindMatchError{} }, + matcherFunc: IsNoMatchError, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if !errors.Is(tc.input, tc.new()) { + t.Error("error doesn't match itself directly") + } + if !errors.Is(fmt.Errorf("wrapepd: %w", tc.input), tc.new()) { + t.Error("error doesn't match itself when wrapped") + } + if !tc.matcherFunc(tc.input) { + t.Errorf("error doesn't get matched by matcherfunc") + } + if errors.Is(tc.input, errors.New("foo")) { + t.Error("error incorrectly matches other error") + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/firsthit_restmapper.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/firsthit_restmapper.go new file mode 100644 index 0000000000..1bc816fe3f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/firsthit_restmapper.go @@ -0,0 +1,105 @@ +/* +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 meta + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" + utilerrors "k8s.io/apimachinery/pkg/util/errors" +) + +var ( + _ ResettableRESTMapper = &FirstHitRESTMapper{} +) + +// FirstHitRESTMapper is a wrapper for multiple RESTMappers which returns the +// first successful result for the singular requests +type FirstHitRESTMapper struct { + MultiRESTMapper +} + +func (m FirstHitRESTMapper) String() string { + return fmt.Sprintf("FirstHitRESTMapper{\n\t%v\n}", m.MultiRESTMapper) +} + +func (m FirstHitRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { + errors := []error{} + for _, t := range m.MultiRESTMapper { + ret, err := t.ResourceFor(resource) + if err == nil { + return ret, nil + } + errors = append(errors, err) + } + + return schema.GroupVersionResource{}, collapseAggregateErrors(errors) +} + +func (m FirstHitRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + errors := []error{} + for _, t := range m.MultiRESTMapper { + ret, err := t.KindFor(resource) + if err == nil { + return ret, nil + } + errors = append(errors, err) + } + + return schema.GroupVersionKind{}, collapseAggregateErrors(errors) +} + +// RESTMapping provides the REST mapping for the resource based on the +// kind and version. This implementation supports multiple REST schemas and +// return the first match. +func (m FirstHitRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { + errors := []error{} + for _, t := range m.MultiRESTMapper { + ret, err := t.RESTMapping(gk, versions...) + if err == nil { + return ret, nil + } + errors = append(errors, err) + } + + return nil, collapseAggregateErrors(errors) +} + +func (m FirstHitRESTMapper) Reset() { + m.MultiRESTMapper.Reset() +} + +// collapseAggregateErrors returns the minimal errors. it handles empty as nil, handles one item in a list +// by returning the item, and collapses all NoMatchErrors to a single one (since they should all be the same) +func collapseAggregateErrors(errors []error) error { + if len(errors) == 0 { + return nil + } + if len(errors) == 1 { + return errors[0] + } + + allNoMatchErrors := true + for _, err := range errors { + allNoMatchErrors = allNoMatchErrors && IsNoMatchError(err) + } + if allNoMatchErrors { + return errors[0] + } + + return utilerrors.NewAggregate(errors) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/help.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/help.go new file mode 100644 index 0000000000..468afd0e9e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/help.go @@ -0,0 +1,334 @@ +/* +Copyright 2015 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 meta + +import ( + "errors" + "fmt" + "reflect" + "sync" + + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + // isListCache maintains a cache of types that are checked for lists + // which is used by IsListType. + // TODO: remove and replace with an interface check + isListCache = struct { + lock sync.RWMutex + byType map[reflect.Type]bool + }{ + byType: make(map[reflect.Type]bool, 1024), + } +) + +// IsListType returns true if the provided Object has a slice called Items. +// TODO: Replace the code in this check with an interface comparison by +// creating and enforcing that lists implement a list accessor. +func IsListType(obj runtime.Object) bool { + switch t := obj.(type) { + case runtime.Unstructured: + return t.IsList() + } + t := reflect.TypeOf(obj) + + isListCache.lock.RLock() + ok, exists := isListCache.byType[t] + isListCache.lock.RUnlock() + + if !exists { + _, err := getItemsPtr(obj) + ok = err == nil + + // cache only the first 1024 types + isListCache.lock.Lock() + if len(isListCache.byType) < 1024 { + isListCache.byType[t] = ok + } + isListCache.lock.Unlock() + } + + return ok +} + +var ( + errExpectFieldItems = errors.New("no Items field in this object") + errExpectSliceItems = errors.New("Items field must be a slice of objects") +) + +// GetItemsPtr returns a pointer to the list object's Items member. +// If 'list' doesn't have an Items member, it's not really a list type +// and an error will be returned. +// This function will either return a pointer to a slice, or an error, but not both. +// TODO: this will be replaced with an interface in the future +func GetItemsPtr(list runtime.Object) (interface{}, error) { + obj, err := getItemsPtr(list) + if err != nil { + return nil, fmt.Errorf("%T is not a list: %v", list, err) + } + return obj, nil +} + +// getItemsPtr returns a pointer to the list object's Items member or an error. +func getItemsPtr(list runtime.Object) (interface{}, error) { + v, err := conversion.EnforcePtr(list) + if err != nil { + return nil, err + } + + items := v.FieldByName("Items") + if !items.IsValid() { + return nil, errExpectFieldItems + } + switch items.Kind() { + case reflect.Interface, reflect.Pointer: + target := reflect.TypeOf(items.Interface()).Elem() + if target.Kind() != reflect.Slice { + return nil, errExpectSliceItems + } + return items.Interface(), nil + case reflect.Slice: + return items.Addr().Interface(), nil + default: + return nil, errExpectSliceItems + } +} + +// EachListItem invokes fn on each runtime.Object in the list. Any error immediately terminates +// the loop. +// +// If items passed to fn are retained for different durations, and you want to avoid +// retaining all items in obj as long as any item is referenced, use EachListItemWithAlloc instead. +func EachListItem(obj runtime.Object, fn func(runtime.Object) error) error { + return eachListItem(obj, fn, false) +} + +// EachListItemWithAlloc works like EachListItem, but avoids retaining references to the items slice in obj. +// It does this by making a shallow copy of non-pointer items in obj. +// +// If the items passed to fn are not retained, or are retained for the same duration, use EachListItem instead for memory efficiency. +func EachListItemWithAlloc(obj runtime.Object, fn func(runtime.Object) error) error { + return eachListItem(obj, fn, true) +} + +// allocNew: Whether shallow copy is required when the elements in Object.Items are struct +func eachListItem(obj runtime.Object, fn func(runtime.Object) error, allocNew bool) error { + if unstructured, ok := obj.(runtime.Unstructured); ok { + if allocNew { + return unstructured.EachListItemWithAlloc(fn) + } + return unstructured.EachListItem(fn) + } + // TODO: Change to an interface call? + itemsPtr, err := GetItemsPtr(obj) + if err != nil { + return err + } + items, err := conversion.EnforcePtr(itemsPtr) + if err != nil { + return err + } + len := items.Len() + if len == 0 { + return nil + } + takeAddr := false + if elemType := items.Type().Elem(); elemType.Kind() != reflect.Pointer && elemType.Kind() != reflect.Interface { + if !items.Index(0).CanAddr() { + return fmt.Errorf("unable to take address of items in %T for EachListItem", obj) + } + takeAddr = true + } + + for i := 0; i < len; i++ { + raw := items.Index(i) + if takeAddr { + if allocNew { + // shallow copy to avoid retaining a reference to the original list item + itemCopy := reflect.New(raw.Type()) + // assign to itemCopy and type-assert + itemCopy.Elem().Set(raw) + // reflect.New will guarantee that itemCopy must be a pointer. + raw = itemCopy + } else { + raw = raw.Addr() + } + } + // raw must be a pointer or an interface + // allocate a pointer is cheap + switch item := raw.Interface().(type) { + case *runtime.RawExtension: + if err := fn(item.Object); err != nil { + return err + } + case runtime.Object: + if err := fn(item); err != nil { + return err + } + default: + obj, ok := item.(runtime.Object) + if !ok { + return fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind()) + } + if err := fn(obj); err != nil { + return err + } + } + } + return nil +} + +// ExtractList returns obj's Items element as an array of runtime.Objects. +// Returns an error if obj is not a List type (does not have an Items member). +// +// If items in the returned list are retained for different durations, and you want to avoid +// retaining all items in obj as long as any item is referenced, use ExtractListWithAlloc instead. +func ExtractList(obj runtime.Object) ([]runtime.Object, error) { + return extractList(obj, false) +} + +// ExtractListWithAlloc works like ExtractList, but avoids retaining references to the items slice in obj. +// It does this by making a shallow copy of non-pointer items in obj. +// +// If the items in the returned list are not retained, or are retained for the same duration, use ExtractList instead for memory efficiency. +func ExtractListWithAlloc(obj runtime.Object) ([]runtime.Object, error) { + return extractList(obj, true) +} + +// allocNew: Whether shallow copy is required when the elements in Object.Items are struct +func extractList(obj runtime.Object, allocNew bool) ([]runtime.Object, error) { + itemsPtr, err := GetItemsPtr(obj) + if err != nil { + return nil, err + } + items, err := conversion.EnforcePtr(itemsPtr) + if err != nil { + return nil, err + } + if items.IsNil() { + return nil, nil + } + list := make([]runtime.Object, items.Len()) + if len(list) == 0 { + return list, nil + } + elemType := items.Type().Elem() + isRawExtension := elemType == rawExtensionObjectType + implementsObject := elemType.Implements(objectType) + for i := range list { + raw := items.Index(i) + switch { + case isRawExtension: + item := raw.Interface().(runtime.RawExtension) + switch { + case item.Object != nil: + list[i] = item.Object + case item.Raw != nil: + // TODO: Set ContentEncoding and ContentType correctly. + list[i] = &runtime.Unknown{Raw: item.Raw} + default: + list[i] = nil + } + case implementsObject: + list[i] = raw.Interface().(runtime.Object) + case allocNew: + // shallow copy to avoid retaining a reference to the original list item + itemCopy := reflect.New(raw.Type()) + // assign to itemCopy and type-assert + itemCopy.Elem().Set(raw) + var ok bool + // reflect.New will guarantee that itemCopy must be a pointer. + if list[i], ok = itemCopy.Interface().(runtime.Object); !ok { + return nil, fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind()) + } + default: + var found bool + if list[i], found = raw.Addr().Interface().(runtime.Object); !found { + return nil, fmt.Errorf("%v: item[%v]: Expected object, got %#v(%s)", obj, i, raw.Interface(), raw.Kind()) + } + } + } + return list, nil +} + +var ( + // objectSliceType is the type of a slice of Objects + objectSliceType = reflect.TypeOf([]runtime.Object{}) + objectType = reflect.TypeOf((*runtime.Object)(nil)).Elem() + rawExtensionObjectType = reflect.TypeOf(runtime.RawExtension{}) +) + +// LenList returns the length of this list or 0 if it is not a list. +func LenList(list runtime.Object) int { + itemsPtr, err := GetItemsPtr(list) + if err != nil { + return 0 + } + items, err := conversion.EnforcePtr(itemsPtr) + if err != nil { + return 0 + } + return items.Len() +} + +// SetList sets the given list object's Items member have the elements given in +// objects. +// Returns an error if list is not a List type (does not have an Items member), +// or if any of the objects are not of the right type. +func SetList(list runtime.Object, objects []runtime.Object) error { + itemsPtr, err := GetItemsPtr(list) + if err != nil { + return err + } + items, err := conversion.EnforcePtr(itemsPtr) + if err != nil { + return err + } + if items.Type() == objectSliceType { + items.Set(reflect.ValueOf(objects)) + return nil + } + slice := reflect.MakeSlice(items.Type(), len(objects), len(objects)) + for i := range objects { + dest := slice.Index(i) + if dest.Type() == rawExtensionObjectType { + dest = dest.FieldByName("Object") + } + + // check to see if you're directly assignable + if reflect.TypeOf(objects[i]).AssignableTo(dest.Type()) { + dest.Set(reflect.ValueOf(objects[i])) + continue + } + + src, err := conversion.EnforcePtr(objects[i]) + if err != nil { + return err + } + if src.Type().AssignableTo(dest.Type()) { + dest.Set(src) + } else if src.Type().ConvertibleTo(dest.Type()) { + dest.Set(src.Convert(dest.Type())) + } else { + return fmt.Errorf("item[%d]: can't assign or convert %v into %v", i, src.Type(), dest.Type()) + } + } + items.Set(slice) + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/help_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/help_test.go new file mode 100644 index 0000000000..e3047f50f1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/help_test.go @@ -0,0 +1,585 @@ +/* +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 meta + +import ( + "reflect" + "strconv" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + fakeObjectItemsNum = 1000 + exemptObjectIndex = fakeObjectItemsNum / 4 +) + +type SampleSpec struct { + Flied int +} + +type FooSpec struct { + Flied int +} + +type FooList struct { + metav1.TypeMeta + metav1.ListMeta + Items []Foo +} + +func (s *FooList) DeepCopyObject() runtime.Object { panic("unimplemented") } + +type SampleList struct { + metav1.TypeMeta + metav1.ListMeta + Items []Sample +} + +func (s *SampleList) DeepCopyObject() runtime.Object { panic("unimplemented") } + +type RawExtensionList struct { + metav1.TypeMeta + metav1.ListMeta + + Items []runtime.RawExtension +} + +func (l RawExtensionList) DeepCopyObject() runtime.Object { panic("unimplemented") } + +// NOTE: Foo struct itself is the implementer of runtime.Object. +type Foo struct { + metav1.TypeMeta + metav1.ObjectMeta + Spec FooSpec +} + +func (f Foo) GetObjectKind() schema.ObjectKind { + tm := f.TypeMeta + return &tm +} + +func (f Foo) DeepCopyObject() runtime.Object { panic("unimplemented") } + +// NOTE: the pointer of Sample that is the implementer of runtime.Object. +// the behavior is similar to our corev1.Pod. corev1.Node +type Sample struct { + metav1.TypeMeta + metav1.ObjectMeta + Spec SampleSpec +} + +func (s *Sample) GetObjectKind() schema.ObjectKind { + tm := s.TypeMeta + return &tm +} + +func (s *Sample) DeepCopyObject() runtime.Object { panic("unimplemented") } + +func fakeSampleList(numItems int) *SampleList { + out := &SampleList{ + Items: make([]Sample, numItems), + } + + for i := range out.Items { + out.Items[i] = Sample{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "sample.org/v1", + Kind: "Sample", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: strconv.Itoa(i), + Namespace: "default", + Labels: map[string]string{ + "label-key-1": "label-value-1", + }, + Annotations: map[string]string{ + "annotations-key-1": "annotations-value-1", + }, + }, + Spec: SampleSpec{ + Flied: i, + }, + } + } + return out +} + +func fakeExtensionList(numItems int) *RawExtensionList { + out := &RawExtensionList{ + Items: make([]runtime.RawExtension, numItems), + } + + for i := range out.Items { + out.Items[i] = runtime.RawExtension{ + Object: &Foo{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "sample.org/v2", + Kind: "Sample", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: strconv.Itoa(i), + Namespace: "default", + Labels: map[string]string{ + "label-key-1": "label-value-1", + }, + Annotations: map[string]string{ + "annotations-key-1": "annotations-value-1", + }, + }, + Spec: FooSpec{ + Flied: i, + }, + }, + } + } + return out +} + +func fakeUnstructuredList(numItems int) runtime.Unstructured { + out := &unstructured.UnstructuredList{ + Items: make([]unstructured.Unstructured, numItems), + } + + for i := range out.Items { + out.Items[i] = unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": map[string]interface{}{ + "creationTimestamp": nil, + "name": strconv.Itoa(i), + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + } + } + return out +} + +func fakeFooList(numItems int) *FooList { + out := &FooList{ + Items: make([]Foo, numItems), + } + + for i := range out.Items { + out.Items[i] = Foo{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "foo.org/v1", + Kind: "Foo", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: strconv.Itoa(i), + Namespace: "default", + Labels: map[string]string{ + "label-key-1": "label-value-1", + }, + Annotations: map[string]string{ + "annotations-key-1": "annotations-value-1", + }, + }, + Spec: FooSpec{ + Flied: i, + }, + } + } + return out +} + +func TestEachList(t *testing.T) { + tests := []struct { + name string + generateFunc func(num int) (list runtime.Object) + expectObjectNum int + }{ + { + name: "StructReceiverList", + generateFunc: func(num int) (list runtime.Object) { + return fakeFooList(num) + }, + expectObjectNum: fakeObjectItemsNum, + }, + { + name: "PointerReceiverList", + generateFunc: func(num int) (list runtime.Object) { + return fakeSampleList(num) + }, + expectObjectNum: fakeObjectItemsNum, + }, + { + name: "RawExtensionList", + generateFunc: func(num int) (list runtime.Object) { + return fakeExtensionList(num) + }, + expectObjectNum: fakeObjectItemsNum, + }, + { + name: "UnstructuredList", + generateFunc: func(num int) (list runtime.Object) { + return fakeUnstructuredList(fakeObjectItemsNum) + }, + expectObjectNum: fakeObjectItemsNum, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Run("EachListItem", func(t *testing.T) { + expectObjectNames := map[string]struct{}{} + for i := 0; i < tc.expectObjectNum; i++ { + expectObjectNames[strconv.Itoa(i)] = struct{}{} + } + list := tc.generateFunc(tc.expectObjectNum) + err := EachListItem(list, func(object runtime.Object) error { + o, err := Accessor(object) + if err != nil { + return err + } + delete(expectObjectNames, o.GetName()) + return nil + }) + if err != nil { + t.Errorf("each list item %#v: %v", list, err) + } + if len(expectObjectNames) != 0 { + t.Fatal("expectObjectNames should be empty") + } + }) + t.Run("EachListItemWithAlloc", func(t *testing.T) { + expectObjectNames := map[string]struct{}{} + for i := 0; i < tc.expectObjectNum; i++ { + expectObjectNames[strconv.Itoa(i)] = struct{}{} + } + list := tc.generateFunc(tc.expectObjectNum) + err := EachListItemWithAlloc(list, func(object runtime.Object) error { + o, err := Accessor(object) + if err != nil { + return err + } + delete(expectObjectNames, o.GetName()) + return nil + }) + if err != nil { + t.Errorf("each list %#v with alloc: %v", list, err) + } + if len(expectObjectNames) != 0 { + t.Fatal("expectObjectNames should be empty") + } + }) + }) + } +} + +func TestExtractList(t *testing.T) { + tests := []struct { + name string + generateFunc func(num int) (list runtime.Object) + expectObjectNum int + }{ + { + name: "StructReceiverList", + generateFunc: func(num int) (list runtime.Object) { + return fakeFooList(num) + }, + expectObjectNum: fakeObjectItemsNum, + }, + { + name: "PointerReceiverList", + generateFunc: func(num int) (list runtime.Object) { + return fakeSampleList(num) + }, + expectObjectNum: fakeObjectItemsNum, + }, + { + name: "RawExtensionList", + generateFunc: func(num int) (list runtime.Object) { + return fakeExtensionList(num) + }, + expectObjectNum: fakeObjectItemsNum, + }, + { + name: "UnstructuredList", + generateFunc: func(num int) (list runtime.Object) { + return fakeUnstructuredList(fakeObjectItemsNum) + }, + expectObjectNum: fakeObjectItemsNum, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Run("ExtractList", func(t *testing.T) { + expectObjectNames := map[string]struct{}{} + for i := 0; i < tc.expectObjectNum; i++ { + expectObjectNames[strconv.Itoa(i)] = struct{}{} + } + list := tc.generateFunc(tc.expectObjectNum) + objs, err := ExtractList(list) + if err != nil { + t.Fatalf("extract list %#v: %v", list, err) + } + for i := range objs { + var ( + o metav1.Object + err error + obj = objs[i] + ) + + if reflect.TypeOf(obj).Kind() == reflect.Struct { + copy := reflect.New(reflect.TypeOf(obj)) + copy.Elem().Set(reflect.ValueOf(obj)) + o, err = Accessor(copy.Interface()) + } else { + o, err = Accessor(obj) + } + if err != nil { + t.Fatalf("Accessor object %#v: %v", obj, err) + } + delete(expectObjectNames, o.GetName()) + } + if len(expectObjectNames) != 0 { + t.Fatal("expectObjectNames should be empty") + } + }) + t.Run("ExtractListWithAlloc", func(t *testing.T) { + expectObjectNames := map[string]struct{}{} + for i := 0; i < tc.expectObjectNum; i++ { + expectObjectNames[strconv.Itoa(i)] = struct{}{} + } + list := tc.generateFunc(tc.expectObjectNum) + objs, err := ExtractListWithAlloc(list) + if err != nil { + t.Fatalf("extract list with alloc: %v", err) + } + for i := range objs { + var ( + o metav1.Object + err error + obj = objs[i] + ) + if reflect.TypeOf(obj).Kind() == reflect.Struct { + copy := reflect.New(reflect.TypeOf(obj)) + copy.Elem().Set(reflect.ValueOf(obj)) + o, err = Accessor(copy.Interface()) + } else { + o, err = Accessor(obj) + } + if err != nil { + t.Fatalf("Accessor object %#v: %v", obj, err) + } + delete(expectObjectNames, o.GetName()) + } + if len(expectObjectNames) != 0 { + t.Fatal("expectObjectNames should be empty") + } + }) + }) + } +} + +func TestLenList(t *testing.T) { + tests := []struct { + name string + list runtime.Object + want int + }{ + { + name: "nil", + list: nil, + want: 0, + }, + { + name: "empty FooList", + list: &FooList{}, + want: 0, + }, + { + name: "FooList", + list: fakeFooList(fakeObjectItemsNum), + want: fakeObjectItemsNum, + }, + { + name: "SampleList", + list: fakeSampleList(fakeObjectItemsNum), + want: fakeObjectItemsNum, + }, + { + name: "RawExtensionList", + list: fakeExtensionList(fakeObjectItemsNum), + want: fakeObjectItemsNum, + }, + { + name: "UnstructuredList", + list: fakeUnstructuredList(fakeObjectItemsNum).(*unstructured.UnstructuredList), + want: fakeObjectItemsNum, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if got := LenList(tc.list); got != tc.want { + t.Errorf("LenList() = %d, want %d", got, tc.want) + } + }) + } +} + +func BenchmarkExtractListItem(b *testing.B) { + tests := []struct { + name string + list runtime.Object + }{ + { + name: "StructReceiverList", + list: fakeFooList(fakeObjectItemsNum), + }, + { + name: "PointerReceiverList", + list: fakeSampleList(fakeObjectItemsNum), + }, + { + name: "RawExtensionList", + list: fakeExtensionList(fakeObjectItemsNum), + }, + } + for _, tc := range tests { + b.Run(tc.name, func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ExtractList(tc.list) + if err != nil { + b.Fatalf("ExtractList: %v", err) + } + } + b.StopTimer() + }) + } +} + +func BenchmarkEachListItem(b *testing.B) { + tests := []struct { + name string + list runtime.Object + }{ + { + name: "StructReceiverList", + list: fakeFooList(fakeObjectItemsNum), + }, + { + name: "PointerReceiverList", + list: fakeSampleList(fakeObjectItemsNum), + }, + { + name: "RawExtensionList", + list: fakeExtensionList(fakeObjectItemsNum), + }, + { + name: "UnstructuredList", + list: fakeUnstructuredList(fakeObjectItemsNum), + }, + } + for _, tc := range tests { + b.Run(tc.name, func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := EachListItem(tc.list, func(object runtime.Object) error { + return nil + }) + if err != nil { + b.Fatalf("EachListItem: %v", err) + } + } + b.StopTimer() + }) + } +} + +func BenchmarkExtractListItemWithAlloc(b *testing.B) { + tests := []struct { + name string + list runtime.Object + }{ + { + name: "StructReceiverList", + list: fakeFooList(fakeObjectItemsNum), + }, + { + name: "PointerReceiverList", + list: fakeSampleList(fakeObjectItemsNum), + }, + { + name: "RawExtensionList", + list: fakeExtensionList(fakeObjectItemsNum), + }, + } + for _, tc := range tests { + b.Run(tc.name, func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ExtractListWithAlloc(tc.list) + if err != nil { + b.Fatalf("ExtractListWithAlloc: %v", err) + } + } + b.StopTimer() + }) + } +} + +func BenchmarkEachListItemWithAlloc(b *testing.B) { + tests := []struct { + name string + list runtime.Object + }{ + { + name: "StructReceiverList", + list: fakeFooList(fakeObjectItemsNum), + }, + { + name: "PointerReceiverList", + list: fakeSampleList(fakeObjectItemsNum), + }, + { + name: "RawExtensionList", + list: fakeExtensionList(fakeObjectItemsNum), + }, + { + name: "UnstructuredList", + list: fakeUnstructuredList(fakeObjectItemsNum), + }, + } + for _, tc := range tests { + b.Run(tc.name, func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := EachListItemWithAlloc(tc.list, func(object runtime.Object) error { + return nil + }) + if err != nil { + b.Fatalf("EachListItemWithAlloc: %v", err) + } + } + b.StopTimer() + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/interfaces.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/interfaces.go new file mode 100644 index 0000000000..628187eeeb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/interfaces.go @@ -0,0 +1,250 @@ +/* +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 meta + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" +) + +type ListMetaAccessor interface { + GetListMeta() List +} + +// List lets you work with list metadata from any of the versioned or +// internal API objects. Attempting to set or retrieve a field on an object that does +// not support that field will be a no-op and return a default value. +type List metav1.ListInterface + +// Type exposes the type and APIVersion of versioned or internal API objects. +type Type metav1.Type + +// MetadataAccessor lets you work with object and list metadata from any of the versioned or +// internal API objects. Attempting to set or retrieve a field on an object that does +// not support that field (Name, UID, Namespace on lists) will be a no-op and return +// a default value. +// +// MetadataAccessor exposes Interface in a way that can be used with multiple objects. +type MetadataAccessor interface { + APIVersion(obj runtime.Object) (string, error) + SetAPIVersion(obj runtime.Object, version string) error + + Kind(obj runtime.Object) (string, error) + SetKind(obj runtime.Object, kind string) error + + Namespace(obj runtime.Object) (string, error) + SetNamespace(obj runtime.Object, namespace string) error + + Name(obj runtime.Object) (string, error) + SetName(obj runtime.Object, name string) error + + GenerateName(obj runtime.Object) (string, error) + SetGenerateName(obj runtime.Object, name string) error + + UID(obj runtime.Object) (types.UID, error) + SetUID(obj runtime.Object, uid types.UID) error + + SelfLink(obj runtime.Object) (string, error) + SetSelfLink(obj runtime.Object, selfLink string) error + + Labels(obj runtime.Object) (map[string]string, error) + SetLabels(obj runtime.Object, labels map[string]string) error + + Annotations(obj runtime.Object) (map[string]string, error) + SetAnnotations(obj runtime.Object, annotations map[string]string) error + + Continue(obj runtime.Object) (string, error) + SetContinue(obj runtime.Object, c string) error + + runtime.ResourceVersioner +} + +type RESTScopeName string + +const ( + RESTScopeNameNamespace RESTScopeName = "namespace" + RESTScopeNameRoot RESTScopeName = "root" +) + +// RESTScope contains the information needed to deal with REST resources that are in a resource hierarchy +type RESTScope interface { + // Name of the scope + Name() RESTScopeName +} + +// RESTMapping contains the information needed to deal with objects of a specific +// resource and kind in a RESTful manner. +type RESTMapping struct { + // Resource is the GroupVersionResource (location) for this endpoint + Resource schema.GroupVersionResource + + // GroupVersionKind is the GroupVersionKind (data format) to submit to this endpoint + GroupVersionKind schema.GroupVersionKind + + // Scope contains the information needed to deal with REST Resources that are in a resource hierarchy + Scope RESTScope +} + +// RESTMapper allows clients to map resources to kind, and map kind and version +// to interfaces for manipulating those objects. It is primarily intended for +// consumers of Kubernetes compatible REST APIs as defined in docs/devel/api-conventions.md. +// +// The Kubernetes API provides versioned resources and object kinds which are scoped +// to API groups. In other words, kinds and resources should not be assumed to be +// unique across groups. +// +// RESTMapperWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use RESTMapperWithContext instead. +type RESTMapper interface { + // KindFor takes a partial resource and returns the single match. Returns an error if there are multiple matches + KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) + + // KindsFor takes a partial resource and returns the list of potential kinds in priority order + KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) + + // ResourceFor takes a partial resource and returns the single match. Returns an error if there are multiple matches + ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) + + // ResourcesFor takes a partial resource and returns the list of potential resource in priority order + ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) + + // RESTMapping identifies a preferred resource mapping for the provided group kind. + RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) + // RESTMappings returns all resource mappings for the provided group kind if no + // version search is provided. Otherwise identifies a preferred resource mapping for + // the provided version(s). + RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) + + ResourceSingularizer(resource string) (singular string, err error) +} + +// RESTMapperWithContext allows clients to map resources to kind, and map kind and version +// to interfaces for manipulating those objects. It is primarily intended for +// consumers of Kubernetes compatible REST APIs as defined in docs/devel/api-conventions.md. +// +// The Kubernetes API provides versioned resources and object kinds which are scoped +// to API groups. In other words, kinds and resources should not be assumed to be +// unique across groups. +type RESTMapperWithContext interface { + // KindFor takes a partial resource and returns the single match. Returns an error if there are multiple matches + KindForWithContext(ctx context.Context, resource schema.GroupVersionResource) (schema.GroupVersionKind, error) + + // KindsFor takes a partial resource and returns the list of potential kinds in priority order + KindsForWithContext(ctx context.Context, resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) + + // ResourceFor takes a partial resource and returns the single match. Returns an error if there are multiple matches + ResourceForWithContext(ctx context.Context, input schema.GroupVersionResource) (schema.GroupVersionResource, error) + + // ResourcesFor takes a partial resource and returns the list of potential resource in priority order + ResourcesForWithContext(ctx context.Context, input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) + + // RESTMapping identifies a preferred resource mapping for the provided group kind. + RESTMappingWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) (*RESTMapping, error) + // RESTMappings returns all resource mappings for the provided group kind if no + // version search is provided. Otherwise identifies a preferred resource mapping for + // the provided version(s). + RESTMappingsWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) + + ResourceSingularizerWithContext(ctx context.Context, resource string) (singular string, err error) +} + +func ToRESTMapperWithContext(m RESTMapper) RESTMapperWithContext { + if m == nil { + return nil + } + if m, ok := m.(RESTMapperWithContext); ok { + return m + } + return &restMapperWrapper{ + delegate: m, + } +} + +type restMapperWrapper struct { + delegate RESTMapper +} + +func (m *restMapperWrapper) KindForWithContext(ctx context.Context, resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + return m.delegate.KindFor(resource) +} +func (m *restMapperWrapper) KindsForWithContext(ctx context.Context, resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { + return m.delegate.KindsFor(resource) +} +func (m *restMapperWrapper) ResourceForWithContext(ctx context.Context, input schema.GroupVersionResource) (schema.GroupVersionResource, error) { + return m.delegate.ResourceFor(input) +} +func (m *restMapperWrapper) ResourcesForWithContext(ctx context.Context, input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + return m.delegate.ResourcesFor(input) +} +func (m *restMapperWrapper) RESTMappingWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) (*RESTMapping, error) { + return m.delegate.RESTMapping(gk, versions...) +} +func (m *restMapperWrapper) RESTMappingsWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + return m.delegate.RESTMappings(gk, versions...) +} +func (m *restMapperWrapper) ResourceSingularizerWithContext(ctx context.Context, resource string) (singular string, err error) { + return m.delegate.ResourceSingularizer(resource) +} + +// ResettableRESTMapper is a RESTMapper which is capable of resetting itself +// from discovery. +// All rest mappers that delegate to other rest mappers must implement this interface and dynamically +// check if the delegate mapper supports the Reset() operation. +// +// ResettableRESTMapperWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use ResettableRESTMapperWithContext instead. +type ResettableRESTMapper interface { + RESTMapper + Reset() +} + +// ResettableRESTMapperWithContext is a RESTMapper which is capable of resetting itself +// from discovery. +// All rest mappers that delegate to other rest mappers must implement this interface and dynamically +// check if the delegate mapper supports the ResetWithContext() operation. +type ResettableRESTMapperWithContext interface { + RESTMapperWithContext + ResetWithContext(ctx context.Context) +} + +func ToResettableRESTMapperWithContext(m ResettableRESTMapper) ResettableRESTMapperWithContext { + if m == nil { + return nil + } + if m, ok := m.(ResettableRESTMapperWithContext); ok { + return m + } + return &resettableRESTMapperWrapper{ + RESTMapperWithContext: ToRESTMapperWithContext(m), + delegate: m, + } +} + +type resettableRESTMapperWrapper struct { + RESTMapperWithContext + delegate ResettableRESTMapper +} + +func (m *resettableRESTMapperWrapper) ResetWithContext(ctx context.Context) { + m.delegate.Reset() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/lazy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/lazy.go new file mode 100644 index 0000000000..a4298114b6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/lazy.go @@ -0,0 +1,112 @@ +/* +Copyright 2017 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 meta + +import ( + "sync" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// lazyObject defers loading the mapper and typer until necessary. +type lazyObject struct { + loader func() (RESTMapper, error) + + lock sync.Mutex + loaded bool + err error + mapper RESTMapper +} + +// NewLazyRESTMapperLoader handles unrecoverable errors when creating a RESTMapper / ObjectTyper by +// returning those initialization errors when the interface methods are invoked. This defers the +// initialization and any server calls until a client actually needs to perform the action. +func NewLazyRESTMapperLoader(fn func() (RESTMapper, error)) RESTMapper { + obj := &lazyObject{loader: fn} + return obj +} + +// init lazily loads the mapper and typer, returning an error if initialization has failed. +func (o *lazyObject) init() error { + o.lock.Lock() + defer o.lock.Unlock() + if o.loaded { + return o.err + } + o.mapper, o.err = o.loader() + o.loaded = true + return o.err +} + +var _ ResettableRESTMapper = &lazyObject{} + +func (o *lazyObject) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + if err := o.init(); err != nil { + return schema.GroupVersionKind{}, err + } + return o.mapper.KindFor(resource) +} + +func (o *lazyObject) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { + if err := o.init(); err != nil { + return []schema.GroupVersionKind{}, err + } + return o.mapper.KindsFor(resource) +} + +func (o *lazyObject) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) { + if err := o.init(); err != nil { + return schema.GroupVersionResource{}, err + } + return o.mapper.ResourceFor(input) +} + +func (o *lazyObject) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + if err := o.init(); err != nil { + return []schema.GroupVersionResource{}, err + } + return o.mapper.ResourcesFor(input) +} + +func (o *lazyObject) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { + if err := o.init(); err != nil { + return nil, err + } + return o.mapper.RESTMapping(gk, versions...) +} + +func (o *lazyObject) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + if err := o.init(); err != nil { + return nil, err + } + return o.mapper.RESTMappings(gk, versions...) +} + +func (o *lazyObject) ResourceSingularizer(resource string) (singular string, err error) { + if err := o.init(); err != nil { + return "", err + } + return o.mapper.ResourceSingularizer(resource) +} + +func (o *lazyObject) Reset() { + o.lock.Lock() + defer o.lock.Unlock() + if o.loaded && o.err == nil { + MaybeResetRESTMapper(o.mapper) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/meta.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/meta.go new file mode 100644 index 0000000000..4bf24da522 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/meta.go @@ -0,0 +1,647 @@ +/* +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 meta + +import ( + "fmt" + "reflect" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" +) + +// errNotList is returned when an object implements the Object style interfaces but not the List style +// interfaces. +var errNotList = fmt.Errorf("object does not implement the List interfaces") + +var errNotCommon = fmt.Errorf("object does not implement the common interface for accessing the SelfLink") + +// CommonAccessor returns a Common interface for the provided object or an error if the object does +// not provide List. +func CommonAccessor(obj interface{}) (metav1.Common, error) { + switch t := obj.(type) { + case List: + return t, nil + case ListMetaAccessor: + if m := t.GetListMeta(); m != nil { + return m, nil + } + return nil, errNotCommon + case metav1.ListMetaAccessor: + if m := t.GetListMeta(); m != nil { + return m, nil + } + return nil, errNotCommon + case metav1.Object: + return t, nil + case metav1.ObjectMetaAccessor: + if m := t.GetObjectMeta(); m != nil { + return m, nil + } + return nil, errNotCommon + default: + return nil, errNotCommon + } +} + +// ListAccessor returns a List interface for the provided object or an error if the object does +// not provide List. +// IMPORTANT: Objects are NOT a superset of lists. Do not use this check to determine whether an +// object *is* a List. +func ListAccessor(obj interface{}) (List, error) { + switch t := obj.(type) { + case List: + return t, nil + case ListMetaAccessor: + if m := t.GetListMeta(); m != nil { + return m, nil + } + return nil, errNotList + case metav1.ListMetaAccessor: + if m := t.GetListMeta(); m != nil { + return m, nil + } + return nil, errNotList + default: + return nil, errNotList + } +} + +// errNotObject is returned when an object implements the List style interfaces but not the Object style +// interfaces. +var errNotObject = fmt.Errorf("object does not implement the Object interfaces") + +// Accessor takes an arbitrary object pointer and returns meta.Interface. +// obj must be a pointer to an API type. An error is returned if the minimum +// required fields are missing. Fields that are not required return the default +// value and are a no-op if set. +func Accessor(obj interface{}) (metav1.Object, error) { + switch t := obj.(type) { + case metav1.Object: + return t, nil + case metav1.ObjectMetaAccessor: + if m := t.GetObjectMeta(); m != nil { + return m, nil + } + return nil, errNotObject + default: + return nil, errNotObject + } +} + +// AsPartialObjectMetadata takes the metav1 interface and returns a partial object. +// TODO: consider making this solely a conversion action. +func AsPartialObjectMetadata(m metav1.Object) *metav1.PartialObjectMetadata { + switch t := m.(type) { + case *metav1.ObjectMeta: + return &metav1.PartialObjectMetadata{ObjectMeta: *t} + default: + return &metav1.PartialObjectMetadata{ + ObjectMeta: metav1.ObjectMeta{ + Name: m.GetName(), + GenerateName: m.GetGenerateName(), + Namespace: m.GetNamespace(), + SelfLink: m.GetSelfLink(), + UID: m.GetUID(), + ResourceVersion: m.GetResourceVersion(), + Generation: m.GetGeneration(), + CreationTimestamp: m.GetCreationTimestamp(), + DeletionTimestamp: m.GetDeletionTimestamp(), + DeletionGracePeriodSeconds: m.GetDeletionGracePeriodSeconds(), + Labels: m.GetLabels(), + Annotations: m.GetAnnotations(), + OwnerReferences: m.GetOwnerReferences(), + Finalizers: m.GetFinalizers(), + ManagedFields: m.GetManagedFields(), + }, + } + } +} + +// TypeAccessor returns an interface that allows retrieving and modifying the APIVersion +// and Kind of an in-memory internal object. +// TODO: this interface is used to test code that does not have ObjectMeta or ListMeta +// in round tripping (objects which can use apiVersion/kind, but do not fit the Kube +// api conventions). +func TypeAccessor(obj interface{}) (Type, error) { + if typed, ok := obj.(runtime.Object); ok { + return objectAccessor{typed}, nil + } + v, err := conversion.EnforcePtr(obj) + if err != nil { + return nil, err + } + t := v.Type() + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), t, v.Interface()) + } + + typeMeta := v.FieldByName("TypeMeta") + if !typeMeta.IsValid() { + return nil, fmt.Errorf("struct %v lacks embedded TypeMeta type", t) + } + a := &genericAccessor{} + if err := extractFromTypeMeta(typeMeta, a); err != nil { + return nil, fmt.Errorf("unable to find type fields on %#v: %v", typeMeta, err) + } + return a, nil +} + +type objectAccessor struct { + runtime.Object +} + +func (obj objectAccessor) GetKind() string { + return obj.GetObjectKind().GroupVersionKind().Kind +} + +func (obj objectAccessor) SetKind(kind string) { + gvk := obj.GetObjectKind().GroupVersionKind() + gvk.Kind = kind + obj.GetObjectKind().SetGroupVersionKind(gvk) +} + +func (obj objectAccessor) GetAPIVersion() string { + return obj.GetObjectKind().GroupVersionKind().GroupVersion().String() +} + +func (obj objectAccessor) SetAPIVersion(version string) { + gvk := obj.GetObjectKind().GroupVersionKind() + gv, err := schema.ParseGroupVersion(version) + if err != nil { + gv = schema.GroupVersion{Version: version} + } + gvk.Group, gvk.Version = gv.Group, gv.Version + obj.GetObjectKind().SetGroupVersionKind(gvk) +} + +// NewAccessor returns a MetadataAccessor that can retrieve +// or manipulate resource version on objects derived from core API +// metadata concepts. +func NewAccessor() MetadataAccessor { + return resourceAccessor{} +} + +// resourceAccessor implements ResourceVersioner and SelfLinker. +type resourceAccessor struct{} + +func (resourceAccessor) Kind(obj runtime.Object) (string, error) { + return objectAccessor{obj}.GetKind(), nil +} + +func (resourceAccessor) SetKind(obj runtime.Object, kind string) error { + objectAccessor{obj}.SetKind(kind) + return nil +} + +func (resourceAccessor) APIVersion(obj runtime.Object) (string, error) { + return objectAccessor{obj}.GetAPIVersion(), nil +} + +func (resourceAccessor) SetAPIVersion(obj runtime.Object, version string) error { + objectAccessor{obj}.SetAPIVersion(version) + return nil +} + +func (resourceAccessor) Namespace(obj runtime.Object) (string, error) { + accessor, err := Accessor(obj) + if err != nil { + return "", err + } + return accessor.GetNamespace(), nil +} + +func (resourceAccessor) SetNamespace(obj runtime.Object, namespace string) error { + accessor, err := Accessor(obj) + if err != nil { + return err + } + accessor.SetNamespace(namespace) + return nil +} + +func (resourceAccessor) Name(obj runtime.Object) (string, error) { + accessor, err := Accessor(obj) + if err != nil { + return "", err + } + return accessor.GetName(), nil +} + +func (resourceAccessor) SetName(obj runtime.Object, name string) error { + accessor, err := Accessor(obj) + if err != nil { + return err + } + accessor.SetName(name) + return nil +} + +func (resourceAccessor) GenerateName(obj runtime.Object) (string, error) { + accessor, err := Accessor(obj) + if err != nil { + return "", err + } + return accessor.GetGenerateName(), nil +} + +func (resourceAccessor) SetGenerateName(obj runtime.Object, name string) error { + accessor, err := Accessor(obj) + if err != nil { + return err + } + accessor.SetGenerateName(name) + return nil +} + +func (resourceAccessor) UID(obj runtime.Object) (types.UID, error) { + accessor, err := Accessor(obj) + if err != nil { + return "", err + } + return accessor.GetUID(), nil +} + +func (resourceAccessor) SetUID(obj runtime.Object, uid types.UID) error { + accessor, err := Accessor(obj) + if err != nil { + return err + } + accessor.SetUID(uid) + return nil +} + +func (resourceAccessor) SelfLink(obj runtime.Object) (string, error) { + accessor, err := CommonAccessor(obj) + if err != nil { + return "", err + } + return accessor.GetSelfLink(), nil +} + +func (resourceAccessor) SetSelfLink(obj runtime.Object, selfLink string) error { + accessor, err := CommonAccessor(obj) + if err != nil { + return err + } + accessor.SetSelfLink(selfLink) + return nil +} + +func (resourceAccessor) Labels(obj runtime.Object) (map[string]string, error) { + accessor, err := Accessor(obj) + if err != nil { + return nil, err + } + return accessor.GetLabels(), nil +} + +func (resourceAccessor) SetLabels(obj runtime.Object, labels map[string]string) error { + accessor, err := Accessor(obj) + if err != nil { + return err + } + accessor.SetLabels(labels) + return nil +} + +func (resourceAccessor) Annotations(obj runtime.Object) (map[string]string, error) { + accessor, err := Accessor(obj) + if err != nil { + return nil, err + } + return accessor.GetAnnotations(), nil +} + +func (resourceAccessor) SetAnnotations(obj runtime.Object, annotations map[string]string) error { + accessor, err := Accessor(obj) + if err != nil { + return err + } + accessor.SetAnnotations(annotations) + return nil +} + +func (resourceAccessor) ResourceVersion(obj runtime.Object) (string, error) { + accessor, err := CommonAccessor(obj) + if err != nil { + return "", err + } + return accessor.GetResourceVersion(), nil +} + +func (resourceAccessor) SetResourceVersion(obj runtime.Object, version string) error { + accessor, err := CommonAccessor(obj) + if err != nil { + return err + } + accessor.SetResourceVersion(version) + return nil +} + +func (resourceAccessor) Continue(obj runtime.Object) (string, error) { + accessor, err := ListAccessor(obj) + if err != nil { + return "", err + } + return accessor.GetContinue(), nil +} + +func (resourceAccessor) SetContinue(obj runtime.Object, version string) error { + accessor, err := ListAccessor(obj) + if err != nil { + return err + } + accessor.SetContinue(version) + return nil +} + +// extractFromOwnerReference extracts v to o. v is the OwnerReferences field of an object. +func extractFromOwnerReference(v reflect.Value, o *metav1.OwnerReference) error { + if err := runtime.Field(v, "APIVersion", &o.APIVersion); err != nil { + return err + } + if err := runtime.Field(v, "Kind", &o.Kind); err != nil { + return err + } + if err := runtime.Field(v, "Name", &o.Name); err != nil { + return err + } + if err := runtime.Field(v, "UID", &o.UID); err != nil { + return err + } + var controllerPtr *bool + if err := runtime.Field(v, "Controller", &controllerPtr); err != nil { + return err + } + if controllerPtr != nil { + controller := *controllerPtr + o.Controller = &controller + } + var blockOwnerDeletionPtr *bool + if err := runtime.Field(v, "BlockOwnerDeletion", &blockOwnerDeletionPtr); err != nil { + return err + } + if blockOwnerDeletionPtr != nil { + block := *blockOwnerDeletionPtr + o.BlockOwnerDeletion = &block + } + return nil +} + +// setOwnerReference sets v to o. v is the OwnerReferences field of an object. +func setOwnerReference(v reflect.Value, o *metav1.OwnerReference) error { + if err := runtime.SetField(o.APIVersion, v, "APIVersion"); err != nil { + return err + } + if err := runtime.SetField(o.Kind, v, "Kind"); err != nil { + return err + } + if err := runtime.SetField(o.Name, v, "Name"); err != nil { + return err + } + if err := runtime.SetField(o.UID, v, "UID"); err != nil { + return err + } + if o.Controller != nil { + controller := *(o.Controller) + if err := runtime.SetField(&controller, v, "Controller"); err != nil { + return err + } + } + if o.BlockOwnerDeletion != nil { + block := *(o.BlockOwnerDeletion) + if err := runtime.SetField(&block, v, "BlockOwnerDeletion"); err != nil { + return err + } + } + return nil +} + +// genericAccessor contains pointers to strings that can modify an arbitrary +// struct and implements the Accessor interface. +type genericAccessor struct { + namespace *string + name *string + generateName *string + uid *types.UID + apiVersion *string + kind *string + resourceVersion *string + selfLink *string + creationTimestamp *metav1.Time + deletionTimestamp **metav1.Time + labels *map[string]string + annotations *map[string]string + ownerReferences reflect.Value + finalizers *[]string +} + +func (a genericAccessor) GetNamespace() string { + if a.namespace == nil { + return "" + } + return *a.namespace +} + +func (a genericAccessor) SetNamespace(namespace string) { + if a.namespace == nil { + return + } + *a.namespace = namespace +} + +func (a genericAccessor) GetName() string { + if a.name == nil { + return "" + } + return *a.name +} + +func (a genericAccessor) SetName(name string) { + if a.name == nil { + return + } + *a.name = name +} + +func (a genericAccessor) GetGenerateName() string { + if a.generateName == nil { + return "" + } + return *a.generateName +} + +func (a genericAccessor) SetGenerateName(generateName string) { + if a.generateName == nil { + return + } + *a.generateName = generateName +} + +func (a genericAccessor) GetUID() types.UID { + if a.uid == nil { + return "" + } + return *a.uid +} + +func (a genericAccessor) SetUID(uid types.UID) { + if a.uid == nil { + return + } + *a.uid = uid +} + +func (a genericAccessor) GetAPIVersion() string { + return *a.apiVersion +} + +func (a genericAccessor) SetAPIVersion(version string) { + *a.apiVersion = version +} + +func (a genericAccessor) GetKind() string { + return *a.kind +} + +func (a genericAccessor) SetKind(kind string) { + *a.kind = kind +} + +func (a genericAccessor) GetResourceVersion() string { + return *a.resourceVersion +} + +func (a genericAccessor) SetResourceVersion(version string) { + *a.resourceVersion = version +} + +func (a genericAccessor) GetSelfLink() string { + return *a.selfLink +} + +func (a genericAccessor) SetSelfLink(selfLink string) { + *a.selfLink = selfLink +} + +func (a genericAccessor) GetCreationTimestamp() metav1.Time { + return *a.creationTimestamp +} + +func (a genericAccessor) SetCreationTimestamp(timestamp metav1.Time) { + *a.creationTimestamp = timestamp +} + +func (a genericAccessor) GetDeletionTimestamp() *metav1.Time { + return *a.deletionTimestamp +} + +func (a genericAccessor) SetDeletionTimestamp(timestamp *metav1.Time) { + *a.deletionTimestamp = timestamp +} + +func (a genericAccessor) GetLabels() map[string]string { + if a.labels == nil { + return nil + } + return *a.labels +} + +func (a genericAccessor) SetLabels(labels map[string]string) { + *a.labels = labels +} + +func (a genericAccessor) GetAnnotations() map[string]string { + if a.annotations == nil { + return nil + } + return *a.annotations +} + +func (a genericAccessor) SetAnnotations(annotations map[string]string) { + if a.annotations == nil { + emptyAnnotations := make(map[string]string) + a.annotations = &emptyAnnotations + } + *a.annotations = annotations +} + +func (a genericAccessor) GetFinalizers() []string { + if a.finalizers == nil { + return nil + } + return *a.finalizers +} + +func (a genericAccessor) SetFinalizers(finalizers []string) { + *a.finalizers = finalizers +} + +func (a genericAccessor) GetOwnerReferences() []metav1.OwnerReference { + var ret []metav1.OwnerReference + s := a.ownerReferences + if s.Kind() != reflect.Pointer || s.Elem().Kind() != reflect.Slice { + //nolint:logcheck // Should not happen. + klog.Errorf("expect %v to be a pointer to slice", s) + return ret + } + s = s.Elem() + // Set the capacity to one element greater to avoid copy if the caller later append an element. + ret = make([]metav1.OwnerReference, s.Len(), s.Len()+1) + for i := 0; i < s.Len(); i++ { + if err := extractFromOwnerReference(s.Index(i), &ret[i]); err != nil { + //nolint:logcheck // Should not happen. + klog.Errorf("extractFromOwnerReference failed: %v", err) + return ret + } + } + return ret +} + +func (a genericAccessor) SetOwnerReferences(references []metav1.OwnerReference) { + s := a.ownerReferences + if s.Kind() != reflect.Pointer || s.Elem().Kind() != reflect.Slice { + //nolint:logcheck // Should not happen. + klog.Errorf("expect %v to be a pointer to slice", s) + } + s = s.Elem() + newReferences := reflect.MakeSlice(s.Type(), len(references), len(references)) + for i := 0; i < len(references); i++ { + if err := setOwnerReference(newReferences.Index(i), &references[i]); err != nil { + //nolint:logcheck // Should not happen. + klog.Errorf("setOwnerReference failed: %v", err) + return + } + } + s.Set(newReferences) +} + +// extractFromTypeMeta extracts pointers to version and kind fields from an object +func extractFromTypeMeta(v reflect.Value, a *genericAccessor) error { + if err := runtime.FieldPtr(v, "APIVersion", &a.apiVersion); err != nil { + return err + } + if err := runtime.FieldPtr(v, "Kind", &a.kind); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/meta_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/meta_test.go new file mode 100644 index 0000000000..31402441a9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/meta_test.go @@ -0,0 +1,51 @@ +/* +Copyright 2017 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 meta + +import ( + "math/rand" + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/randfill" +) + +func TestAsPartialObjectMetadata(t *testing.T) { + f := randfill.New().NilChance(.5).NumElements(0, 1).RandSource(rand.NewSource(1)) + + for i := 0; i < 100; i++ { + m := &metav1.ObjectMeta{} + f.Fill(m) + partial := AsPartialObjectMetadata(m) + if !reflect.DeepEqual(&partial.ObjectMeta, m) { + t.Fatalf("incomplete partial object metadata: %s", cmp.Diff(&partial.ObjectMeta, m)) + } + } + + for i := 0; i < 100; i++ { + m := &metav1beta1.PartialObjectMetadata{} + f.Fill(&m.ObjectMeta) + partial := AsPartialObjectMetadata(m) + if !reflect.DeepEqual(&partial.ObjectMeta, &m.ObjectMeta) { + t.Fatalf("incomplete partial object metadata: %s", cmp.Diff(&partial.ObjectMeta, &m.ObjectMeta)) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/multirestmapper.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/multirestmapper.go new file mode 100644 index 0000000000..2b2c143ce7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/multirestmapper.go @@ -0,0 +1,318 @@ +/* +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 meta + +import ( + "context" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" + utilerrors "k8s.io/apimachinery/pkg/util/errors" +) + +var ( + _ ResettableRESTMapper = MultiRESTMapper{} + _ fmt.Stringer = MultiRESTMapper{} + _ ResettableRESTMapperWithContext = MultiRESTMapperWithContext{} + _ fmt.Stringer = MultiRESTMapperWithContext{} +) + +// MultiRESTMapper is a wrapper for multiple RESTMappers. +// +// MultiRESTMapperWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext instead. +type MultiRESTMapper []RESTMapper + +// MultiRESTMapperWithContext is a wrapper for multiple RESTMapperWithContext instances. +type MultiRESTMapperWithContext []RESTMapperWithContext + +func (m MultiRESTMapper) String() string { + return stringifyMapper("MultiRESTMapper", m) +} + +func (m MultiRESTMapperWithContext) String() string { + return stringifyMapper("MultiRESTMapperWithContext", m) +} + +func stringifyMapper[T any](typeName string, m []T) string { + nested := make([]string, 0, len(m)) + for _, t := range m { + currString := fmt.Sprintf("%v", t) + splitStrings := strings.Split(currString, "\n") + nested = append(nested, strings.Join(splitStrings, "\n\t")) + } + + return fmt.Sprintf("%s{\n\t%s\n}", typeName, strings.Join(nested, "\n\t")) +} + +func ToMultiRESTMapperWithContext(m MultiRESTMapper) MultiRESTMapperWithContext { + if m == nil { + return nil + } + mc := make(MultiRESTMapperWithContext, len(m)) + for i, m := range m { + mc[i] = ToRESTMapperWithContext(m) + } + return mc +} + +// ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod) +// This implementation supports multiple REST schemas and return the first match. +// +// MultiRESTMapperWithContext.ResourceSingularizerWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext.ResourceSingularizerWithContext instead. +func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + return ToMultiRESTMapperWithContext(m).ResourceSingularizerWithContext(context.Background(), resource) +} + +// MultiRESTMapperWithContext.ResourcesForWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext.ResourcesForWithContext instead. +func (m MultiRESTMapper) ResourcesFor(resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + return ToMultiRESTMapperWithContext(m).ResourcesForWithContext(context.Background(), resource) +} + +// MultiRESTMapperWithContext.KindsForWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext.KindsForWithContext instead. +func (m MultiRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) { + return ToMultiRESTMapperWithContext(m).KindsForWithContext(context.Background(), resource) +} + +// MultiRESTMapperWithContext.ResourceForWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext.ResourceForWithContext instead. +func (m MultiRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { + return ToMultiRESTMapperWithContext(m).ResourceForWithContext(context.Background(), resource) +} + +// MultiRESTMapperWithContext.KindForWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext.KindForWithContext instead. +func (m MultiRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + return ToMultiRESTMapperWithContext(m).KindForWithContext(context.Background(), resource) +} + +// RESTMapping provides the REST mapping for the resource based on the +// kind and version. This implementation supports multiple REST schemas and +// return the first match. +// +// MultiRESTMapperWithContext.RESTMappingWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext.RESTMappingWithContext instead. +func (m MultiRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { + return ToMultiRESTMapperWithContext(m).RESTMappingWithContext(context.Background(), gk, versions...) +} + +// RESTMappings returns all possible RESTMappings for the provided group kind, or an error +// if the type is not recognized. +// +// MultiRESTMapperWithContext.RESTMappingsWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext.RESTMappingsWithContext instead. +func (m MultiRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + return ToMultiRESTMapperWithContext(m).RESTMappingsWithContext(context.Background(), gk, versions...) +} + +// MultiRESTMapperWithContext.Reset is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MultiRESTMapperWithContext.Reset instead. +func (m MultiRESTMapper) Reset() { + for _, t := range m { + MaybeResetRESTMapper(t) + } +} + +// ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod) +// This implementation supports multiple REST schemas and return the first match. +func (m MultiRESTMapperWithContext) ResourceSingularizerWithContext(ctx context.Context, resource string) (singular string, err error) { + for _, t := range m { + singular, err = t.ResourceSingularizerWithContext(ctx, resource) + if err == nil { + return + } + } + return +} + +func (m MultiRESTMapperWithContext) ResourcesForWithContext(ctx context.Context, resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + allGVRs := []schema.GroupVersionResource{} + for _, t := range m { + gvrs, err := t.ResourcesForWithContext(ctx, resource) + // ignore "no match" errors, but any other error percolates back up + if IsNoMatchError(err) { + continue + } + if err != nil { + return nil, err + } + + // walk the existing values to de-dup + for _, curr := range gvrs { + found := false + for _, existing := range allGVRs { + if curr == existing { + found = true + break + } + } + + if !found { + allGVRs = append(allGVRs, curr) + } + } + } + + if len(allGVRs) == 0 { + return nil, &NoResourceMatchError{PartialResource: resource} + } + + return allGVRs, nil +} + +func (m MultiRESTMapperWithContext) KindsForWithContext(ctx context.Context, resource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) { + allGVKs := []schema.GroupVersionKind{} + for _, t := range m { + gvks, err := t.KindsForWithContext(ctx, resource) + // ignore "no match" errors, but any other error percolates back up + if IsNoMatchError(err) { + continue + } + if err != nil { + return nil, err + } + + // walk the existing values to de-dup + for _, curr := range gvks { + found := false + for _, existing := range allGVKs { + if curr == existing { + found = true + break + } + } + + if !found { + allGVKs = append(allGVKs, curr) + } + } + } + + if len(allGVKs) == 0 { + return nil, &NoResourceMatchError{PartialResource: resource} + } + + return allGVKs, nil +} + +func (m MultiRESTMapperWithContext) ResourceForWithContext(ctx context.Context, resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { + resources, err := m.ResourcesForWithContext(ctx, resource) + if err != nil { + return schema.GroupVersionResource{}, err + } + if len(resources) == 1 { + return resources[0], nil + } + + return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources} +} + +func (m MultiRESTMapperWithContext) KindForWithContext(ctx context.Context, resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + kinds, err := m.KindsForWithContext(ctx, resource) + if err != nil { + return schema.GroupVersionKind{}, err + } + if len(kinds) == 1 { + return kinds[0], nil + } + + return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds} +} + +// RESTMapping provides the REST mapping for the resource based on the +// kind and version. This implementation supports multiple REST schemas and +// return the first match. +func (m MultiRESTMapperWithContext) RESTMappingWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) (*RESTMapping, error) { + allMappings := []*RESTMapping{} + errors := []error{} + + for _, t := range m { + currMapping, err := t.RESTMappingWithContext(ctx, gk, versions...) + // ignore "no match" errors, but any other error percolates back up + if IsNoMatchError(err) { + continue + } + if err != nil { + errors = append(errors, err) + continue + } + + allMappings = append(allMappings, currMapping) + } + + // if we got exactly one mapping, then use it even if other requested failed + if len(allMappings) == 1 { + return allMappings[0], nil + } + if len(allMappings) > 1 { + var kinds []schema.GroupVersionKind + for _, m := range allMappings { + kinds = append(kinds, m.GroupVersionKind) + } + return nil, &AmbiguousKindError{PartialKind: gk.WithVersion(""), MatchingKinds: kinds} + } + if len(errors) > 0 { + return nil, utilerrors.NewAggregate(errors) + } + return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} +} + +// RESTMappings returns all possible RESTMappings for the provided group kind, or an error +// if the type is not recognized. +func (m MultiRESTMapperWithContext) RESTMappingsWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + var allMappings []*RESTMapping + var errors []error + + for _, t := range m { + currMappings, err := t.RESTMappingsWithContext(ctx, gk, versions...) + // ignore "no match" errors, but any other error percolates back up + if IsNoMatchError(err) { + continue + } + if err != nil { + errors = append(errors, err) + continue + } + allMappings = append(allMappings, currMappings...) + } + if len(errors) > 0 { + return nil, utilerrors.NewAggregate(errors) + } + if len(allMappings) == 0 { + return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} + } + return allMappings, nil +} + +func (m MultiRESTMapperWithContext) ResetWithContext(ctx context.Context) { + for _, t := range m { + MaybeResetRESTMapperWithContext(ctx, t) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/multirestmapper_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/multirestmapper_test.go new file mode 100644 index 0000000000..b71ca468d3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/multirestmapper_test.go @@ -0,0 +1,391 @@ +/* +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 meta + +import ( + "errors" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestMultiRESTMapperResourceFor(t *testing.T) { + tcs := []struct { + name string + + mapper MultiRESTMapper + input schema.GroupVersionResource + result schema.GroupVersionResource + err error + }{ + { + name: "empty", + mapper: MultiRESTMapper{}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: schema.GroupVersionResource{}, + err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "ignore not found", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: schema.GroupVersionResource{}, + err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "unused"}}}}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: schema.GroupVersionResource{}, + err: errors.New("fail on this"), + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.ResourceFor(tc.input) + if e, a := tc.result, actualResult; e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +func TestMultiRESTMapperResourcesFor(t *testing.T) { + tcs := []struct { + name string + + mapper MultiRESTMapper + input schema.GroupVersionResource + result []schema.GroupVersionResource + err error + }{ + { + name: "empty", + mapper: MultiRESTMapper{}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: nil, + err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "ignore not found", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: nil, + err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "unused"}}}}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: nil, + err: errors.New("fail on this"), + }, + { + name: "union and dedup", + mapper: MultiRESTMapper{ + fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "dupe"}, {Resource: "first"}}}, + fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "dupe"}, {Resource: "second"}}}, + }, + input: schema.GroupVersionResource{Resource: "foo"}, + result: []schema.GroupVersionResource{{Resource: "dupe"}, {Resource: "first"}, {Resource: "second"}}, + }, + { + name: "skip not and continue", + mapper: MultiRESTMapper{ + fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}, + fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "first"}, {Resource: "second"}}}, + }, + input: schema.GroupVersionResource{Resource: "foo"}, + result: []schema.GroupVersionResource{{Resource: "first"}, {Resource: "second"}}, + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.ResourcesFor(tc.input) + if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +func TestMultiRESTMapperKindsFor(t *testing.T) { + tcs := []struct { + name string + + mapper MultiRESTMapper + input schema.GroupVersionResource + result []schema.GroupVersionKind + err error + }{ + { + name: "empty", + mapper: MultiRESTMapper{}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: nil, + err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "ignore not found", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: nil, + err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "unused"}}}}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: nil, + err: errors.New("fail on this"), + }, + { + name: "union and dedup", + mapper: MultiRESTMapper{ + fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "dupe"}, {Kind: "first"}}}, + fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "dupe"}, {Kind: "second"}}}, + }, + input: schema.GroupVersionResource{Resource: "foo"}, + result: []schema.GroupVersionKind{{Kind: "dupe"}, {Kind: "first"}, {Kind: "second"}}, + }, + { + name: "skip not and continue", + mapper: MultiRESTMapper{ + fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}, + fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "first"}, {Kind: "second"}}}, + }, + input: schema.GroupVersionResource{Resource: "foo"}, + result: []schema.GroupVersionKind{{Kind: "first"}, {Kind: "second"}}, + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.KindsFor(tc.input) + if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +func TestMultiRESTMapperKindFor(t *testing.T) { + tcs := []struct { + name string + + mapper MultiRESTMapper + input schema.GroupVersionResource + result schema.GroupVersionKind + err error + }{ + { + name: "empty", + mapper: MultiRESTMapper{}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: schema.GroupVersionKind{}, + err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "ignore not found", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: schema.GroupVersionKind{}, + err: &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "unused"}}}}, + input: schema.GroupVersionResource{Resource: "foo"}, + result: schema.GroupVersionKind{}, + err: errors.New("fail on this"), + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.KindFor(tc.input) + if e, a := tc.result, actualResult; e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +func TestMultiRESTMapperRESTMappings(t *testing.T) { + mapping1, mapping2 := &RESTMapping{}, &RESTMapping{} + tcs := []struct { + name string + + mapper MultiRESTMapper + groupKind schema.GroupKind + versions []string + result []*RESTMapping + err error + }{ + { + name: "empty with no versions", + mapper: MultiRESTMapper{}, + groupKind: schema.GroupKind{Kind: "Foo"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, + }, + { + name: "empty with one version", + mapper: MultiRESTMapper{}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta"}}, + }, + { + name: "empty with multi(two) vesions", + mapper: MultiRESTMapper{}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta", "v2"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta", "v2"}}, + }, + { + name: "ignore not found with kind not exist", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "IGNORE_THIS"}}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: nil, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, + }, + { + name: "ignore not found with version not exist", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1"}}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta"}}, + }, + { + name: "ignore not found with multi versions not exist", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1"}}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta", "v2"}, + result: nil, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}, SearchedVersions: []string{"v1beta", "v2"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{mappings: []*RESTMapping{mapping1}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta"}, + result: nil, + err: errors.New("fail on this"), + }, + { + name: "return both", + mapper: MultiRESTMapper{fixedRESTMapper{mappings: []*RESTMapping{mapping1}}, fixedRESTMapper{mappings: []*RESTMapping{mapping2}}}, + groupKind: schema.GroupKind{Kind: "Foo"}, + versions: []string{"v1beta"}, + result: []*RESTMapping{mapping1, mapping2}, + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.RESTMappings(tc.groupKind, tc.versions...) + if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +type fixedRESTMapper struct { + resourcesFor []schema.GroupVersionResource + kindsFor []schema.GroupVersionKind + resourceFor schema.GroupVersionResource + kindFor schema.GroupVersionKind + mappings []*RESTMapping + + err error +} + +func (m fixedRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + return "", m.err +} + +func (m fixedRESTMapper) ResourcesFor(resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + return m.resourcesFor, m.err +} + +func (m fixedRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) { + return m.kindsFor, m.err +} + +func (m fixedRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { + return m.resourceFor, m.err +} + +func (m fixedRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + return m.kindFor, m.err +} + +func (m fixedRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) { + return nil, m.err +} + +func (m fixedRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (mappings []*RESTMapping, err error) { + return m.mappings, m.err +} + +func (m fixedRESTMapper) ResourceIsValid(resource schema.GroupVersionResource) bool { + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/priority.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/priority.go new file mode 100644 index 0000000000..d152e7e322 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/priority.go @@ -0,0 +1,328 @@ +/* +Copyright 2016 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 meta + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + AnyGroup = "*" + AnyVersion = "*" + AnyResource = "*" + AnyKind = "*" +) + +var ( + _ ResettableRESTMapper = PriorityRESTMapper{} + _ ResettableRESTMapperWithContext = PriorityRESTMapperWithContext{} + _ fmt.Stringer = PriorityRESTMapperWithContext{} +) + +// PriorityRESTMapper is a wrapper for automatically choosing a particular Resource or Kind +// when multiple matches are possible +// +// PriorityRESTMapperWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext instead. +type PriorityRESTMapper struct { + // Delegate is the RESTMapper to use to locate all the Kind and Resource matches + Delegate RESTMapper + + // ResourcePriority is a list of priority patterns to apply to matching resources. + // The list of all matching resources is narrowed based on the patterns until only one remains. + // A pattern with no matches is skipped. A pattern with more than one match uses its + // matches as the list to continue matching against. + ResourcePriority []schema.GroupVersionResource + + // KindPriority is a list of priority patterns to apply to matching kinds. + // The list of all matching kinds is narrowed based on the patterns until only one remains. + // A pattern with no matches is skipped. A pattern with more than one match uses its + // matches as the list to continue matching against. + KindPriority []schema.GroupVersionKind +} + +func (m PriorityRESTMapper) String() string { + return fmt.Sprintf("PriorityRESTMapper{\n\t%v\n\t%v\n\t%v\n}", m.ResourcePriority, m.KindPriority, m.Delegate) +} + +// PriorityRESTMapperWithContext is a wrapper for automatically choosing a particular Resource or Kind +// when multiple matches are possible +type PriorityRESTMapperWithContext struct { + // Delegate is the RESTMapperWithContext to use to locate all the Kind and Resource matches + Delegate RESTMapperWithContext + + // ResourcePriority is a list of priority patterns to apply to matching resources. + // The list of all matching resources is narrowed based on the patterns until only one remains. + // A pattern with no matches is skipped. A pattern with more than one match uses its + // matches as the list to continue matching against. + ResourcePriority []schema.GroupVersionResource + + // KindPriority is a list of priority patterns to apply to matching kinds. + // The list of all matching kinds is narrowed based on the patterns until only one remains. + // A pattern with no matches is skipped. A pattern with more than one match uses its + // matches as the list to continue matching against. + KindPriority []schema.GroupVersionKind +} + +func (m PriorityRESTMapperWithContext) String() string { + return fmt.Sprintf("PriorityRESTMapperWithContext{\n\t%v\n\t%v\n\t%v\n}", m.ResourcePriority, m.KindPriority, m.Delegate) +} + +func ToPriorityRESTMapperWithContext(m PriorityRESTMapper) PriorityRESTMapperWithContext { + return PriorityRESTMapperWithContext{ + Delegate: ToRESTMapperWithContext(m.Delegate), + ResourcePriority: m.ResourcePriority, + KindPriority: m.KindPriority, + } +} + +// ResourceFor finds all resources, then passes them through the ResourcePriority patterns to find a single matching hit. +// +// PriorityRESTMapperWithContext.ResourceForWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext.ResourceForWithContext instead. +func (m PriorityRESTMapper) ResourceFor(partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionResource, error) { + return ToPriorityRESTMapperWithContext(m).ResourceForWithContext(context.Background(), partiallySpecifiedResource) +} + +// KindFor finds all kinds, then passes them through the KindPriority patterns to find a single matching hit. +// +// PriorityRESTMapperWithContext.KindForWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext.KindForWithContext instead. +func (m PriorityRESTMapper) KindFor(partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + return ToPriorityRESTMapperWithContext(m).KindForWithContext(context.Background(), partiallySpecifiedResource) +} + +// PriorityRESTMapperWithContext.RESTMappingWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext.RESTMappingWithContext instead. +func (m PriorityRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) { + return ToPriorityRESTMapperWithContext(m).RESTMappingWithContext(context.Background(), gk, versions...) +} + +// PriorityRESTMapperWithContext.RESTMappingsWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext.RESTMappingsWithContext instead. +func (m PriorityRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + return m.Delegate.RESTMappings(gk, versions...) +} + +// PriorityRESTMapperWithContext.ResourceSingularizerWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext.ResourceSingularizerWithContext instead. +func (m PriorityRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + return m.Delegate.ResourceSingularizer(resource) +} + +// PriorityRESTMapperWithContext.ResourcesForWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext.ResourcesForWithContext instead. +func (m PriorityRESTMapper) ResourcesFor(partiallySpecifiedResource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + return m.Delegate.ResourcesFor(partiallySpecifiedResource) +} + +// PriorityRESTMapperWithContext.KindsForWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext.KindsForWithContext instead. +func (m PriorityRESTMapper) KindsFor(partiallySpecifiedResource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) { + return m.Delegate.KindsFor(partiallySpecifiedResource) +} + +// PriorityRESTMapperWithContext.ResetWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use PriorityRESTMapperWithContext.ResetWithContext instead. +func (m PriorityRESTMapper) Reset() { + MaybeResetRESTMapper(m.Delegate) +} + +// ResourceFor finds all resources, then passes them through the ResourcePriority patterns to find a single matching hit. +func (m PriorityRESTMapperWithContext) ResourceForWithContext(ctx context.Context, partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionResource, error) { + originalGVRs, originalErr := m.Delegate.ResourcesForWithContext(ctx, partiallySpecifiedResource) + if originalErr != nil && len(originalGVRs) == 0 { + return schema.GroupVersionResource{}, originalErr + } + if len(originalGVRs) == 1 { + return originalGVRs[0], originalErr + } + + remainingGVRs := append([]schema.GroupVersionResource{}, originalGVRs...) + for _, pattern := range m.ResourcePriority { + matchedGVRs := []schema.GroupVersionResource{} + for _, gvr := range remainingGVRs { + if resourceMatches(pattern, gvr) { + matchedGVRs = append(matchedGVRs, gvr) + } + } + + switch len(matchedGVRs) { + case 0: + // if you have no matches, then nothing matched this pattern just move to the next + continue + case 1: + // one match, return + return matchedGVRs[0], originalErr + default: + // more than one match, use the matched hits as the list moving to the next pattern. + // this way you can have a series of selection criteria + remainingGVRs = matchedGVRs + } + } + + return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingResources: originalGVRs} +} + +// KindFor finds all kinds, then passes them through the KindPriority patterns to find a single matching hit. +func (m PriorityRESTMapperWithContext) KindForWithContext(ctx context.Context, partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + originalGVKs, originalErr := m.Delegate.KindsForWithContext(ctx, partiallySpecifiedResource) + if originalErr != nil && len(originalGVKs) == 0 { + return schema.GroupVersionKind{}, originalErr + } + if len(originalGVKs) == 1 { + return originalGVKs[0], originalErr + } + + remainingGVKs := append([]schema.GroupVersionKind{}, originalGVKs...) + for _, pattern := range m.KindPriority { + matchedGVKs := []schema.GroupVersionKind{} + for _, gvr := range remainingGVKs { + if kindMatches(pattern, gvr) { + matchedGVKs = append(matchedGVKs, gvr) + } + } + + switch len(matchedGVKs) { + case 0: + // if you have no matches, then nothing matched this pattern just move to the next + continue + case 1: + // one match, return + return matchedGVKs[0], originalErr + default: + // more than one match, use the matched hits as the list moving to the next pattern. + // this way you can have a series of selection criteria + remainingGVKs = matchedGVKs + } + } + + return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingKinds: originalGVKs} +} + +func resourceMatches(pattern schema.GroupVersionResource, resource schema.GroupVersionResource) bool { + if pattern.Group != AnyGroup && pattern.Group != resource.Group { + return false + } + if pattern.Version != AnyVersion && pattern.Version != resource.Version { + return false + } + if pattern.Resource != AnyResource && pattern.Resource != resource.Resource { + return false + } + + return true +} + +func kindMatches(pattern schema.GroupVersionKind, kind schema.GroupVersionKind) bool { + if pattern.Group != AnyGroup && pattern.Group != kind.Group { + return false + } + if pattern.Version != AnyVersion && pattern.Version != kind.Version { + return false + } + if pattern.Kind != AnyKind && pattern.Kind != kind.Kind { + return false + } + + return true +} + +func (m PriorityRESTMapperWithContext) RESTMappingWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) { + mappings, originalErr := m.Delegate.RESTMappingsWithContext(ctx, gk, versions...) + if originalErr != nil && len(mappings) == 0 { + return nil, originalErr + } + + // any versions the user provides take priority + priorities := m.KindPriority + if len(versions) > 0 { + priorities = make([]schema.GroupVersionKind, 0, len(m.KindPriority)+len(versions)) + for _, version := range versions { + gv := schema.GroupVersion{ + Version: version, + Group: gk.Group, + } + priorities = append(priorities, gv.WithKind(AnyKind)) + } + priorities = append(priorities, m.KindPriority...) + } + + remaining := append([]*RESTMapping{}, mappings...) + for _, pattern := range priorities { + var matching []*RESTMapping + for _, m := range remaining { + if kindMatches(pattern, m.GroupVersionKind) { + matching = append(matching, m) + } + } + + switch len(matching) { + case 0: + // if you have no matches, then nothing matched this pattern just move to the next + continue + case 1: + // one match, return + return matching[0], originalErr + default: + // more than one match, use the matched hits as the list moving to the next pattern. + // this way you can have a series of selection criteria + remaining = matching + } + } + if len(remaining) == 1 { + return remaining[0], originalErr + } + + var kinds []schema.GroupVersionKind + for _, m := range mappings { + kinds = append(kinds, m.GroupVersionKind) + } + return nil, &AmbiguousKindError{PartialKind: gk.WithVersion(""), MatchingKinds: kinds} +} + +func (m PriorityRESTMapperWithContext) RESTMappingsWithContext(ctx context.Context, gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + return m.Delegate.RESTMappingsWithContext(ctx, gk, versions...) +} + +func (m PriorityRESTMapperWithContext) ResourceSingularizerWithContext(ctx context.Context, resource string) (singular string, err error) { + return m.Delegate.ResourceSingularizerWithContext(ctx, resource) +} + +func (m PriorityRESTMapperWithContext) ResourcesForWithContext(ctx context.Context, partiallySpecifiedResource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + return m.Delegate.ResourcesForWithContext(ctx, partiallySpecifiedResource) +} + +func (m PriorityRESTMapperWithContext) KindsForWithContext(ctx context.Context, partiallySpecifiedResource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) { + return m.Delegate.KindsForWithContext(ctx, partiallySpecifiedResource) +} + +func (m PriorityRESTMapperWithContext) ResetWithContext(ctx context.Context) { + MaybeResetRESTMapperWithContext(ctx, m.Delegate) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/priority_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/priority_test.go new file mode 100644 index 0000000000..fff1afd172 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/priority_test.go @@ -0,0 +1,409 @@ +/* +Copyright 2016 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 meta + +import ( + "errors" + "reflect" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestPriorityRESTMapperResourceForErrorHandling(t *testing.T) { + tcs := []struct { + name string + + delegate RESTMapper + resourcePatterns []schema.GroupVersionResource + result schema.GroupVersionResource + err string + }{ + { + name: "error", + delegate: fixedRESTMapper{err: errors.New("delegateError")}, + err: "delegateError", + }, + { + name: "single hit + error", + delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "single-hit"}}, err: errors.New("delegateError")}, + result: schema.GroupVersionResource{Resource: "single-hit"}, + err: "delegateError", + }, + { + name: "group selection + error", + delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + }, err: errors.New("delegateError")}, + resourcePatterns: []schema.GroupVersionResource{ + {Group: "one", Version: AnyVersion, Resource: AnyResource}, + }, + result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, + err: "delegateError", + }, + + { + name: "single hit", + delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{{Resource: "single-hit"}}}, + result: schema.GroupVersionResource{Resource: "single-hit"}, + }, + { + name: "ambiguous match", + delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + }}, + err: "matches multiple resources", + }, + { + name: "group selection", + delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + }}, + resourcePatterns: []schema.GroupVersionResource{ + {Group: "one", Version: AnyVersion, Resource: AnyResource}, + }, + result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, + }, + { + name: "empty match continues", + delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + }}, + resourcePatterns: []schema.GroupVersionResource{ + {Group: "fail", Version: AnyVersion, Resource: AnyResource}, + {Group: "one", Version: AnyVersion, Resource: AnyResource}, + }, + result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, + }, + { + name: "group followed by version selection", + delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + {Group: "one", Version: "c", Resource: "third"}, + }}, + resourcePatterns: []schema.GroupVersionResource{ + {Group: "one", Version: AnyVersion, Resource: AnyResource}, + {Group: AnyGroup, Version: "a", Resource: AnyResource}, + }, + result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, + }, + { + name: "resource selection", + delegate: fixedRESTMapper{resourcesFor: []schema.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "one", Version: "a", Resource: "second"}, + }}, + resourcePatterns: []schema.GroupVersionResource{ + {Group: AnyGroup, Version: AnyVersion, Resource: "second"}, + }, + result: schema.GroupVersionResource{Group: "one", Version: "a", Resource: "second"}, + }, + } + + for _, tc := range tcs { + mapper := PriorityRESTMapper{Delegate: tc.delegate, ResourcePriority: tc.resourcePatterns} + + actualResult, actualErr := mapper.ResourceFor(schema.GroupVersionResource{}) + if e, a := tc.result, actualResult; e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + if len(tc.err) == 0 && actualErr == nil { + continue + } + if len(tc.err) == 0 && actualErr != nil { + t.Errorf("%s: unexpected err: %v", tc.name, actualErr) + continue + } + if len(tc.err) > 0 && actualErr == nil { + t.Errorf("%s: missing expected err: %v", tc.name, tc.err) + continue + } + if !strings.Contains(actualErr.Error(), tc.err) { + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +func TestPriorityRESTMapperKindForErrorHandling(t *testing.T) { + tcs := []struct { + name string + + delegate RESTMapper + kindPatterns []schema.GroupVersionKind + result schema.GroupVersionKind + err string + }{ + { + name: "error", + delegate: fixedRESTMapper{err: errors.New("delegateErr")}, + err: "delegateErr", + }, + { + name: "single hit + error", + delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "single-hit"}}, err: errors.New("delegateErr")}, + result: schema.GroupVersionKind{Kind: "single-hit"}, + err: "delegateErr", + }, + { + name: "group selection + error", + delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + }, err: errors.New("delegateErr")}, + kindPatterns: []schema.GroupVersionKind{ + {Group: "one", Version: AnyVersion, Kind: AnyKind}, + }, + result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, + err: "delegateErr", + }, + + { + name: "single hit", + delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{{Kind: "single-hit"}}}, + result: schema.GroupVersionKind{Kind: "single-hit"}, + }, + { + name: "ambiguous match", + delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + }}, + err: "matches multiple kinds", + }, + { + name: "group selection", + delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + }}, + kindPatterns: []schema.GroupVersionKind{ + {Group: "one", Version: AnyVersion, Kind: AnyKind}, + }, + result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, + }, + { + name: "empty match continues", + delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + }}, + kindPatterns: []schema.GroupVersionKind{ + {Group: "fail", Version: AnyVersion, Kind: AnyKind}, + {Group: "one", Version: AnyVersion, Kind: AnyKind}, + }, + result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, + }, + { + name: "group followed by version selection", + delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + {Group: "one", Version: "c", Kind: "third"}, + }}, + kindPatterns: []schema.GroupVersionKind{ + {Group: "one", Version: AnyVersion, Kind: AnyKind}, + {Group: AnyGroup, Version: "a", Kind: AnyKind}, + }, + result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, + }, + { + name: "kind selection", + delegate: fixedRESTMapper{kindsFor: []schema.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "one", Version: "a", Kind: "second"}, + }}, + kindPatterns: []schema.GroupVersionKind{ + {Group: AnyGroup, Version: AnyVersion, Kind: "second"}, + }, + result: schema.GroupVersionKind{Group: "one", Version: "a", Kind: "second"}, + }, + } + + for _, tc := range tcs { + mapper := PriorityRESTMapper{Delegate: tc.delegate, KindPriority: tc.kindPatterns} + + actualResult, actualErr := mapper.KindFor(schema.GroupVersionResource{}) + if e, a := tc.result, actualResult; e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + if len(tc.err) == 0 && actualErr == nil { + continue + } + if len(tc.err) == 0 && actualErr != nil { + t.Errorf("%s: unexpected err: %v", tc.name, actualErr) + continue + } + if len(tc.err) > 0 && actualErr == nil { + t.Errorf("%s: missing expected err: %v", tc.name, tc.err) + continue + } + if !strings.Contains(actualErr.Error(), tc.err) { + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +func TestPriorityRESTMapperRESTMapping(t *testing.T) { + mapping1 := &RESTMapping{ + GroupVersionKind: schema.GroupVersionKind{Kind: "Foo", Version: "v1alpha1"}, + } + mapping2 := &RESTMapping{ + GroupVersionKind: schema.GroupVersionKind{Kind: "Foo", Version: "v1"}, + } + mapping3 := &RESTMapping{ + GroupVersionKind: schema.GroupVersionKind{Group: "other", Kind: "Foo", Version: "v1"}, + } + allMappers := MultiRESTMapper{ + fixedRESTMapper{mappings: []*RESTMapping{mapping1}}, + fixedRESTMapper{mappings: []*RESTMapping{mapping2}}, + fixedRESTMapper{mappings: []*RESTMapping{mapping3}}, + } + tcs := []struct { + name string + + mapper PriorityRESTMapper + input schema.GroupKind + result *RESTMapping + err error + }{ + { + name: "empty", + mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{}}, + input: schema.GroupKind{Kind: "Foo"}, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, + }, + { + name: "ignore not found", + mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{fixedRESTMapper{err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "IGNORE_THIS"}}}}}, + input: schema.GroupKind{Kind: "Foo"}, + err: &NoKindMatchError{GroupKind: schema.GroupKind{Kind: "Foo"}}, + }, + { + name: "accept first failure", + mapper: PriorityRESTMapper{Delegate: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{mappings: []*RESTMapping{mapping1}}}}, + input: schema.GroupKind{Kind: "Foo"}, + err: errors.New("fail on this"), + }, + { + name: "result + error", + mapper: PriorityRESTMapper{Delegate: fixedRESTMapper{mappings: []*RESTMapping{mapping1}, err: errors.New("fail on this")}}, + input: schema.GroupKind{Kind: "Foo"}, + result: mapping1, + err: errors.New("fail on this"), + }, + { + name: "return error for ambiguous", + mapper: PriorityRESTMapper{ + Delegate: allMappers, + }, + input: schema.GroupKind{Kind: "Foo"}, + err: &AmbiguousKindError{ + PartialKind: schema.GroupVersionKind{Kind: "Foo"}, + MatchingKinds: []schema.GroupVersionKind{ + {Kind: "Foo", Version: "v1alpha1"}, + {Kind: "Foo", Version: "v1"}, + {Group: "other", Kind: "Foo", Version: "v1"}, + }, + }, + }, + { + name: "accept only item", + mapper: PriorityRESTMapper{ + Delegate: fixedRESTMapper{mappings: []*RESTMapping{mapping1}}, + }, + input: schema.GroupKind{Kind: "Foo"}, + result: mapping1, + }, + { + name: "return single priority", + mapper: PriorityRESTMapper{ + Delegate: allMappers, + KindPriority: []schema.GroupVersionKind{{Version: "v1", Kind: AnyKind}, {Version: "v1alpha1", Kind: AnyKind}}, + }, + input: schema.GroupKind{Kind: "Foo"}, + result: mapping2, + }, + { + name: "return out of group match", + mapper: PriorityRESTMapper{ + Delegate: allMappers, + KindPriority: []schema.GroupVersionKind{{Group: AnyGroup, Version: "v1", Kind: AnyKind}, {Group: "other", Version: AnyVersion, Kind: AnyKind}}, + }, + input: schema.GroupKind{Kind: "Foo"}, + result: mapping3, + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.RESTMapping(tc.input) + if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +func TestPriorityRESTMapperRESTMappingHonorsUserVersion(t *testing.T) { + mappingV2alpha1 := &RESTMapping{ + GroupVersionKind: schema.GroupVersionKind{Group: "Bar", Kind: "Foo", Version: "v2alpha1"}, + } + mappingV1 := &RESTMapping{ + GroupVersionKind: schema.GroupVersionKind{Group: "Bar", Kind: "Foo", Version: "v1"}, + } + + allMappers := MultiRESTMapper{ + fixedRESTMapper{mappings: []*RESTMapping{mappingV2alpha1}}, + fixedRESTMapper{mappings: []*RESTMapping{mappingV1}}, + } + + mapper := PriorityRESTMapper{ + Delegate: allMappers, + KindPriority: []schema.GroupVersionKind{{Group: "Bar", Version: "v2alpha1", Kind: AnyKind}, {Group: "Bar", Version: AnyVersion, Kind: AnyKind}}, + } + + outMapping1, err := mapper.RESTMapping(schema.GroupKind{Group: "Bar", Kind: "Foo"}, "v1") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if outMapping1 != mappingV1 { + t.Errorf("asked for version %v, expected mapping for %v, got mapping for %v", "v1", mappingV1.GroupVersionKind, outMapping1.GroupVersionKind) + } + + outMapping2, err := mapper.RESTMapping(schema.GroupKind{Group: "Bar", Kind: "Foo"}, "v2alpha1") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if outMapping2 != mappingV2alpha1 { + t.Errorf("asked for version %v, expected mapping for %v, got mapping for %v", "v2alpha1", mappingV2alpha1.GroupVersionKind, outMapping2.GroupVersionKind) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/restmapper.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/restmapper.go new file mode 100644 index 0000000000..d64db33d41 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/restmapper.go @@ -0,0 +1,579 @@ +/* +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. +*/ + +// TODO: move everything in this file to pkg/api/rest +package meta + +import ( + "context" + "fmt" + "sort" + "strings" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Implements RESTScope interface +type restScope struct { + name RESTScopeName +} + +func (r *restScope) Name() RESTScopeName { + return r.name +} + +var RESTScopeNamespace = &restScope{ + name: RESTScopeNameNamespace, +} + +var RESTScopeRoot = &restScope{ + name: RESTScopeNameRoot, +} + +// DefaultRESTMapper exposes mappings between the types defined in a +// runtime.Scheme. It assumes that all types defined the provided scheme +// can be mapped with the provided MetadataAccessor and Codec interfaces. +// +// The resource name of a Kind is defined as the lowercase, +// English-plural version of the Kind string. +// When converting from resource to Kind, the singular version of the +// resource name is also accepted for convenience. +// +// TODO: Only accept plural for some operations for increased control? +// (`get pod bar` vs `get pods bar`) +type DefaultRESTMapper struct { + defaultGroupVersions []schema.GroupVersion + + resourceToKind map[schema.GroupVersionResource]schema.GroupVersionKind + kindToPluralResource map[schema.GroupVersionKind]schema.GroupVersionResource + kindToScope map[schema.GroupVersionKind]RESTScope + singularToPlural map[schema.GroupVersionResource]schema.GroupVersionResource + pluralToSingular map[schema.GroupVersionResource]schema.GroupVersionResource +} + +func (m *DefaultRESTMapper) String() string { + if m == nil { + return "" + } + return fmt.Sprintf("DefaultRESTMapper{kindToPluralResource=%v}", m.kindToPluralResource) +} + +var _ RESTMapper = &DefaultRESTMapper{} +var _ RESTMapperWithContext = &DefaultRESTMapper{} +var _ fmt.Stringer = &DefaultRESTMapper{} + +// NewDefaultRESTMapper initializes a mapping between Kind and APIVersion +// to a resource name and back based on the objects in a runtime.Scheme +// and the Kubernetes API conventions. Takes a group name, a priority list of the versions +// to search when an object has no default version (set empty to return an error), +// and a function that retrieves the correct metadata for a given version. +func NewDefaultRESTMapper(defaultGroupVersions []schema.GroupVersion) *DefaultRESTMapper { + resourceToKind := make(map[schema.GroupVersionResource]schema.GroupVersionKind) + kindToPluralResource := make(map[schema.GroupVersionKind]schema.GroupVersionResource) + kindToScope := make(map[schema.GroupVersionKind]RESTScope) + singularToPlural := make(map[schema.GroupVersionResource]schema.GroupVersionResource) + pluralToSingular := make(map[schema.GroupVersionResource]schema.GroupVersionResource) + // TODO: verify name mappings work correctly when versions differ + + return &DefaultRESTMapper{ + resourceToKind: resourceToKind, + kindToPluralResource: kindToPluralResource, + kindToScope: kindToScope, + defaultGroupVersions: defaultGroupVersions, + singularToPlural: singularToPlural, + pluralToSingular: pluralToSingular, + } +} + +func (m *DefaultRESTMapper) Add(kind schema.GroupVersionKind, scope RESTScope) { + plural, singular := UnsafeGuessKindToResource(kind) + m.AddSpecific(kind, plural, singular, scope) +} + +func (m *DefaultRESTMapper) AddSpecific(kind schema.GroupVersionKind, plural, singular schema.GroupVersionResource, scope RESTScope) { + m.singularToPlural[singular] = plural + m.pluralToSingular[plural] = singular + + m.resourceToKind[singular] = kind + m.resourceToKind[plural] = kind + + m.kindToPluralResource[kind] = plural + m.kindToScope[kind] = scope +} + +// unpluralizedSuffixes is a list of resource suffixes that are the same plural and singular +// This is only is only necessary because some bits of code are lazy and don't actually use the RESTMapper like they should. +// TODO eliminate this so that different callers can correctly map to resources. This probably means updating all +// callers to use the RESTMapper they mean. +var unpluralizedSuffixes = []string{ + "endpoints", +} + +// UnsafeGuessKindToResource converts Kind to a resource name. +// Broken. This method only "sort of" works when used outside of this package. It assumes that Kinds and Resources match +// and they aren't guaranteed to do so. +func UnsafeGuessKindToResource(kind schema.GroupVersionKind) ( /*plural*/ schema.GroupVersionResource /*singular*/, schema.GroupVersionResource) { + kindName := kind.Kind + if len(kindName) == 0 { + return schema.GroupVersionResource{}, schema.GroupVersionResource{} + } + singularName := strings.ToLower(kindName) + singular := kind.GroupVersion().WithResource(singularName) + + for _, skip := range unpluralizedSuffixes { + if strings.HasSuffix(singularName, skip) { + return singular, singular + } + } + + switch string(singularName[len(singularName)-1]) { + case "s": + return kind.GroupVersion().WithResource(singularName + "es"), singular + case "y": + return kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies"), singular + } + + return kind.GroupVersion().WithResource(singularName + "s"), singular +} + +// ResourceSingularizer implements RESTMapper +// It converts a resource name from plural to singular (e.g., from pods to pod) +func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) { + partialResource := schema.GroupVersionResource{Resource: resourceType} + resources, err := m.ResourcesFor(partialResource) + if err != nil { + return resourceType, err + } + + singular := schema.GroupVersionResource{} + for _, curr := range resources { + currSingular, ok := m.pluralToSingular[curr] + if !ok { + continue + } + if singular.Empty() { + singular = currSingular + continue + } + + if currSingular.Resource != singular.Resource { + return resourceType, fmt.Errorf("multiple possible singular resources (%v) found for %v", resources, resourceType) + } + } + + if singular.Empty() { + return resourceType, fmt.Errorf("no singular of resource %v has been defined", resourceType) + } + + return singular.Resource, nil +} + +func (m *DefaultRESTMapper) ResourceSingularizerWithContext(_ context.Context, resourceType string) (string, error) { + return m.ResourceSingularizer(resourceType) +} + +// coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior) +func coerceResourceForMatching(resource schema.GroupVersionResource) schema.GroupVersionResource { + resource.Resource = strings.ToLower(resource.Resource) + if resource.Version == runtime.APIVersionInternal { + resource.Version = "" + } + + return resource +} + +func (m *DefaultRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + resource := coerceResourceForMatching(input) + + hasResource := len(resource.Resource) > 0 + hasGroup := len(resource.Group) > 0 + hasVersion := len(resource.Version) > 0 + + if !hasResource { + return nil, fmt.Errorf("a resource must be present, got: %v", resource) + } + + ret := []schema.GroupVersionResource{} + switch { + case hasGroup && hasVersion: + // fully qualified. Find the exact match + for plural, singular := range m.pluralToSingular { + if singular == resource { + ret = append(ret, plural) + break + } + if plural == resource { + ret = append(ret, plural) + break + } + } + + case hasGroup: + // given a group, prefer an exact match. If you don't find one, resort to a prefix match on group + foundExactMatch := false + requestedGroupResource := resource.GroupResource() + for plural, singular := range m.pluralToSingular { + if singular.GroupResource() == requestedGroupResource { + foundExactMatch = true + ret = append(ret, plural) + } + if plural.GroupResource() == requestedGroupResource { + foundExactMatch = true + ret = append(ret, plural) + } + } + + // if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match + // storageclass.storage.k8s.io + if !foundExactMatch { + for plural, singular := range m.pluralToSingular { + if !strings.HasPrefix(plural.Group, requestedGroupResource.Group) { + continue + } + if singular.Resource == requestedGroupResource.Resource { + ret = append(ret, plural) + } + if plural.Resource == requestedGroupResource.Resource { + ret = append(ret, plural) + } + } + + } + + case hasVersion: + for plural, singular := range m.pluralToSingular { + if singular.Version == resource.Version && singular.Resource == resource.Resource { + ret = append(ret, plural) + } + if plural.Version == resource.Version && plural.Resource == resource.Resource { + ret = append(ret, plural) + } + } + + default: + for plural, singular := range m.pluralToSingular { + if singular.Resource == resource.Resource { + ret = append(ret, plural) + } + if plural.Resource == resource.Resource { + ret = append(ret, plural) + } + } + } + + if len(ret) == 0 { + return nil, &NoResourceMatchError{PartialResource: resource} + } + + sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions}) + return ret, nil +} + +func (m *DefaultRESTMapper) ResourcesForWithContext(_ context.Context, input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + return m.ResourcesFor(input) +} + +func (m *DefaultRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { + resources, err := m.ResourcesFor(resource) + if err != nil { + return schema.GroupVersionResource{}, err + } + if len(resources) == 1 { + return resources[0], nil + } + + return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources} +} + +func (m *DefaultRESTMapper) ResourceForWithContext(_ context.Context, resource schema.GroupVersionResource) (schema.GroupVersionResource, error) { + return m.ResourceFor(resource) +} + +func (m *DefaultRESTMapper) KindsFor(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { + resource := coerceResourceForMatching(input) + + hasResource := len(resource.Resource) > 0 + hasGroup := len(resource.Group) > 0 + hasVersion := len(resource.Version) > 0 + + if !hasResource { + return nil, fmt.Errorf("a resource must be present, got: %v", resource) + } + + ret := []schema.GroupVersionKind{} + switch { + // fully qualified. Find the exact match + case hasGroup && hasVersion: + kind, exists := m.resourceToKind[resource] + if exists { + ret = append(ret, kind) + } + + case hasGroup: + foundExactMatch := false + requestedGroupResource := resource.GroupResource() + for currResource, currKind := range m.resourceToKind { + if currResource.GroupResource() == requestedGroupResource { + foundExactMatch = true + ret = append(ret, currKind) + } + } + + // if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match + // storageclass.storage.k8s.io + if !foundExactMatch { + for currResource, currKind := range m.resourceToKind { + if !strings.HasPrefix(currResource.Group, requestedGroupResource.Group) { + continue + } + if currResource.Resource == requestedGroupResource.Resource { + ret = append(ret, currKind) + } + } + + } + + case hasVersion: + for currResource, currKind := range m.resourceToKind { + if currResource.Version == resource.Version && currResource.Resource == resource.Resource { + ret = append(ret, currKind) + } + } + + default: + for currResource, currKind := range m.resourceToKind { + if currResource.Resource == resource.Resource { + ret = append(ret, currKind) + } + } + } + + if len(ret) == 0 { + return nil, &NoResourceMatchError{PartialResource: input} + } + + sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions}) + return ret, nil +} + +func (m *DefaultRESTMapper) KindsForWithContext(_ context.Context, input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { + return m.KindsFor(input) +} + +func (m *DefaultRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + kinds, err := m.KindsFor(resource) + if err != nil { + return schema.GroupVersionKind{}, err + } + if len(kinds) == 1 { + return kinds[0], nil + } + + return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds} +} + +func (m *DefaultRESTMapper) KindForWithContext(_ context.Context, resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + return m.KindFor(resource) +} + +type kindByPreferredGroupVersion struct { + list []schema.GroupVersionKind + sortOrder []schema.GroupVersion +} + +func (o kindByPreferredGroupVersion) Len() int { return len(o.list) } +func (o kindByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] } +func (o kindByPreferredGroupVersion) Less(i, j int) bool { + lhs := o.list[i] + rhs := o.list[j] + if lhs == rhs { + return false + } + + if lhs.GroupVersion() == rhs.GroupVersion() { + return lhs.Kind < rhs.Kind + } + + // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order + lhsIndex := -1 + rhsIndex := -1 + + for i := range o.sortOrder { + if o.sortOrder[i] == lhs.GroupVersion() { + lhsIndex = i + } + if o.sortOrder[i] == rhs.GroupVersion() { + rhsIndex = i + } + } + + if rhsIndex == -1 { + return true + } + + return lhsIndex < rhsIndex +} + +type resourceByPreferredGroupVersion struct { + list []schema.GroupVersionResource + sortOrder []schema.GroupVersion +} + +func (o resourceByPreferredGroupVersion) Len() int { return len(o.list) } +func (o resourceByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] } +func (o resourceByPreferredGroupVersion) Less(i, j int) bool { + lhs := o.list[i] + rhs := o.list[j] + if lhs == rhs { + return false + } + + if lhs.GroupVersion() == rhs.GroupVersion() { + return lhs.Resource < rhs.Resource + } + + // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order + lhsIndex := -1 + rhsIndex := -1 + + for i := range o.sortOrder { + if o.sortOrder[i] == lhs.GroupVersion() { + lhsIndex = i + } + if o.sortOrder[i] == rhs.GroupVersion() { + rhsIndex = i + } + } + + if rhsIndex == -1 { + return true + } + + return lhsIndex < rhsIndex +} + +// RESTMapping returns a struct representing the resource path and conversion interfaces a +// RESTClient should use to operate on the provided group/kind in order of versions. If a version search +// order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which +// version should be used to access the named group/kind. +func (m *DefaultRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) { + mappings, err := m.RESTMappings(gk, versions...) + if err != nil { + return nil, err + } + if len(mappings) == 0 { + return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} + } + // since we rely on RESTMappings method + // take the first match and return to the caller + // as this was the existing behavior. + return mappings[0], nil +} + +func (m *DefaultRESTMapper) RESTMappingWithContext(_ context.Context, gk schema.GroupKind, versions ...string) (*RESTMapping, error) { + return m.RESTMapping(gk, versions...) +} + +// RESTMappings returns the RESTMappings for the provided group kind. If a version search order +// is not provided, the search order provided to DefaultRESTMapper will be used. +func (m *DefaultRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + mappings := make([]*RESTMapping, 0) + potentialGVK := make([]schema.GroupVersionKind, 0) + hadVersion := false + + // Pick an appropriate version + for _, version := range versions { + if len(version) == 0 || version == runtime.APIVersionInternal { + continue + } + currGVK := gk.WithVersion(version) + hadVersion = true + if _, ok := m.kindToPluralResource[currGVK]; ok { + potentialGVK = append(potentialGVK, currGVK) + break + } + } + // Use the default preferred versions + if !hadVersion && len(potentialGVK) == 0 { + for _, gv := range m.defaultGroupVersions { + if gv.Group != gk.Group { + continue + } + potentialGVK = append(potentialGVK, gk.WithVersion(gv.Version)) + } + } + + if len(potentialGVK) == 0 { + return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions} + } + + for _, gvk := range potentialGVK { + //Ensure we have a REST mapping + res, ok := m.kindToPluralResource[gvk] + if !ok { + continue + } + + // Ensure we have a REST scope + scope, ok := m.kindToScope[gvk] + if !ok { + return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion(), gvk.Kind) + } + + mappings = append(mappings, &RESTMapping{ + Resource: res, + GroupVersionKind: gvk, + Scope: scope, + }) + } + + if len(mappings) == 0 { + return nil, &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}} + } + return mappings, nil +} + +func (m *DefaultRESTMapper) RESTMappingsWithContext(_ context.Context, gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) { + return m.RESTMappings(gk, versions...) +} + +// MaybeResetRESTMapper calls Reset() on the mapper if it is a ResettableRESTMapper or +// ResetWithContext() if it is a ResettableRESTMapperWithContext. +// +// MaybeResetRESTMapperWithContext is a better alternative because it supports contextual logging and cancellation. +// +// Contextual logging: Use MaybeResetRESTMapperWithContext instead. +func MaybeResetRESTMapper(mapper RESTMapper) { + maybeReset(context.Background(), mapper) +} + +// MaybeResetRESTMapperWithContext calls Reset() on the mapper if it is a ResettableRESTMapper or +// ResetWithContext() if it is a ResettableRESTMapperWithContext. +func MaybeResetRESTMapperWithContext(ctx context.Context, mapper RESTMapperWithContext) { + maybeReset(ctx, mapper) +} + +func maybeReset(ctx context.Context, mapper any) { + if m, ok := mapper.(ResettableRESTMapperWithContext); ok { + m.ResetWithContext(ctx) + return + } + if m, ok := mapper.(ResettableRESTMapper); ok { + m.Reset() + return + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/restmapper_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/restmapper_test.go new file mode 100644 index 0000000000..f168f033c7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/restmapper_test.go @@ -0,0 +1,724 @@ +/* +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 meta + +import ( + "reflect" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestRESTMapperVersionAndKindForResource(t *testing.T) { + testGroup := "test.group" + testVersion := "test" + testGroupVersion := schema.GroupVersion{Group: testGroup, Version: testVersion} + + testCases := []struct { + Resource schema.GroupVersionResource + GroupVersionToRegister schema.GroupVersion + ExpectedGVK schema.GroupVersionKind + Err bool + }{ + {Resource: schema.GroupVersionResource{Resource: "internalobjec"}, Err: true}, + {Resource: schema.GroupVersionResource{Resource: "internalObjec"}, Err: true}, + + {Resource: schema.GroupVersionResource{Resource: "internalobject"}, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, + {Resource: schema.GroupVersionResource{Resource: "internalobjects"}, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, + } + for i, testCase := range testCases { + mapper := NewDefaultRESTMapper([]schema.GroupVersion{testGroupVersion}) + if len(testCase.ExpectedGVK.Kind) != 0 { + mapper.Add(testCase.ExpectedGVK, RESTScopeNamespace) + } + actualGVK, err := mapper.KindFor(testCase.Resource) + + hasErr := err != nil + if hasErr != testCase.Err { + t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) + continue + } + if err != nil { + continue + } + + if actualGVK != testCase.ExpectedGVK { + t.Errorf("%d: unexpected version and kind: e=%s a=%s", i, testCase.ExpectedGVK, actualGVK) + } + } +} + +func TestRESTMapperGroupForResource(t *testing.T) { + testCases := []struct { + Resource schema.GroupVersionResource + GroupVersionKind schema.GroupVersionKind + Err bool + }{ + {Resource: schema.GroupVersionResource{Resource: "myObject"}, GroupVersionKind: schema.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, + {Resource: schema.GroupVersionResource{Resource: "myobject"}, GroupVersionKind: schema.GroupVersionKind{Group: "testapi2", Version: "test", Kind: "MyObject"}}, + {Resource: schema.GroupVersionResource{Resource: "myObje"}, Err: true, GroupVersionKind: schema.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, + {Resource: schema.GroupVersionResource{Resource: "myobje"}, Err: true, GroupVersionKind: schema.GroupVersionKind{Group: "testapi", Version: "test", Kind: "MyObject"}}, + } + for i, testCase := range testCases { + mapper := NewDefaultRESTMapper([]schema.GroupVersion{testCase.GroupVersionKind.GroupVersion()}) + mapper.Add(testCase.GroupVersionKind, RESTScopeNamespace) + + actualGVK, err := mapper.KindFor(testCase.Resource) + if testCase.Err { + if err == nil { + t.Errorf("%d: expected error", i) + } + } else if err != nil { + t.Errorf("%d: unexpected error: %v", i, err) + } else if actualGVK != testCase.GroupVersionKind { + t.Errorf("%d: expected group %q, got %q", i, testCase.GroupVersionKind, actualGVK) + } + } +} + +func TestRESTMapperKindsFor(t *testing.T) { + testCases := []struct { + Name string + PreferredOrder []schema.GroupVersion + KindsToRegister []schema.GroupVersionKind + PartialResourceToRequest schema.GroupVersionResource + + ExpectedKinds []schema.GroupVersionKind + ExpectedKindErr string + }{ + { + // exact matches are preferred + Name: "groups, with group exact", + PreferredOrder: []schema.GroupVersion{ + {Group: "first-group-1", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + PartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kind"}, + + ExpectedKinds: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + }, + + { + // group prefixes work + Name: "groups, with group prefix", + PreferredOrder: []schema.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + }, + PartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kind"}, + + ExpectedKinds: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + }, + + { + // group prefixes can be ambiguous + Name: "groups, with ambiguous group prefix", + PreferredOrder: []schema.GroupVersion{ + {Group: "first-group-1", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + PartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kind"}, + + ExpectedKinds: []schema.GroupVersionKind{ + {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + ExpectedKindErr: " matches multiple kinds ", + }, + + { + Name: "ambiguous groups, with preference order", + PreferredOrder: []schema.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: schema.GroupVersionResource{Resource: "my-kinds"}, + + ExpectedKinds: []schema.GroupVersionKind{ + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + ExpectedKindErr: " matches multiple kinds ", + }, + + { + Name: "ambiguous groups, with explicit group match", + PreferredOrder: []schema.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kinds"}, + + ExpectedKinds: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + }, + + { + Name: "ambiguous groups, with ambiguous version match", + PreferredOrder: []schema.GroupVersion{ + {Group: "first-group", Version: "first-version"}, + {Group: "second-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PartialResourceToRequest: schema.GroupVersionResource{Version: "first-version", Resource: "my-kinds"}, + + ExpectedKinds: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + }, + ExpectedKindErr: " matches multiple kinds ", + }, + } + for _, testCase := range testCases { + tcName := testCase.Name + mapper := NewDefaultRESTMapper(testCase.PreferredOrder) + for _, kind := range testCase.KindsToRegister { + mapper.Add(kind, RESTScopeNamespace) + } + + actualKinds, err := mapper.KindsFor(testCase.PartialResourceToRequest) + if err != nil { + t.Errorf("%s: unexpected error: %v", tcName, err) + continue + } + if !reflect.DeepEqual(testCase.ExpectedKinds, actualKinds) { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKinds, actualKinds) + } + + singleKind, err := mapper.KindFor(testCase.PartialResourceToRequest) + if err == nil && len(testCase.ExpectedKindErr) != 0 { + t.Errorf("%s: expected error: %v", tcName, testCase.ExpectedKindErr) + continue + } + if err != nil { + if len(testCase.ExpectedKindErr) == 0 { + t.Errorf("%s: unexpected error: %v", tcName, err) + continue + } else { + if !strings.Contains(err.Error(), testCase.ExpectedKindErr) { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKindErr, err) + continue + } + } + + } else { + if testCase.ExpectedKinds[0] != singleKind { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedKinds[0], singleKind) + } + + } + } +} + +func TestRESTMapperResourcesFor(t *testing.T) { + testCases := []struct { + Name string + PreferredOrder []schema.GroupVersion + KindsToRegister []schema.GroupVersionKind + PluralPartialResourceToRequest schema.GroupVersionResource + SingularPartialResourceToRequest schema.GroupVersionResource + + ExpectedResources []schema.GroupVersionResource + ExpectedResourceErr string + }{ + { + // exact matches are preferred + Name: "groups, with group exact", + PreferredOrder: []schema.GroupVersion{ + {Group: "first-group-1", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + PluralPartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kinds"}, + SingularPartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kind"}, + + ExpectedResources: []schema.GroupVersionResource{ + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + }, + }, + + { + // group prefixes work + Name: "groups, with group prefix", + PreferredOrder: []schema.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + }, + PluralPartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kinds"}, + SingularPartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kind"}, + + ExpectedResources: []schema.GroupVersionResource{ + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + }, + }, + + { + // group prefixes can be ambiguous + Name: "groups, with ambiguous group prefix", + PreferredOrder: []schema.GroupVersion{ + {Group: "first-group-1", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group-1", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + }, + PluralPartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kinds"}, + SingularPartialResourceToRequest: schema.GroupVersionResource{Group: "first", Resource: "my-kind"}, + + ExpectedResources: []schema.GroupVersionResource{ + {Group: "first-group-1", Version: "first-version", Resource: "my-kinds"}, + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + }, + ExpectedResourceErr: " matches multiple resources ", + }, + + { + Name: "ambiguous groups, with preference order", + PreferredOrder: []schema.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PluralPartialResourceToRequest: schema.GroupVersionResource{Resource: "my-kinds"}, + SingularPartialResourceToRequest: schema.GroupVersionResource{Resource: "my-kind"}, + + ExpectedResources: []schema.GroupVersionResource{ + {Group: "second-group", Version: "first-version", Resource: "my-kinds"}, + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + }, + ExpectedResourceErr: " matches multiple resources ", + }, + + { + Name: "ambiguous groups, with explicit group match", + PreferredOrder: []schema.GroupVersion{ + {Group: "second-group", Version: "first-version"}, + {Group: "first-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PluralPartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kinds"}, + SingularPartialResourceToRequest: schema.GroupVersionResource{Group: "first-group", Resource: "my-kind"}, + + ExpectedResources: []schema.GroupVersionResource{ + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + }, + }, + + { + Name: "ambiguous groups, with ambiguous version match", + PreferredOrder: []schema.GroupVersion{ + {Group: "first-group", Version: "first-version"}, + {Group: "second-group", Version: "first-version"}, + }, + KindsToRegister: []schema.GroupVersionKind{ + {Group: "first-group", Version: "first-version", Kind: "my-kind"}, + {Group: "first-group", Version: "first-version", Kind: "your-kind"}, + {Group: "second-group", Version: "first-version", Kind: "my-kind"}, + {Group: "second-group", Version: "first-version", Kind: "your-kind"}, + }, + PluralPartialResourceToRequest: schema.GroupVersionResource{Version: "first-version", Resource: "my-kinds"}, + SingularPartialResourceToRequest: schema.GroupVersionResource{Version: "first-version", Resource: "my-kind"}, + + ExpectedResources: []schema.GroupVersionResource{ + {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, + {Group: "second-group", Version: "first-version", Resource: "my-kinds"}, + }, + ExpectedResourceErr: " matches multiple resources ", + }, + } + for _, testCase := range testCases { + tcName := testCase.Name + + for _, partialResource := range []schema.GroupVersionResource{testCase.PluralPartialResourceToRequest, testCase.SingularPartialResourceToRequest} { + mapper := NewDefaultRESTMapper(testCase.PreferredOrder) + for _, kind := range testCase.KindsToRegister { + mapper.Add(kind, RESTScopeNamespace) + } + + actualResources, err := mapper.ResourcesFor(partialResource) + if err != nil { + t.Errorf("%s: unexpected error: %v", tcName, err) + continue + } + if !reflect.DeepEqual(testCase.ExpectedResources, actualResources) { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResources, actualResources) + } + + singleResource, err := mapper.ResourceFor(partialResource) + if err == nil && len(testCase.ExpectedResourceErr) != 0 { + t.Errorf("%s: expected error: %v", tcName, testCase.ExpectedResourceErr) + continue + } + if err != nil { + if len(testCase.ExpectedResourceErr) == 0 { + t.Errorf("%s: unexpected error: %v", tcName, err) + continue + } else { + if !strings.Contains(err.Error(), testCase.ExpectedResourceErr) { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResourceErr, err) + continue + } + } + + } else { + if testCase.ExpectedResources[0] != singleResource { + t.Errorf("%s: expected %v, got %v", tcName, testCase.ExpectedResources[0], singleResource) + } + + } + } + } +} + +func TestKindToResource(t *testing.T) { + testCases := []struct { + Kind string + Plural, Singular string + }{ + {Kind: "Pod", Plural: "pods", Singular: "pod"}, + + {Kind: "ReplicationController", Plural: "replicationcontrollers", Singular: "replicationcontroller"}, + + // Add "ies" when ending with "y" + {Kind: "ImageRepository", Plural: "imagerepositories", Singular: "imagerepository"}, + // Add "es" when ending with "s" + {Kind: "miss", Plural: "misses", Singular: "miss"}, + // Add "s" otherwise + {Kind: "lowercase", Plural: "lowercases", Singular: "lowercase"}, + } + for i, testCase := range testCases { + version := schema.GroupVersion{} + + plural, singular := UnsafeGuessKindToResource(version.WithKind(testCase.Kind)) + if singular != version.WithResource(testCase.Singular) || plural != version.WithResource(testCase.Plural) { + t.Errorf("%d: unexpected plural and singular: %v %v", i, plural, singular) + } + } +} + +func TestRESTMapperResourceSingularizer(t *testing.T) { + testGroupVersion := schema.GroupVersion{Group: "tgroup", Version: "test"} + + testCases := []struct { + Kind string + Plural string + Singular string + }{ + {Kind: "Pod", Plural: "pods", Singular: "pod"}, + {Kind: "ReplicationController", Plural: "replicationcontrollers", Singular: "replicationcontroller"}, + {Kind: "ImageRepository", Plural: "imagerepositories", Singular: "imagerepository"}, + {Kind: "Status", Plural: "statuses", Singular: "status"}, + + {Kind: "lowercase", Plural: "lowercases", Singular: "lowercase"}, + // TODO this test is broken. This updates to reflect actual behavior. Kinds are expected to be singular + // old (incorrect), comment: Don't add extra s if the original object is already plural + {Kind: "lowercases", Plural: "lowercaseses", Singular: "lowercases"}, + } + for i, testCase := range testCases { + mapper := NewDefaultRESTMapper([]schema.GroupVersion{testGroupVersion}) + // create singular/plural mapping + mapper.Add(testGroupVersion.WithKind(testCase.Kind), RESTScopeNamespace) + + singular, err := mapper.ResourceSingularizer(testCase.Plural) + if err != nil { + t.Errorf("%d: unexpected error: %v", i, err) + } + if singular != testCase.Singular { + t.Errorf("%d: mismatched singular: got %v, expected %v", i, singular, testCase.Singular) + } + } +} + +func TestRESTMapperRESTMapping(t *testing.T) { + testGroup := "tgroup" + testGroupVersion := schema.GroupVersion{Group: testGroup, Version: "test"} + internalGroupVersion := schema.GroupVersion{Group: testGroup, Version: "test"} + + testCases := []struct { + Kind string + APIGroupVersions []schema.GroupVersion + DefaultVersions []schema.GroupVersion + + Resource schema.GroupVersionResource + ExpectedGroupVersion *schema.GroupVersion + Err bool + }{ + {Kind: "Unknown", Err: true}, + {Kind: "InternalObject", Err: true}, + + {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "Unknown", Err: true}, + + {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: testGroupVersion.WithResource("internalobjects")}, + {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: testGroupVersion.WithResource("internalobjects")}, + + {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: testGroupVersion.WithResource("internalobjects")}, + + {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{}, Resource: internalGroupVersion.WithResource("internalobjects"), ExpectedGroupVersion: &schema.GroupVersion{Group: testGroup, Version: "test"}}, + + {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "test"}}, Resource: testGroupVersion.WithResource("internalobjects")}, + + // TODO: add test for a resource that exists in one version but not another + } + for i, testCase := range testCases { + mapper := NewDefaultRESTMapper(testCase.DefaultVersions) + mapper.Add(internalGroupVersion.WithKind("InternalObject"), RESTScopeNamespace) + + preferredVersions := []string{} + for _, gv := range testCase.APIGroupVersions { + preferredVersions = append(preferredVersions, gv.Version) + } + gk := schema.GroupKind{Group: testGroup, Kind: testCase.Kind} + + mapping, err := mapper.RESTMapping(gk, preferredVersions...) + hasErr := err != nil + if hasErr != testCase.Err { + t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) + } + if hasErr { + continue + } + if mapping.Resource != testCase.Resource { + t.Errorf("%d: unexpected resource: %#v", i, mapping) + } + + groupVersion := testCase.ExpectedGroupVersion + if groupVersion == nil { + groupVersion = &testCase.APIGroupVersions[0] + } + if mapping.GroupVersionKind.GroupVersion() != *groupVersion { + t.Errorf("%d: unexpected version: %#v", i, mapping) + } + + } +} + +func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { + expectedGroupVersion1 := schema.GroupVersion{Group: "tgroup", Version: "test1"} + expectedGroupVersion2 := schema.GroupVersion{Group: "tgroup", Version: "test2"} + expectedGroupVersion3 := schema.GroupVersion{Group: "tgroup", Version: "test3"} + internalObjectGK := schema.GroupKind{Group: "tgroup", Kind: "InternalObject"} + otherObjectGK := schema.GroupKind{Group: "tgroup", Kind: "OtherObject"} + + mapper := NewDefaultRESTMapper([]schema.GroupVersion{expectedGroupVersion1, expectedGroupVersion2}) + mapper.Add(expectedGroupVersion1.WithKind("InternalObject"), RESTScopeNamespace) + mapper.Add(expectedGroupVersion2.WithKind("OtherObject"), RESTScopeNamespace) + + // pick default matching object kind based on search order + mapping, err := mapper.RESTMapping(otherObjectGK) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if mapping.Resource != expectedGroupVersion2.WithResource("otherobjects") || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 { + t.Errorf("unexpected mapping: %#v", mapping) + } + + mapping, err = mapper.RESTMapping(internalObjectGK) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if mapping.Resource != expectedGroupVersion1.WithResource("internalobjects") || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion1 { + t.Errorf("unexpected mapping: %#v", mapping) + } + + // mismatch of version + _, err = mapper.RESTMapping(internalObjectGK, expectedGroupVersion2.Version) + if err == nil { + t.Errorf("unexpected non-error") + } + _, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion1.Version) + if err == nil { + t.Errorf("unexpected non-error") + } + + // not in the search versions + _, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version) + if err == nil { + t.Errorf("unexpected non-error") + } + + // explicit search order + _, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version, expectedGroupVersion1.Version) + if err == nil { + t.Errorf("unexpected non-error") + } + + mapping, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version, expectedGroupVersion2.Version) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if mapping.Resource != expectedGroupVersion2.WithResource("otherobjects") || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 { + t.Errorf("unexpected mapping: %#v", mapping) + } +} + +func TestRESTMapperRESTMappings(t *testing.T) { + testGroup := "tgroup" + testGroupVersion := schema.GroupVersion{Group: testGroup, Version: "v1"} + + testCases := []struct { + Kind string + APIGroupVersions []schema.GroupVersion + DefaultVersions []schema.GroupVersion + AddGroupVersionKind []schema.GroupVersionKind + + ExpectedRESTMappings []*RESTMapping + Err bool + }{ + {Kind: "Unknown", Err: true}, + {Kind: "InternalObject", Err: true}, + + {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "Unknown", Err: true}, + + // ask for specific version - not available - thus error + {DefaultVersions: []schema.GroupVersion{testGroupVersion}, Kind: "InternalObject", APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "v2"}}, Err: true}, + + // ask for specific version - available - check ExpectedRESTMappings + { + DefaultVersions: []schema.GroupVersion{testGroupVersion}, + Kind: "InternalObject", + APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "v2"}}, + AddGroupVersionKind: []schema.GroupVersionKind{schema.GroupVersion{Group: testGroup, Version: "v2"}.WithKind("InternalObject")}, + ExpectedRESTMappings: []*RESTMapping{{Resource: schema.GroupVersionResource{Group: testGroup, Version: "v2", Resource: "internalobjects"}, GroupVersionKind: schema.GroupVersionKind{Group: testGroup, Version: "v2", Kind: "InternalObject"}}}, + }, + + // ask for specific versions - only one available - check ExpectedRESTMappings + { + DefaultVersions: []schema.GroupVersion{testGroupVersion}, + Kind: "InternalObject", + APIGroupVersions: []schema.GroupVersion{{Group: testGroup, Version: "v3"}, {Group: testGroup, Version: "v2"}}, + AddGroupVersionKind: []schema.GroupVersionKind{schema.GroupVersion{Group: testGroup, Version: "v2"}.WithKind("InternalObject")}, + ExpectedRESTMappings: []*RESTMapping{{Resource: schema.GroupVersionResource{Group: testGroup, Version: "v2", Resource: "internalobjects"}, GroupVersionKind: schema.GroupVersionKind{Group: testGroup, Version: "v2", Kind: "InternalObject"}}}, + }, + + // do not ask for specific version - search through default versions - check ExpectedRESTMappings + { + DefaultVersions: []schema.GroupVersion{testGroupVersion, {Group: testGroup, Version: "v2"}}, + Kind: "InternalObject", + AddGroupVersionKind: []schema.GroupVersionKind{schema.GroupVersion{Group: testGroup, Version: "v1"}.WithKind("InternalObject"), schema.GroupVersion{Group: testGroup, Version: "v2"}.WithKind("InternalObject")}, + ExpectedRESTMappings: []*RESTMapping{ + { + Resource: schema.GroupVersionResource{Group: testGroup, Version: "v1", Resource: "internalobjects"}, + GroupVersionKind: schema.GroupVersionKind{Group: testGroup, Version: "v1", Kind: "InternalObject"}, + }, + { + Resource: schema.GroupVersionResource{Group: testGroup, Version: "v2", Resource: "internalobjects"}, + GroupVersionKind: schema.GroupVersionKind{Group: testGroup, Version: "v2", Kind: "InternalObject"}, + }, + }, + }, + } + + for i, testCase := range testCases { + mapper := NewDefaultRESTMapper(testCase.DefaultVersions) + for _, gvk := range testCase.AddGroupVersionKind { + mapper.Add(gvk, RESTScopeNamespace) + } + + preferredVersions := []string{} + for _, gv := range testCase.APIGroupVersions { + preferredVersions = append(preferredVersions, gv.Version) + } + gk := schema.GroupKind{Group: testGroup, Kind: testCase.Kind} + + mappings, err := mapper.RESTMappings(gk, preferredVersions...) + hasErr := err != nil + if hasErr != testCase.Err { + t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) + } + if hasErr { + continue + } + if len(mappings) != len(testCase.ExpectedRESTMappings) { + t.Errorf("%d: unexpected number = %d of rest mappings was returned, expected = %d", i, len(mappings), len(testCase.ExpectedRESTMappings)) + } + for j, mapping := range mappings { + exp := testCase.ExpectedRESTMappings[j] + if mapping.Resource != exp.Resource { + t.Errorf("%d - %d: unexpected resource: %#v", i, j, mapping) + } + if mapping.GroupVersionKind != exp.GroupVersionKind { + t.Errorf("%d - %d: unexpected GroupVersionKind: %#v", i, j, mapping) + } + } + } +} + +func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) { + expectedGroupVersion1 := schema.GroupVersion{Group: "tgroup", Version: "test1"} + expectedGroupVersion2 := schema.GroupVersion{Group: "tgroup", Version: "test2"} + internalObjectGK := schema.GroupKind{Group: "tgroup", Kind: "InternalObject"} + + mapper := NewDefaultRESTMapper([]schema.GroupVersion{expectedGroupVersion1, expectedGroupVersion2}) + mapper.Add(expectedGroupVersion1.WithKind("InternalObject"), RESTScopeNamespace) + _, err := mapper.RESTMapping(internalObjectGK, "test3") + if err == nil { + t.Errorf("unexpected non-error") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/table/table.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/table/table.go new file mode 100644 index 0000000000..1887f32626 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/table/table.go @@ -0,0 +1,70 @@ +/* +Copyright 2018 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 table + +import ( + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/duration" +) + +// MetaToTableRow converts a list or object into one or more table rows. The provided rowFn is invoked for +// each accessed item, with name and age being passed to each. +func MetaToTableRow(obj runtime.Object, rowFn func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error)) ([]metav1.TableRow, error) { + if meta.IsListType(obj) { + rows := make([]metav1.TableRow, 0, 16) + err := meta.EachListItem(obj, func(obj runtime.Object) error { + nestedRows, err := MetaToTableRow(obj, rowFn) + if err != nil { + return err + } + rows = append(rows, nestedRows...) + return nil + }) + if err != nil { + return nil, err + } + return rows, nil + } + + rows := make([]metav1.TableRow, 0, 1) + m, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + row := metav1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + row.Cells, err = rowFn(obj, m, m.GetName(), ConvertToHumanReadableDateType(m.GetCreationTimestamp())) + if err != nil { + return nil, err + } + rows = append(rows, row) + return rows, nil +} + +// ConvertToHumanReadableDateType returns the elapsed time since timestamp in +// human-readable approximation. +func ConvertToHumanReadableDateType(timestamp metav1.Time) string { + if timestamp.IsZero() { + return "" + } + return duration.HumanDuration(time.Since(timestamp.Time)) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/testrestmapper/test_restmapper.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/testrestmapper/test_restmapper.go new file mode 100644 index 0000000000..72c6438cb6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/meta/testrestmapper/test_restmapper.go @@ -0,0 +1,165 @@ +/* +Copyright 2018 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 testrestmapper + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" +) + +// TestOnlyStaticRESTMapper returns a union RESTMapper of all known types with priorities chosen in the following order: +// 1. legacy kube group preferred version, extensions preferred version, metrics preferred version, legacy +// kube any version, extensions any version, metrics any version, all other groups alphabetical preferred version, +// all other groups alphabetical. +// +// TODO callers of this method should be updated to build their own specific restmapper based on their scheme for their tests +// TODO the things being tested are related to whether various cases are handled, not tied to the particular types being checked. +func TestOnlyStaticRESTMapper(scheme *runtime.Scheme, versionPatterns ...schema.GroupVersion) meta.RESTMapper { + unionMapper := meta.MultiRESTMapper{} + unionedGroups := sets.NewString() + for _, enabledVersion := range scheme.PrioritizedVersionsAllGroups() { + if !unionedGroups.Has(enabledVersion.Group) { + unionedGroups.Insert(enabledVersion.Group) + unionMapper = append(unionMapper, newRESTMapper(enabledVersion.Group, scheme)) + } + } + + if len(versionPatterns) != 0 { + resourcePriority := []schema.GroupVersionResource{} + kindPriority := []schema.GroupVersionKind{} + for _, versionPriority := range versionPatterns { + resourcePriority = append(resourcePriority, versionPriority.WithResource(meta.AnyResource)) + kindPriority = append(kindPriority, versionPriority.WithKind(meta.AnyKind)) + } + + return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} + } + + prioritizedGroups := []string{"", "extensions", "metrics"} + resourcePriority, kindPriority := prioritiesForGroups(scheme, prioritizedGroups...) + + prioritizedGroupsSet := sets.NewString(prioritizedGroups...) + remainingGroups := sets.String{} + for _, enabledVersion := range scheme.PrioritizedVersionsAllGroups() { + if !prioritizedGroupsSet.Has(enabledVersion.Group) { + remainingGroups.Insert(enabledVersion.Group) + } + } + + remainingResourcePriority, remainingKindPriority := prioritiesForGroups(scheme, remainingGroups.List()...) + resourcePriority = append(resourcePriority, remainingResourcePriority...) + kindPriority = append(kindPriority, remainingKindPriority...) + + return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} +} + +// prioritiesForGroups returns the resource and kind priorities for a PriorityRESTMapper, preferring the preferred version of each group first, +// then any non-preferred version of the group second. +func prioritiesForGroups(scheme *runtime.Scheme, groups ...string) ([]schema.GroupVersionResource, []schema.GroupVersionKind) { + resourcePriority := []schema.GroupVersionResource{} + kindPriority := []schema.GroupVersionKind{} + + for _, group := range groups { + availableVersions := scheme.PrioritizedVersionsForGroup(group) + if len(availableVersions) > 0 { + resourcePriority = append(resourcePriority, availableVersions[0].WithResource(meta.AnyResource)) + kindPriority = append(kindPriority, availableVersions[0].WithKind(meta.AnyKind)) + } + } + for _, group := range groups { + resourcePriority = append(resourcePriority, schema.GroupVersionResource{Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource}) + kindPriority = append(kindPriority, schema.GroupVersionKind{Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind}) + } + + return resourcePriority, kindPriority +} + +func newRESTMapper(group string, scheme *runtime.Scheme) meta.RESTMapper { + mapper := meta.NewDefaultRESTMapper(scheme.PrioritizedVersionsForGroup(group)) + for _, gv := range scheme.PrioritizedVersionsForGroup(group) { + for kind := range scheme.KnownTypes(gv) { + if ignoredKinds.Has(kind) { + continue + } + scope := meta.RESTScopeNamespace + if rootScopedKinds[gv.WithKind(kind).GroupKind()] { + scope = meta.RESTScopeRoot + } + mapper.Add(gv.WithKind(kind), scope) + } + } + + return mapper +} + +// hardcoded is good enough for the test we're running +var rootScopedKinds = map[schema.GroupKind]bool{ + {Group: "admission.k8s.io", Kind: "AdmissionReview"}: true, + + {Group: "admissionregistration.k8s.io", Kind: "ValidatingWebhookConfiguration"}: true, + {Group: "admissionregistration.k8s.io", Kind: "MutatingWebhookConfiguration"}: true, + + {Group: "authentication.k8s.io", Kind: "TokenReview"}: true, + + {Group: "authorization.k8s.io", Kind: "SubjectAccessReview"}: true, + {Group: "authorization.k8s.io", Kind: "SelfSubjectAccessReview"}: true, + {Group: "authorization.k8s.io", Kind: "SelfSubjectRulesReview"}: true, + + {Group: "certificates.k8s.io", Kind: "CertificateSigningRequest"}: true, + + {Group: "", Kind: "Node"}: true, + {Group: "", Kind: "Namespace"}: true, + {Group: "", Kind: "PersistentVolume"}: true, + {Group: "", Kind: "ComponentStatus"}: true, + + {Group: "rbac.authorization.k8s.io", Kind: "ClusterRole"}: true, + {Group: "rbac.authorization.k8s.io", Kind: "ClusterRoleBinding"}: true, + + {Group: "scheduling.k8s.io", Kind: "PriorityClass"}: true, + + {Group: "storage.k8s.io", Kind: "StorageClass"}: true, + {Group: "storage.k8s.io", Kind: "VolumeAttachment"}: true, + + {Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}: true, + + {Group: "apiserver.k8s.io", Kind: "AdmissionConfiguration"}: true, + + {Group: "audit.k8s.io", Kind: "Event"}: true, + {Group: "audit.k8s.io", Kind: "Policy"}: true, + + {Group: "apiregistration.k8s.io", Kind: "APIService"}: true, + + {Group: "metrics.k8s.io", Kind: "NodeMetrics"}: true, + + {Group: "wardle.example.com", Kind: "Fischer"}: true, +} + +// hardcoded is good enough for the test we're running +var ignoredKinds = sets.NewString( + "ListOptions", + "DeleteOptions", + "Status", + "PodLogOptions", + "PodExecOptions", + "PodAttachOptions", + "PodPortForwardOptions", + "PodProxyOptions", + "NodeProxyOptions", + "ServiceProxyOptions", +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/operation/operation.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/operation/operation.go new file mode 100644 index 0000000000..cccc7e60c2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/operation/operation.go @@ -0,0 +1,100 @@ +/* +Copyright 2024 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 operation + +import ( + "strings" +) + +// Operation provides contextual information about a validation request and the API +// operation being validated. +// This type is intended for use with generate validation code and may be enhanced +// in the future to include other information needed to validate requests. +type Operation struct { + // Type is the category of operation being validated. This does not + // differentiate between HTTP verbs like PUT and PATCH, but rather merges + // those into a single "Update" category. + Type Type + + // Options are the validation options in effect for this operation, mapping option + // name to whether it is enabled. Option names typically match feature gates, but an + // option may be enabled even when its feature gate is off — e.g. when the feature is + // already in use by the object being updated. Set by the resource strategy and + // read-only during validation. + // + // Every option a validation tag references must be defined here by the strategy; an + // option that is not defined is a programming error (see HasOption). + Options map[string]bool + + // Request provides information about the request being validated. + Request Request +} + +// HasOption returns whether the named option is enabled and whether it was defined by +// the strategy. Every option a validation tag references must be defined; callers treat +// an undefined option as an internal error (see validate.IfOption) rather than silently +// as disabled. +func (o Operation) HasOption(option string) (enabled, defined bool) { + enabled, defined = o.Options[option] + return +} + +// Request provides information about the request being validated. +type Request struct { + // Subresources identifies the subresource path components of the request. For + // example, Subresources for a request to `/api/v1/pods/my-pod/status` would be + // `["status"]`. For `/api/v1/widget/my-widget/x/y/z`, it would be `["x", "y", + // "z"]`. For a root resource (`/api/v1/pods/my-pod`), Subresources will be an + // empty slice. + // + // Validation logic should only consult this field if the validation rules for a + // particular field differ depending on whether the main resource or a specific + // subresource is being accessed. For example: + // + // Updates to a Pod resource (`/`) normally cannot change container resource + // requests/limits after the Pod is created (they are immutable). However, when + // accessing the Pod's "resize" subresource (`/resize`), these specific fields + // are allowed to be modified. In this scenario, the validation logic for + // `spec.container[*].resources` must check `Subresources` to permit changes only + // when the request targets the "resize" subresource. + // + // Note: This field should not be used to control which fields a subresource + // operation is allowed to write. This is the responsibility of "field wiping". + // Field wiping logic is expected to be handled in resource strategies by + // modifying the incoming object before it is validated. + Subresources []string +} + +// SubresourcePath returns the path is a slash-separated list of subresource +// names. For example, `/status`, `/resize`, or `/x/y/z`. +func (r Request) SubresourcePath() string { + if len(r.Subresources) == 0 { + return "/" + } + return "/" + strings.Join(r.Subresources, "/") +} + +// Code is the request operation to be validated. +type Type uint32 + +const ( + // Create indicates the request being validated is for a resource create operation. + Create Type = iota + + // Update indicates the request being validated is for a resource update operation. + Update +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/OWNERS new file mode 100644 index 0000000000..063fd285da --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/OWNERS @@ -0,0 +1,10 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +reviewers: + - thockin + - smarterclayton + - wojtek-t + - derekwaynecarr + - mikedanese + - saad-ali + - janetkuo diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/amount.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/amount.go new file mode 100644 index 0000000000..2eebec667d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/amount.go @@ -0,0 +1,337 @@ +/* +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 resource + +import ( + "math/big" + "strconv" + + inf "gopkg.in/inf.v0" +) + +// Scale is used for getting and setting the base-10 scaled value. +// Base-2 scales are omitted for mathematical simplicity. +// See Quantity.ScaledValue for more details. +type Scale int32 + +// infScale adapts a Scale value to an inf.Scale value. +func (s Scale) infScale() inf.Scale { + return inf.Scale(-s) // inf.Scale is upside-down +} + +const ( + Nano Scale = -9 + Micro Scale = -6 + Milli Scale = -3 + Kilo Scale = 3 + Mega Scale = 6 + Giga Scale = 9 + Tera Scale = 12 + Peta Scale = 15 + Exa Scale = 18 +) + +var ( + Zero = int64Amount{} + + // Used by quantity strings - treat as read only + zeroBytes = []byte("0") +) + +// int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster +// than operations on inf.Dec for values that can be represented as int64. +// +k8s:openapi-gen=true +type int64Amount struct { + value int64 + scale Scale +} + +// Sign returns 0 if the value is zero, -1 if it is less than 0, or 1 if it is greater than 0. +func (a int64Amount) Sign() int { + switch { + case a.value == 0: + return 0 + case a.value > 0: + return 1 + default: + return -1 + } +} + +// AsInt64 returns the current amount as an int64 at scale 0, or false if the value cannot be +// represented in an int64 OR would result in a loss of precision. This method is intended as +// an optimization to avoid calling AsDec. +func (a int64Amount) AsInt64() (int64, bool) { + if a.scale == 0 { + return a.value, true + } + if a.scale < 0 { + // TODO: attempt to reduce factors, although it is assumed that factors are reduced prior + // to the int64Amount being created. + return 0, false + } + return positiveScaleInt64(a.value, a.scale) +} + +// AsScaledInt64 returns an int64 representing the value of this amount at the specified scale, +// rounding up, or false if that would result in overflow. (1e20).AsScaledInt64(1) would result +// in overflow because 1e19 is not representable as an int64. Note that setting a scale larger +// than the current value may result in loss of precision - i.e. (1e-6).AsScaledInt64(0) would +// return 1, because 0.000001 is rounded up to 1. +func (a int64Amount) AsScaledInt64(scale Scale) (result int64, ok bool) { + if a.scale < scale { + result, _ = negativeScaleInt64(a.value, scale-a.scale) + return result, true + } + return positiveScaleInt64(a.value, a.scale-scale) +} + +// AsDec returns an inf.Dec representation of this value. +func (a int64Amount) AsDec() *inf.Dec { + var base inf.Dec + base.SetUnscaled(a.value) + base.SetScale(inf.Scale(-a.scale)) + return &base +} + +// Cmp returns 0 if a and b are equal, 1 if a is greater than b, or -1 if a is less than b. +func (a int64Amount) Cmp(b int64Amount) int { + switch { + case a.scale == b.scale: + // compare only the unscaled portion + case a.scale > b.scale: + result, remainder, exact := divideByScaleInt64(b.value, a.scale-b.scale) + if !exact { + return a.AsDec().Cmp(b.AsDec()) + } + if result == a.value { + switch { + case remainder == 0: + return 0 + case remainder > 0: + return -1 + default: + return 1 + } + } + b.value = result + default: + result, remainder, exact := divideByScaleInt64(a.value, b.scale-a.scale) + if !exact { + return a.AsDec().Cmp(b.AsDec()) + } + if result == b.value { + switch { + case remainder == 0: + return 0 + case remainder > 0: + return 1 + default: + return -1 + } + } + a.value = result + } + + switch { + case a.value == b.value: + return 0 + case a.value < b.value: + return -1 + default: + return 1 + } +} + +// Add adds two int64Amounts together, matching scales. It will return false and not mutate +// a if overflow or underflow would result. +func (a *int64Amount) Add(b int64Amount) bool { + switch { + case b.value == 0: + return true + case a.value == 0: + a.value = b.value + a.scale = b.scale + return true + case a.scale == b.scale: + c, ok := int64Add(a.value, b.value) + if !ok { + return false + } + a.value = c + case a.scale > b.scale: + c, ok := positiveScaleInt64(a.value, a.scale-b.scale) + if !ok { + return false + } + c, ok = int64Add(c, b.value) + if !ok { + return false + } + a.scale = b.scale + a.value = c + default: + c, ok := positiveScaleInt64(b.value, b.scale-a.scale) + if !ok { + return false + } + c, ok = int64Add(a.value, c) + if !ok { + return false + } + a.value = c + } + return true +} + +// Sub removes the value of b from the current amount, or returns false if underflow would result. +func (a *int64Amount) Sub(b int64Amount) bool { + return a.Add(int64Amount{value: -b.value, scale: b.scale}) +} + +// Mul multiplies the provided b to the current amount, or +// returns false if overflow or underflow would result. +func (a *int64Amount) Mul(b int64) bool { + switch { + case a.value == 0: + return true + case b == 0: + a.value = 0 + a.scale = 0 + return true + case a.scale == 0: + c, ok := int64Multiply(a.value, b) + if !ok { + return false + } + a.value = c + case a.scale > 0: + c, ok := int64Multiply(a.value, b) + if !ok { + return false + } + if _, ok = positiveScaleInt64(c, a.scale); !ok { + return false + } + a.value = c + default: + c, ok := int64Multiply(a.value, b) + if !ok { + return false + } + if _, ok = negativeScaleInt64(c, -a.scale); !ok { + return false + } + a.value = c + } + return true +} + +// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision +// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. +func (a int64Amount) AsScale(scale Scale) (int64Amount, bool) { + if a.scale >= scale { + return a, true + } + result, exact := negativeScaleInt64(a.value, scale-a.scale) + return int64Amount{value: result, scale: scale}, exact +} + +// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted +// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. +func (a int64Amount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + mantissa := a.value + exponent = int32(a.scale) + + amount, times := removeInt64Factors(mantissa, 10) + exponent += int32(times) + + // make sure exponent is a multiple of 3 + var ok bool + switch exponent % 3 { + case 1, -2: + amount, ok = int64MultiplyScale10(amount) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) + } + exponent = exponent - 1 + case 2, -1: + amount, ok = int64MultiplyScale100(amount) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) + } + exponent = exponent - 2 + } + return strconv.AppendInt(out, amount, 10), exponent +} + +// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would +// return []byte("2048"), 1. +func (a int64Amount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { + value, ok := a.AsScaledInt64(0) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBase1024Bytes(out) + } + amount, exponent := removeInt64Factors(value, 1024) + return strconv.AppendInt(out, amount, 10), exponent +} + +// infDecAmount implements common operations over an inf.Dec that are specific to the quantity +// representation. +type infDecAmount struct { + *inf.Dec +} + +// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision +// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. +func (a infDecAmount) AsScale(scale Scale) (infDecAmount, bool) { + tmp := &inf.Dec{} + tmp.Round(a.Dec, scale.infScale(), inf.RoundUp) + return infDecAmount{tmp}, tmp.Cmp(a.Dec) == 0 +} + +// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted +// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. +func (a infDecAmount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + mantissa := a.Dec.UnscaledBig() + exponent = int32(-a.Dec.Scale()) + amount := big.NewInt(0).Set(mantissa) + // move all factors of 10 into the exponent for easy reasoning + amount, times := removeBigIntFactors(amount, bigTen) + exponent += times + + // make sure exponent is a multiple of 3 + for exponent%3 != 0 { + amount.Mul(amount, bigTen) + exponent-- + } + + return append(out, amount.String()...), exponent +} + +// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would +// return []byte("2048"), 1. +func (a infDecAmount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { + tmp := &inf.Dec{} + tmp.Round(a.Dec, 0, inf.RoundUp) + amount, exponent := removeBigIntFactors(tmp.UnscaledBig(), big1024) + return append(out, amount.String()...), exponent +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/amount_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/amount_test.go new file mode 100644 index 0000000000..a6c4054b98 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/amount_test.go @@ -0,0 +1,205 @@ +/* +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 resource + +import ( + "testing" +) + +func TestInt64AmountAsInt64(t *testing.T) { + for _, test := range []struct { + value int64 + scale Scale + result int64 + ok bool + }{ + {100, 0, 100, true}, + {100, 1, 1000, true}, + {100, -5, 0, false}, + {100, 100, 0, false}, + } { + r, ok := int64Amount{value: test.value, scale: test.scale}.AsInt64() + if r != test.result { + t.Errorf("%v: unexpected result: %d", test, r) + } + if ok != test.ok { + t.Errorf("%v: unexpected ok: %t", test, ok) + } + } +} + +func TestInt64AmountAdd(t *testing.T) { + for _, test := range []struct { + a, b, c int64Amount + ok bool + }{ + {int64Amount{value: 100, scale: 1}, int64Amount{value: 10, scale: 2}, int64Amount{value: 200, scale: 1}, true}, + {int64Amount{value: 100, scale: 1}, int64Amount{value: 1, scale: 2}, int64Amount{value: 110, scale: 1}, true}, + {int64Amount{value: 100, scale: 1}, int64Amount{value: 1, scale: 100}, int64Amount{value: 1, scale: 100}, false}, + {int64Amount{value: -5, scale: 2}, int64Amount{value: 50, scale: 1}, int64Amount{value: 0, scale: 1}, true}, + {int64Amount{value: -5, scale: 2}, int64Amount{value: 5, scale: 2}, int64Amount{value: 0, scale: 2}, true}, + + {int64Amount{value: mostPositive, scale: -1}, int64Amount{value: 1, scale: -1}, int64Amount{value: 0, scale: -1}, false}, + {int64Amount{value: mostPositive, scale: -1}, int64Amount{value: 0, scale: -1}, int64Amount{value: mostPositive, scale: -1}, true}, + {int64Amount{value: mostPositive / 10, scale: 1}, int64Amount{value: 10, scale: 0}, int64Amount{value: mostPositive, scale: -1}, false}, + } { + c := test.a + ok := c.Add(test.b) + if ok != test.ok { + t.Errorf("%v: unexpected ok: %t", test, ok) + } + if ok { + if c != test.c { + t.Errorf("%v: unexpected result: %d", test, c) + } + } else { + if c != test.a { + t.Errorf("%v: overflow addition mutated source: %d", test, c) + } + } + + // addition is commutative + c = test.b + if ok := c.Add(test.a); ok != test.ok { + t.Errorf("%v: unexpected ok: %t", test, ok) + } + if ok { + if c != test.c { + t.Errorf("%v: unexpected result: %d", test, c) + } + } else { + if c != test.b { + t.Errorf("%v: overflow addition mutated source: %d", test, c) + } + } + } +} + +func TestInt64AmountMul(t *testing.T) { + for _, test := range []struct { + a int64Amount + b int64 + c int64Amount + ok bool + }{ + {int64Amount{value: 100, scale: 1}, 1000, int64Amount{value: 100000, scale: 1}, true}, + {int64Amount{value: 100, scale: -1}, 1000, int64Amount{value: 100000, scale: -1}, true}, + {int64Amount{value: 1, scale: 100}, 10, int64Amount{value: 1, scale: 100}, false}, + {int64Amount{value: 1, scale: -100}, 10, int64Amount{value: 1, scale: -100}, false}, + {int64Amount{value: -5, scale: 2}, 500, int64Amount{value: -2500, scale: 2}, true}, + {int64Amount{value: -5, scale: -2}, 500, int64Amount{value: -2500, scale: -2}, true}, + {int64Amount{value: 0, scale: 1}, 0, int64Amount{value: 0, scale: 1}, true}, + + {int64Amount{value: mostPositive, scale: -1}, 10, int64Amount{value: mostPositive, scale: -1}, false}, + {int64Amount{value: mostPositive, scale: -1}, 0, int64Amount{value: 0, scale: 0}, true}, + {int64Amount{value: mostPositive, scale: 0}, 1, int64Amount{value: mostPositive, scale: 0}, true}, + {int64Amount{value: mostPositive / 10, scale: 1}, 10, int64Amount{value: mostPositive / 10, scale: 1}, false}, + {int64Amount{value: mostPositive, scale: 0}, -1, int64Amount{value: -mostPositive, scale: 0}, true}, + {int64Amount{value: mostNegative, scale: 0}, 1, int64Amount{value: mostNegative, scale: 0}, true}, + {int64Amount{value: mostNegative, scale: 1}, 0, int64Amount{value: 0, scale: 0}, true}, + {int64Amount{value: mostNegative, scale: 1}, 1, int64Amount{value: mostNegative, scale: 1}, false}, + } { + c := test.a + ok := c.Mul(test.b) + if ok && !test.ok { + t.Errorf("unextected success: %v", c) + } else if !ok && test.ok { + t.Errorf("unexpeted failure: %v", c) + } else if ok { + if c != test.c { + t.Errorf("%v: unexpected result: %d", test, c) + } + } else { + if c != test.a { + t.Errorf("%v: overflow multiplication mutated source: %d", test, c) + } + } + } +} + +func TestInt64AsCanonicalString(t *testing.T) { + for _, test := range []struct { + value int64 + scale Scale + result string + exponent int32 + }{ + {100, 0, "100", 0}, + {100, 1, "1", 3}, + {100, -1, "10", 0}, + {10800, -10, "1080", -9}, + } { + r, exp := int64Amount{value: test.value, scale: test.scale}.AsCanonicalBytes(nil) + if string(r) != test.result { + t.Errorf("%v: unexpected result: %s", test, r) + } + if exp != test.exponent { + t.Errorf("%v: unexpected exponent: %d", test, exp) + } + } +} + +func TestAmountSign(t *testing.T) { + table := []struct { + i int64Amount + expect int + }{ + {int64Amount{value: -50, scale: 1}, -1}, + {int64Amount{value: 0, scale: 1}, 0}, + {int64Amount{value: 300, scale: 1}, 1}, + {int64Amount{value: -50, scale: -8}, -1}, + {int64Amount{value: 50, scale: -8}, 1}, + {int64Amount{value: 0, scale: -8}, 0}, + {int64Amount{value: -50, scale: 0}, -1}, + {int64Amount{value: 50, scale: 0}, 1}, + {int64Amount{value: 0, scale: 0}, 0}, + } + for _, testCase := range table { + if result := testCase.i.Sign(); result != testCase.expect { + t.Errorf("i: %v, Expected: %v, Actual: %v", testCase.i, testCase.expect, result) + } + } +} + +func TestInt64AmountAsScaledInt64(t *testing.T) { + for _, test := range []struct { + name string + i int64Amount + scaled Scale + result int64 + ok bool + }{ + {"test when i.scale < scaled ", int64Amount{value: 100, scale: 0}, 5, 1, true}, + {"test when i.scale = scaled", int64Amount{value: 100, scale: 1}, 1, 100, true}, + {"test when i.scale > scaled and result doesn't overflow", int64Amount{value: 100, scale: 5}, 2, 100000, true}, + {"test when i.scale > scaled and result overflows", int64Amount{value: 876, scale: 30}, 4, 0, false}, + {"test when i.scale < 0 and fraction exists", int64Amount{value: 93, scale: -1}, 0, 10, true}, + {"test when i.scale < 0 and fraction doesn't exist", int64Amount{value: 100, scale: -1}, 0, 10, true}, + {"test when i.value < 0 and fraction exists", int64Amount{value: -1932, scale: 2}, 4, -20, true}, + {"test when i.value < 0 and fraction doesn't exists", int64Amount{value: -1900, scale: 2}, 4, -19, true}, + } { + t.Run(test.name, func(t *testing.T) { + r, ok := test.i.AsScaledInt64(test.scaled) + if r != test.result { + t.Errorf("%v: expected result: %d, got result: %d", test.name, test.result, r) + } + if ok != test.ok { + t.Errorf("%v: expected ok: %t, got ok: %t", test.name, test.ok, ok) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/generated.pb.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/generated.pb.go new file mode 100644 index 0000000000..9e1a5c0e1f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/generated.pb.go @@ -0,0 +1,24 @@ +/* +Copyright 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/apimachinery/pkg/api/resource/generated.proto + +package resource + +func (m *Quantity) Reset() { *m = Quantity{} } + +func (m *QuantityValue) Reset() { *m = QuantityValue{} } diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/generated.proto b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/generated.proto new file mode 100644 index 0000000000..875ad8577a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/generated.proto @@ -0,0 +1,113 @@ +/* +Copyright 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.apimachinery.pkg.api.resource; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/apimachinery/pkg/api/resource"; + +// Quantity is a fixed-point representation of a number. +// It provides convenient marshaling/unmarshaling in JSON and YAML, +// in addition to String() and AsInt64() accessors. +// +// The serialization format is: +// +// ``` +// ::= +// +// (Note that may be empty, from the "" case in .) +// +// ::= 0 | 1 | ... | 9 +// ::= | +// ::= | . | . | . +// ::= "+" | "-" +// ::= | +// ::= | | +// ::= Ki | Mi | Gi | Ti | Pi | Ei +// +// (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) +// +// ::= m | "" | k | M | G | T | P | E +// +// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) +// +// ::= "e" | "E" +// ``` +// +// No matter which of the three exponent forms is used, no quantity may represent +// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal +// places. Numbers larger or more precise will be capped or rounded up. +// (E.g.: 0.1m will rounded up to 1m.) +// This may be extended in the future if we require larger or smaller quantities. +// +// When a Quantity is parsed from a string, it will remember the type of suffix +// it had, and will use the same type again when it is serialized. +// +// Before serializing, Quantity will be put in "canonical form". +// This means that Exponent/suffix will be adjusted up or down (with a +// corresponding increase or decrease in Mantissa) such that: +// +// - No precision is lost +// - No fractional digits will be emitted +// - The exponent (or suffix) is as large as possible. +// +// The sign will be omitted unless the number is negative. +// +// Examples: +// +// - 1.5 will be serialized as "1500m" +// - 1.5Gi will be serialized as "1536Mi" +// +// Note that the quantity will NEVER be internally represented by a +// floating point number. That is the whole point of this exercise. +// +// Non-canonical values will still parse as long as they are well formed, +// but will be re-emitted in their canonical form. (So always use canonical +// form, or don't diff.) +// +// This format is intended to make it difficult to use these numbers without +// writing some sort of special handling code in the hopes that that will +// cause implementors to also use a fixed point implementation. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.api.resource +message Quantity { + optional string string = 1; +} + +// QuantityValue makes it possible to use a Quantity as value for a command +// line parameter. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.api.resource +message QuantityValue { + optional string string = 1; +} + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/math.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/math.go new file mode 100644 index 0000000000..8ffcb9f09a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/math.go @@ -0,0 +1,310 @@ +/* +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 resource + +import ( + "math/big" + + inf "gopkg.in/inf.v0" +) + +const ( + // maxInt64Factors is the highest value that will be checked when removing factors of 10 from an int64. + // It is also the maximum decimal digits that can be represented with an int64. + maxInt64Factors = 18 +) + +var ( + // Commonly needed big.Int values-- treat as read only! + bigTen = big.NewInt(10) + bigZero = big.NewInt(0) + bigOne = big.NewInt(1) + bigThousand = big.NewInt(1000) + big1024 = big.NewInt(1024) + + // Commonly needed inf.Dec values-- treat as read only! + decZero = inf.NewDec(0, 0) + decOne = inf.NewDec(1, 0) + + // Largest (in magnitude) number allowed. + maxAllowed = infDecAmount{inf.NewDec((1<<63)-1, 0)} // == max int64 + + // The maximum value we can represent milli-units for. + // Compare with the return value of Quantity.Value() to + // see if it's safe to use Quantity.MilliValue(). + MaxMilliValue = int64(((1 << 63) - 1) / 1000) +) + +const mostNegative = -(mostPositive + 1) +const mostPositive = 1<<63 - 1 + +// int64Add returns a+b, or false if that would overflow int64. +func int64Add(a, b int64) (int64, bool) { + c := a + b + switch { + case a > 0 && b > 0: + if c < 0 { + return 0, false + } + case a < 0 && b < 0: + if c > 0 { + return 0, false + } + if a == mostNegative && b == mostNegative { + return 0, false + } + } + return c, true +} + +// int64Multiply returns a*b, or false if that would overflow or underflow int64. +func int64Multiply(a, b int64) (int64, bool) { + if a == 0 || b == 0 || a == 1 || b == 1 { + return a * b, true + } + if a == mostNegative || b == mostNegative { + return 0, false + } + c := a * b + return c, c/b == a +} + +// int64MultiplyScale returns a*b, assuming b is greater than one, or false if that would overflow or underflow int64. +// Use when b is known to be greater than one. +func int64MultiplyScale(a int64, b int64) (int64, bool) { + if a == 0 || a == 1 { + return a * b, true + } + if a == mostNegative && b != 1 { + return 0, false + } + c := a * b + return c, c/b == a +} + +// int64MultiplyScale10 multiplies a by 10, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 10) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale10(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 10, true + } + if a == mostNegative { + return 0, false + } + c := a * 10 + return c, c/10 == a +} + +// int64MultiplyScale100 multiplies a by 100, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 100) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale100(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 100, true + } + if a == mostNegative { + return 0, false + } + c := a * 100 + return c, c/100 == a +} + +// int64MultiplyScale1000 multiplies a by 1000, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 1000) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale1000(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 1000, true + } + if a == mostNegative { + return 0, false + } + c := a * 1000 + return c, c/1000 == a +} + +// positiveScaleInt64 multiplies base by 10^scale, returning false if the +// value overflows. Passing a negative scale is undefined. +func positiveScaleInt64(base int64, scale Scale) (int64, bool) { + switch scale { + case 0: + return base, true + case 1: + return int64MultiplyScale10(base) + case 2: + return int64MultiplyScale100(base) + case 3: + return int64MultiplyScale1000(base) + case 6: + return int64MultiplyScale(base, 1000000) + case 9: + return int64MultiplyScale(base, 1000000000) + default: + value := base + var ok bool + for i := Scale(0); i < scale; i++ { + if value, ok = int64MultiplyScale(value, 10); !ok { + return 0, false + } + } + return value, true + } +} + +// negativeScaleInt64 reduces base by the provided scale, rounding up, until the +// value is zero or the scale is reached. Passing a negative scale is undefined. +// The value returned, if not exact, is rounded away from zero. +func negativeScaleInt64(base int64, scale Scale) (result int64, exact bool) { + if scale == 0 { + return base, true + } + + value := base + var fraction bool + for i := Scale(0); i < scale; i++ { + if !fraction && value%10 != 0 { + fraction = true + } + value = value / 10 + if value == 0 { + if fraction { + if base > 0 { + return 1, false + } + return -1, false + } + return 0, true + } + } + if fraction { + if base > 0 { + value++ + } else { + value-- + } + } + return value, !fraction +} + +func pow10Int64(b int64) int64 { + switch b { + case 0: + return 1 + case 1: + return 10 + case 2: + return 100 + case 3: + return 1000 + case 4: + return 10000 + case 5: + return 100000 + case 6: + return 1000000 + case 7: + return 10000000 + case 8: + return 100000000 + case 9: + return 1000000000 + case 10: + return 10000000000 + case 11: + return 100000000000 + case 12: + return 1000000000000 + case 13: + return 10000000000000 + case 14: + return 100000000000000 + case 15: + return 1000000000000000 + case 16: + return 10000000000000000 + case 17: + return 100000000000000000 + case 18: + return 1000000000000000000 + default: + return 0 + } +} + +// negativeScaleInt64 returns the result of dividing base by scale * 10 and the remainder, or +// false if no such division is possible. Dividing by negative scales is undefined. +func divideByScaleInt64(base int64, scale Scale) (result, remainder int64, exact bool) { + if scale == 0 { + return base, 0, true + } + // the max scale representable in base 10 in an int64 is 18 decimal places + if scale >= 18 { + return 0, base, false + } + divisor := pow10Int64(int64(scale)) + return base / divisor, base % divisor, true +} + +// removeInt64Factors divides in a loop; the return values have the property that +// value == result * base ^ scale +func removeInt64Factors(value int64, base int64) (result int64, times int32) { + times = 0 + result = value + negative := result < 0 + if negative { + result = -result + } + switch base { + // allow the compiler to optimize the common cases + case 10: + for result >= 10 && result%10 == 0 { + times++ + result = result / 10 + } + // allow the compiler to optimize the common cases + case 1024: + for result >= 1024 && result%1024 == 0 { + times++ + result = result / 1024 + } + default: + for result >= base && result%base == 0 { + times++ + result = result / base + } + } + if negative { + result = -result + } + return result, times +} + +// removeBigIntFactors divides in a loop; the return values have the property that +// d == result * factor ^ times +// d may be modified in place. +// If d == 0, then the return values will be (0, 0) +func removeBigIntFactors(d, factor *big.Int) (result *big.Int, times int32) { + q := big.NewInt(0) + m := big.NewInt(0) + for d.Cmp(bigZero) != 0 { + q.DivMod(d, factor, m) + if m.Cmp(bigZero) != 0 { + break + } + times++ + d, q = q, d + } + return d, times +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/math_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/math_test.go new file mode 100644 index 0000000000..070a0c237e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/math_test.go @@ -0,0 +1,211 @@ +/* +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 resource + +import ( + "testing" +) + +func TestDetectOverflowAdd(t *testing.T) { + for _, test := range []struct { + a, b int64 + c int64 + ok bool + }{ + {0, 0, 0, true}, + {-1, 1, 0, true}, + {0, 1, 1, true}, + {2, 2, 4, true}, + {2, -2, 0, true}, + {-2, -2, -4, true}, + + {mostNegative, -1, 0, false}, + {mostNegative, 1, mostNegative + 1, true}, + {mostPositive, -1, mostPositive - 1, true}, + {mostPositive, 1, 0, false}, + + {mostNegative, mostPositive, -1, true}, + {mostPositive, mostNegative, -1, true}, + {mostPositive, mostPositive, 0, false}, + {mostNegative, mostNegative, 0, false}, + + {-mostPositive, mostNegative, 0, false}, + {mostNegative, -mostPositive, 0, false}, + {-mostPositive, -mostPositive, 0, false}, + } { + c, ok := int64Add(test.a, test.b) + if c != test.c { + t.Errorf("%v: unexpected result: %d", test, c) + } + if ok != test.ok { + t.Errorf("%v: unexpected overflow: %t", test, ok) + } + // addition is commutative + d, ok2 := int64Add(test.b, test.a) + if c != d || ok != ok2 { + t.Errorf("%v: not commutative: %d %t", test, d, ok2) + } + } +} + +func TestDetectOverflowMultiply(t *testing.T) { + for _, test := range []struct { + a, b int64 + c int64 + ok bool + }{ + {0, 0, 0, true}, + {-1, 1, -1, true}, + {-1, -1, 1, true}, + {1, 1, 1, true}, + {0, 1, 0, true}, + {1, 0, 0, true}, + {2, 2, 4, true}, + {2, -2, -4, true}, + {-2, -2, 4, true}, + + {mostNegative, -1, 0, false}, + {mostNegative, 1, mostNegative, true}, + {mostPositive, -1, -mostPositive, true}, + {mostPositive, 1, mostPositive, true}, + + {mostNegative, mostPositive, 0, false}, + {mostPositive, mostNegative, 0, false}, + {mostPositive, mostPositive, 1, false}, + {mostNegative, mostNegative, 0, false}, + + {-mostPositive, mostNegative, 0, false}, + {mostNegative, -mostPositive, 0, false}, + {-mostPositive, -mostPositive, 1, false}, + } { + c, ok := int64Multiply(test.a, test.b) + if c != test.c { + t.Errorf("%v: unexpected result: %d", test, c) + } + if ok != test.ok { + t.Errorf("%v: unexpected overflow: %t", test, ok) + } + // multiplication is commutative + d, ok2 := int64Multiply(test.b, test.a) + if c != d || ok != ok2 { + t.Errorf("%v: not commutative: %d %t", test, d, ok2) + } + } +} + +func TestDetectOverflowScale(t *testing.T) { + for _, a := range []int64{0, -1, 1, 10, -10, mostPositive, mostNegative, -mostPositive} { + for _, b := range []int64{1, 2, 10, 100, 1000, mostPositive} { + expect, expectOk := int64Multiply(a, b) + + c, ok := int64MultiplyScale(a, b) + if c != expect { + t.Errorf("%d*%d: unexpected result: %d", a, b, c) + } + if ok != expectOk { + t.Errorf("%d*%d: unexpected overflow: %t", a, b, ok) + } + } + for _, test := range []struct { + base int64 + fn func(a int64) (int64, bool) + }{ + {10, int64MultiplyScale10}, + {100, int64MultiplyScale100}, + {1000, int64MultiplyScale1000}, + } { + expect, expectOk := int64Multiply(a, test.base) + c, ok := test.fn(a) + if c != expect { + t.Errorf("%d*%d: unexpected result: %d", a, test.base, c) + } + if ok != expectOk { + t.Errorf("%d*%d: unexpected overflow: %t", a, test.base, ok) + } + } + } +} + +func TestRemoveInt64Factors(t *testing.T) { + for _, test := range []struct { + value int64 + max int64 + result int64 + scale int32 + }{ + {100, 10, 1, 2}, + {100, 10, 1, 2}, + {100, 100, 1, 1}, + {1, 10, 1, 0}, + } { + r, s := removeInt64Factors(test.value, test.max) + if r != test.result { + t.Errorf("%v: unexpected result: %d", test, r) + } + if s != test.scale { + t.Errorf("%v: unexpected scale: %d", test, s) + } + } +} + +func TestNegativeScaleInt64(t *testing.T) { + for _, test := range []struct { + base int64 + scale Scale + result int64 + exact bool + }{ + {1234567, 0, 1234567, true}, + {1234567, 1, 123457, false}, + {1234567, 2, 12346, false}, + {1234567, 3, 1235, false}, + {1234567, 4, 124, false}, + + {-1234567, 0, -1234567, true}, + {-1234567, 1, -123457, false}, + {-1234567, 2, -12346, false}, + {-1234567, 3, -1235, false}, + {-1234567, 4, -124, false}, + + {1000, 0, 1000, true}, + {1000, 1, 100, true}, + {1000, 2, 10, true}, + {1000, 3, 1, true}, + {1000, 4, 1, false}, + + {-1000, 0, -1000, true}, + {-1000, 1, -100, true}, + {-1000, 2, -10, true}, + {-1000, 3, -1, true}, + {-1000, 4, -1, false}, + + {0, 0, 0, true}, + {0, 1, 0, true}, + {0, 2, 0, true}, + + // negative scale is undefined behavior + {1000, -1, 1000, true}, + } { + result, exact := negativeScaleInt64(test.base, test.scale) + if result != test.result { + t.Errorf("%v: unexpected result: %d", test, result) + } + if exact != test.exact { + t.Errorf("%v: unexpected exact: %t", test, exact) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity.go new file mode 100644 index 0000000000..e6b1f1b535 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity.go @@ -0,0 +1,891 @@ +/* +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 resource + +import ( + "bytes" + "errors" + "fmt" + math "math" + "math/big" + "strconv" + "strings" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + + inf "gopkg.in/inf.v0" +) + +// Quantity is a fixed-point representation of a number. +// It provides convenient marshaling/unmarshaling in JSON and YAML, +// in addition to String() and AsInt64() accessors. +// +// The serialization format is: +// +// ``` +// ::= +// +// (Note that may be empty, from the "" case in .) +// +// ::= 0 | 1 | ... | 9 +// ::= | +// ::= | . | . | . +// ::= "+" | "-" +// ::= | +// ::= | | +// ::= Ki | Mi | Gi | Ti | Pi | Ei +// +// (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) +// +// ::= m | "" | k | M | G | T | P | E +// +// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) +// +// ::= "e" | "E" +// ``` +// +// No matter which of the three exponent forms is used, no quantity may represent +// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal +// places. Numbers larger or more precise will be capped or rounded up. +// (E.g.: 0.1m will rounded up to 1m.) +// This may be extended in the future if we require larger or smaller quantities. +// +// When a Quantity is parsed from a string, it will remember the type of suffix +// it had, and will use the same type again when it is serialized. +// +// Before serializing, Quantity will be put in "canonical form". +// This means that Exponent/suffix will be adjusted up or down (with a +// corresponding increase or decrease in Mantissa) such that: +// +// - No precision is lost +// - No fractional digits will be emitted +// - The exponent (or suffix) is as large as possible. +// +// The sign will be omitted unless the number is negative. +// +// Examples: +// +// - 1.5 will be serialized as "1500m" +// - 1.5Gi will be serialized as "1536Mi" +// +// Note that the quantity will NEVER be internally represented by a +// floating point number. That is the whole point of this exercise. +// +// Non-canonical values will still parse as long as they are well formed, +// but will be re-emitted in their canonical form. (So always use canonical +// form, or don't diff.) +// +// This format is intended to make it difficult to use these numbers without +// writing some sort of special handling code in the hopes that that will +// cause implementors to also use a fixed point implementation. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.api.resource +type Quantity struct { + // i is the quantity in int64 scaled form, if d.Dec == nil + i int64Amount + // d is the quantity in inf.Dec form if d.Dec != nil + d infDecAmount + // s is the generated value of this quantity to avoid recalculation + s string + + // Change Format at will. See the comment for Canonicalize for + // more details. + Format +} + +// CanonicalValue allows a quantity amount to be converted to a string. +type CanonicalValue interface { + // AsCanonicalBytes returns a byte array representing the string representation + // of the value mantissa and an int32 representing its exponent in base-10. Callers may + // pass a byte slice to the method to avoid allocations. + AsCanonicalBytes(out []byte) ([]byte, int32) + // AsCanonicalBase1024Bytes returns a byte array representing the string representation + // of the value mantissa and an int32 representing its exponent in base-1024. Callers + // may pass a byte slice to the method to avoid allocations. + AsCanonicalBase1024Bytes(out []byte) ([]byte, int32) +} + +// Format lists the three possible formattings of a quantity. +type Format string + +const ( + DecimalExponent = Format("DecimalExponent") // e.g., 12e6 + BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20) + DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6) +) + +// MustParse turns the given string into a quantity or panics; for tests +// or other cases where you know the string is valid. +func MustParse(str string) Quantity { + q, err := ParseQuantity(str) + if err != nil { + panic(fmt.Errorf("cannot parse '%v': %v", str, err)) + } + return q +} + +const ( + // splitREString is used to separate a number from its suffix; as such, + // this is overly permissive, but that's OK-- it will be checked later. + splitREString = "^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$" +) + +var ( + // Errors that could happen while parsing a string. + ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'") + ErrNumeric = errors.New("unable to parse numeric part of quantity") + ErrSuffix = errors.New("unable to parse quantity's suffix") +) + +// parseQuantityString is a fast scanner for quantity values. +func parseQuantityString(str string) (positive bool, value, num, denom, suffix string, err error) { + positive = true + pos := 0 + end := len(str) + + // handle leading sign + if pos < end { + switch str[0] { + case '-': + positive = false + pos++ + case '+': + pos++ + } + } + + // strip leading zeros +Zeroes: + for i := pos; ; i++ { + if i >= end { + num = "0" + value = num + return + } + switch str[i] { + case '0': + pos++ + default: + break Zeroes + } + } + + // extract the numerator +Num: + for i := pos; ; i++ { + if i >= end { + num = str[pos:end] + value = str[0:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + num = str[pos:i] + pos = i + break Num + } + } + + // if we stripped all numerator positions, always return 0 + if len(num) == 0 { + num = "0" + } + + // handle a denominator + if pos < end && str[pos] == '.' { + pos++ + Denom: + for i := pos; ; i++ { + if i >= end { + denom = str[pos:end] + value = str[0:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + denom = str[pos:i] + pos = i + break Denom + } + } + // TODO: we currently allow 1.G, but we may not want to in the future. + // if len(denom) == 0 { + // err = ErrFormatWrong + // return + // } + } + value = str[0:pos] + + // grab the elements of the suffix + suffixStart := pos + for i := pos; ; i++ { + if i >= end { + suffix = str[suffixStart:end] + return + } + if !strings.ContainsAny(str[i:i+1], "eEinumkKMGTP") { + pos = i + break + } + } + if pos < end { + switch str[pos] { + case '-', '+': + pos++ + } + } +Suffix: + for i := pos; ; i++ { + if i >= end { + suffix = str[suffixStart:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + break Suffix + } + } + // we encountered a non decimal in the Suffix loop, but the last character + // was not a valid exponent + err = ErrFormatWrong + return +} + +// ParseQuantity turns str into a Quantity, or returns an error. +func ParseQuantity(str string) (Quantity, error) { + if len(str) == 0 { + return Quantity{}, ErrFormatWrong + } + if str == "0" { + return Quantity{Format: DecimalSI, s: str}, nil + } + + positive, value, num, denom, suf, err := parseQuantityString(str) + if err != nil { + return Quantity{}, err + } + + base, exponent, format, ok := quantitySuffixer.interpret(suffix(suf)) + if !ok { + return Quantity{}, ErrSuffix + } + + precision := int32(0) + scale := int32(0) + mantissa := int64(1) + switch format { + case DecimalExponent, DecimalSI: + scale = exponent + precision = maxInt64Factors - int32(len(num)+len(denom)) + case BinarySI: + scale = 0 + switch { + case exponent >= 0 && len(denom) == 0: + // only handle positive binary numbers with the fast path + mantissa = int64(int64(mantissa) << uint64(exponent)) + // 1Mi (2^20) has ~6 digits of decimal precision, so exponent*3/10 -1 is roughly the precision + precision = 15 - int32(len(num)) - int32(float32(exponent)*3/10) - 1 + default: + precision = -1 + } + } + + if precision >= 0 { + // if we have a denominator, shift the entire value to the left by the number of places in the + // denominator + scale -= int32(len(denom)) + if scale >= int32(Nano) { + shifted := num + denom + + var value int64 + value, err := strconv.ParseInt(shifted, 10, 64) + if err != nil { + return Quantity{}, ErrNumeric + } + if result, ok := int64Multiply(value, int64(mantissa)); ok { + if !positive { + result = -result + } + // if the number is in canonical form, reuse the string + switch format { + case BinarySI: + if exponent%10 == 0 && (value&0x07 != 0) { + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil + } + default: + if scale%3 == 0 && !strings.HasSuffix(shifted, "000") && shifted[0] != '0' { + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil + } + } + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil + } + } + } + + amount := new(inf.Dec) + if _, ok := amount.SetString(value); !ok { + return Quantity{}, ErrNumeric + } + + // So that no one but us has to think about suffixes, remove it. + if base == 10 { + amount.SetScale(amount.Scale() + Scale(exponent).infScale()) + } else if base == 2 { + // numericSuffix = 2 ** exponent + numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent)) + ub := amount.UnscaledBig() + amount.SetUnscaledBig(ub.Mul(ub, numericSuffix)) + } + + // Cap at min/max bounds. + sign := amount.Sign() + if sign == -1 { + amount.Neg(amount) + } + + // This rounds non-zero values up to the minimum representable value, under the theory that + // if you want some resources, you should get some resources, even if you asked for way too small + // of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have + // the side effect of rounding values < .5n to zero. + if v, ok := amount.Unscaled(); v != int64(0) || !ok { + amount.Round(amount, Nano.infScale(), inf.RoundUp) + } + + // The max is just a simple cap. + // TODO: this prevents accumulating quantities greater than int64, for instance quota across a cluster + if format == BinarySI && amount.Cmp(maxAllowed.Dec) > 0 { + amount.Set(maxAllowed.Dec) + } + + if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 { + // This avoids rounding and hopefully confusion, too. + format = DecimalSI + } + if sign == -1 { + amount.Neg(amount) + } + + return Quantity{d: infDecAmount{amount}, Format: format}, nil +} + +// DeepCopy returns a deep-copy of the Quantity value. Note that the method +// receiver is a value, so we can mutate it in-place and return it. +func (q Quantity) DeepCopy() Quantity { + if q.d.Dec != nil { + tmp := &inf.Dec{} + q.d.Dec = tmp.Set(q.d.Dec) + } + return q +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ Quantity) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ Quantity) OpenAPISchemaFormat() string { return "" } + +// OpenAPIV3OneOfTypes is used by the kube-openapi generator when constructing +// the OpenAPI v3 spec of this type. +func (Quantity) OpenAPIV3OneOfTypes() []string { return []string{"string", "number"} } + +// CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity). +// +// Note about BinarySI: +// - If q.Format is set to BinarySI and q.Amount represents a non-zero value between +// -1 and +1, it will be emitted as if q.Format were DecimalSI. +// - Otherwise, if q.Format is set to BinarySI, fractional parts of q.Amount will be +// rounded up. (1.1i becomes 2i.) +func (q *Quantity) CanonicalizeBytes(out []byte) (result, suffix []byte) { + if q.IsZero() { + return zeroBytes, nil + } + + var rounded CanonicalValue + format := q.Format + switch format { + case DecimalExponent, DecimalSI: + case BinarySI: + if q.CmpInt64(-1024) > 0 && q.CmpInt64(1024) < 0 { + // This avoids rounding and hopefully confusion, too. + format = DecimalSI + } else { + var exact bool + if rounded, exact = q.AsScale(0); !exact { + // Don't lose precision-- show as DecimalSI + format = DecimalSI + } + } + default: + format = DecimalExponent + } + + // TODO: If BinarySI formatting is requested but would cause rounding, upgrade to + // one of the other formats. + switch format { + case DecimalExponent, DecimalSI: + number, exponent := q.AsCanonicalBytes(out) + suffix, _ := quantitySuffixer.constructBytes(10, exponent, format) + return number, suffix + default: + // format must be BinarySI + number, exponent := rounded.AsCanonicalBase1024Bytes(out) + suffix, _ := quantitySuffixer.constructBytes(2, exponent*10, format) + return number, suffix + } +} + +// AsApproximateFloat64 returns a float64 representation of the quantity which +// may lose precision. If precision matter more than performance, see +// AsFloat64Slow. If the value of the quantity is outside the range of a +// float64 +Inf/-Inf will be returned. +func (q *Quantity) AsApproximateFloat64() float64 { + var base float64 + var exponent int + if q.d.Dec != nil { + base, _ = big.NewFloat(0).SetInt(q.d.Dec.UnscaledBig()).Float64() + exponent = int(-q.d.Dec.Scale()) + } else { + base = float64(q.i.value) + exponent = int(q.i.scale) + } + if exponent == 0 { + return base + } + + return base * math.Pow10(exponent) +} + +// AsFloat64Slow returns a float64 representation of the quantity. This is +// more precise than AsApproximateFloat64 but significantly slower. If the +// value of the quantity is outside the range of a float64 +Inf/-Inf will be +// returned. +func (q *Quantity) AsFloat64Slow() float64 { + infDec := q.AsDec() + + var absScale int64 + if infDec.Scale() < 0 { + absScale = int64(-infDec.Scale()) + } else { + absScale = int64(infDec.Scale()) + } + pow10AbsScale := big.NewInt(10) + pow10AbsScale = pow10AbsScale.Exp(pow10AbsScale, big.NewInt(absScale), nil) + + var resultBigFloat *big.Float + if infDec.Scale() < 0 { + resultBigInt := new(big.Int).Mul(infDec.UnscaledBig(), pow10AbsScale) + resultBigFloat = new(big.Float).SetInt(resultBigInt) + } else { + pow10AbsScaleFloat := new(big.Float).SetInt(pow10AbsScale) + resultBigFloat = new(big.Float).SetInt(infDec.UnscaledBig()) + resultBigFloat = resultBigFloat.Quo(resultBigFloat, pow10AbsScaleFloat) + } + + result, _ := resultBigFloat.Float64() + return result +} + +// AsInt64 returns a representation of the current value as an int64 if a fast conversion +// is possible. If false is returned, callers must use the inf.Dec form of this quantity. +func (q *Quantity) AsInt64() (int64, bool) { + if q.d.Dec != nil { + return 0, false + } + return q.i.AsInt64() +} + +// ToDec promotes the quantity in place to use an inf.Dec representation and returns itself. +func (q *Quantity) ToDec() *Quantity { + if q.d.Dec == nil { + q.d.Dec = q.i.AsDec() + q.i = int64Amount{} + } + return q +} + +// AsDec returns the quantity as represented by a scaled inf.Dec. +func (q *Quantity) AsDec() *inf.Dec { + if q.d.Dec != nil { + return q.d.Dec + } + q.d.Dec = q.i.AsDec() + q.i = int64Amount{} + return q.d.Dec +} + +// AsCanonicalBytes returns the canonical byte representation of this quantity as a mantissa +// and base 10 exponent. The out byte slice may be passed to the method to avoid an extra +// allocation. +func (q *Quantity) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + if q.d.Dec != nil { + return q.d.AsCanonicalBytes(out) + } + return q.i.AsCanonicalBytes(out) +} + +// IsZero returns true if the quantity is equal to zero. +func (q *Quantity) IsZero() bool { + if q.d.Dec != nil { + return q.d.Dec.Sign() == 0 + } + return q.i.value == 0 +} + +// Sign returns 0 if the quantity is zero, -1 if the quantity is less than zero, or 1 if the +// quantity is greater than zero. +func (q *Quantity) Sign() int { + if q.d.Dec != nil { + return q.d.Dec.Sign() + } + return q.i.Sign() +} + +// AsScale returns the current value, rounded up to the provided scale, and returns +// false if the scale resulted in a loss of precision. +func (q *Quantity) AsScale(scale Scale) (CanonicalValue, bool) { + if q.d.Dec != nil { + return q.d.AsScale(scale) + } + return q.i.AsScale(scale) +} + +// RoundUp updates the quantity to the provided scale, ensuring that the value is at +// least 1. False is returned if the rounding operation resulted in a loss of precision. +// Negative numbers are rounded away from zero (-9 scale 1 rounds to -10). +func (q *Quantity) RoundUp(scale Scale) bool { + if q.d.Dec != nil { + q.s = "" + d, exact := q.d.AsScale(scale) + q.d = d + return exact + } + // avoid clearing the string value if we have already calculated it + if q.i.scale >= scale { + return true + } + q.s = "" + i, exact := q.i.AsScale(scale) + q.i = i + return exact +} + +// Add adds the provide y quantity to the current value. If the current value is zero, +// the format of the quantity will be updated to the format of y. +func (q *Quantity) Add(y Quantity) { + q.s = "" + if q.d.Dec == nil && y.d.Dec == nil { + if q.i.value == 0 { + q.Format = y.Format + } + if q.i.Add(y.i) { + return + } + } else if q.IsZero() { + q.Format = y.Format + } + q.ToDec().d.Dec.Add(q.d.Dec, y.AsDec()) +} + +// Sub subtracts the provided quantity from the current value in place. If the current +// value is zero, the format of the quantity will be updated to the format of y. +func (q *Quantity) Sub(y Quantity) { + q.s = "" + if q.IsZero() { + q.Format = y.Format + } + if q.d.Dec == nil && y.d.Dec == nil && q.i.Sub(y.i) { + return + } + q.ToDec().d.Dec.Sub(q.d.Dec, y.AsDec()) +} + +// Mul multiplies the provided y to the current value. +// It will return false if the result is inexact. Otherwise, it will return true. +func (q *Quantity) Mul(y int64) bool { + q.s = "" + if q.d.Dec == nil && q.i.Mul(y) { + return true + } + return q.ToDec().d.Dec.Mul(q.d.Dec, inf.NewDec(y, inf.Scale(0))).UnscaledBig().IsInt64() +} + +// Cmp returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the +// quantity is greater than y. +func (q *Quantity) Cmp(y Quantity) int { + if q.d.Dec == nil && y.d.Dec == nil { + return q.i.Cmp(y.i) + } + return q.AsDec().Cmp(y.AsDec()) +} + +// CmpInt64 returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the +// quantity is greater than y. +func (q *Quantity) CmpInt64(y int64) int { + if q.d.Dec != nil { + return q.d.Dec.Cmp(inf.NewDec(y, inf.Scale(0))) + } + return q.i.Cmp(int64Amount{value: y}) +} + +// Neg sets quantity to be the negative value of itself. +func (q *Quantity) Neg() { + q.s = "" + if q.d.Dec == nil { + q.i.value = -q.i.value + return + } + q.d.Dec.Neg(q.d.Dec) +} + +// Equal checks equality of two Quantities. This is useful for testing with +// cmp.Equal. +func (q Quantity) Equal(v Quantity) bool { + return q.Cmp(v) == 0 +} + +// int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation +// of most Quantity values. +const int64QuantityExpectedBytes = 18 + +// String formats the Quantity as a string, caching the result if not calculated. +// String is an expensive operation and caching this result significantly reduces the cost of +// normal parse / marshal operations on Quantity. +func (q *Quantity) String() string { + if q == nil { + return "" + } + if len(q.s) == 0 { + result := make([]byte, 0, int64QuantityExpectedBytes) + number, suffix := q.CanonicalizeBytes(result) + number = append(number, suffix...) + q.s = string(number) + } + return q.s +} + +// MarshalJSON implements the json.Marshaller interface. +func (q Quantity) MarshalJSON() ([]byte, error) { + if len(q.s) > 0 { + out := make([]byte, len(q.s)+2) + out[0], out[len(out)-1] = '"', '"' + copy(out[1:], q.s) + return out, nil + } + result := make([]byte, int64QuantityExpectedBytes) + result[0] = '"' + number, suffix := q.CanonicalizeBytes(result[1:1]) + // if the same slice was returned to us that we passed in, avoid another allocation by copying number into + // the source slice and returning that + if len(number) > 0 && &number[0] == &result[1] && (len(number)+len(suffix)+2) <= int64QuantityExpectedBytes { + number = append(number, suffix...) + number = append(number, '"') + return result[:1+len(number)], nil + } + // if CanonicalizeBytes needed more space than our slice provided, we may need to allocate again so use + // append + result = result[:1] + result = append(result, number...) + result = append(result, suffix...) + result = append(result, '"') + return result, nil +} + +func (q Quantity) MarshalCBOR() ([]byte, error) { + // The call to String() should never return the string "" because the receiver's + // address will never be nil. + return cbor.Marshal(q.String()) +} + +// ToUnstructured implements the value.UnstructuredConverter interface. +func (q Quantity) ToUnstructured() interface{} { + return q.String() +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +// TODO: Remove support for leading/trailing whitespace +func (q *Quantity) UnmarshalJSON(value []byte) error { + l := len(value) + if l == 4 && bytes.Equal(value, []byte("null")) { + q.d.Dec = nil + q.i = int64Amount{} + return nil + } + if l >= 2 && value[0] == '"' && value[l-1] == '"' { + value = value[1 : l-1] + } + + parsed, err := ParseQuantity(strings.TrimSpace(string(value))) + if err != nil { + return err + } + + // This copy is safe because parsed will not be referred to again. + *q = parsed + return nil +} + +func (q *Quantity) UnmarshalCBOR(value []byte) error { + var s *string + if err := cbor.Unmarshal(value, &s); err != nil { + return err + } + + if s == nil { + q.d.Dec = nil + q.i = int64Amount{} + return nil + } + + parsed, err := ParseQuantity(strings.TrimSpace(*s)) + if err != nil { + return err + } + + *q = parsed + return nil +} + +// NewDecimalQuantity returns a new Quantity representing the given +// value in the given format. +func NewDecimalQuantity(b inf.Dec, format Format) *Quantity { + return &Quantity{ + d: infDecAmount{&b}, + Format: format, + } +} + +// NewQuantity returns a new Quantity representing the given +// value in the given format. +func NewQuantity(value int64, format Format) *Quantity { + return &Quantity{ + i: int64Amount{value: value}, + Format: format, + } +} + +// NewMilliQuantity returns a new Quantity representing the given +// value * 1/1000 in the given format. Note that BinarySI formatting +// will round fractional values, and will be changed to DecimalSI for +// values x where (-1 < x < 1) && (x != 0). +func NewMilliQuantity(value int64, format Format) *Quantity { + return &Quantity{ + i: int64Amount{value: value, scale: -3}, + Format: format, + } +} + +// NewScaledQuantity returns a new Quantity representing the given +// value * 10^scale in DecimalSI format. +func NewScaledQuantity(value int64, scale Scale) *Quantity { + return &Quantity{ + i: int64Amount{value: value, scale: scale}, + Format: DecimalSI, + } +} + +// Value returns the unscaled value of q rounded up to the nearest integer away from 0. +func (q *Quantity) Value() int64 { + return q.ScaledValue(0) +} + +// MilliValue returns the value of ceil(q * 1000); this could overflow an int64; +// if that's a concern, call Value() first to verify the number is small enough. +func (q *Quantity) MilliValue() int64 { + return q.ScaledValue(Milli) +} + +// ScaledValue returns the value of ceil(q / 10^scale). +// For example, NewQuantity(1, DecimalSI).ScaledValue(Milli) returns 1000. +// This could overflow an int64. +// To detect overflow, call Value() first and verify the expected magnitude. +func (q *Quantity) ScaledValue(scale Scale) int64 { + if q.d.Dec == nil { + i, _ := q.i.AsScaledInt64(scale) + return i + } + dec := q.d.Dec + return scaledValue(dec.UnscaledBig(), int(dec.Scale()), int(scale.infScale())) +} + +// Set sets q's value to be value. +func (q *Quantity) Set(value int64) { + q.SetScaled(value, 0) +} + +// SetMilli sets q's value to be value * 1/1000. +func (q *Quantity) SetMilli(value int64) { + q.SetScaled(value, Milli) +} + +// SetScaled sets q's value to be value * 10^scale +func (q *Quantity) SetScaled(value int64, scale Scale) { + q.s = "" + q.d.Dec = nil + q.i = int64Amount{value: value, scale: scale} +} + +// QuantityValue makes it possible to use a Quantity as value for a command +// line parameter. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.api.resource +type QuantityValue struct { + Quantity +} + +// Set implements pflag.Value.Set and Go flag.Value.Set. +func (q *QuantityValue) Set(s string) error { + quantity, err := ParseQuantity(s) + if err != nil { + return err + } + q.Quantity = quantity + return nil +} + +// Type implements pflag.Value.Type. +func (q QuantityValue) Type() string { + return "quantity" +} + +// QuantityPtrEqual compares two Quantity pointers and returns true if they are both nil or point to equal quantities. +func QuantityPtrEqual(a, b *Quantity) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return a.Equal(*b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_example_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_example_test.go new file mode 100644 index 0000000000..56a7dbe0e0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_example_test.go @@ -0,0 +1,59 @@ +/* +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 resource_test + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/resource" +) + +func ExampleFormat() { + memorySize := resource.NewQuantity(5*1024*1024*1024, resource.BinarySI) + fmt.Printf("memorySize = %v\n", memorySize) + + diskSize := resource.NewQuantity(5*1000*1000*1000, resource.DecimalSI) + fmt.Printf("diskSize = %v\n", diskSize) + + cores := resource.NewMilliQuantity(5300, resource.DecimalSI) + fmt.Printf("cores = %v\n", cores) + + // Output: + // memorySize = 5Gi + // diskSize = 5G + // cores = 5300m +} + +func ExampleMustParse() { + memorySize := resource.MustParse("5Gi") + fmt.Printf("memorySize = %v (%v)\n", memorySize.Value(), memorySize.Format) + + diskSize := resource.MustParse("5G") + fmt.Printf("diskSize = %v (%v)\n", diskSize.Value(), diskSize.Format) + + cores := resource.MustParse("5300m") + fmt.Printf("milliCores = %v (%v)\n", cores.MilliValue(), cores.Format) + + cores2 := resource.MustParse("5.4") + fmt.Printf("milliCores = %v (%v)\n", cores2.MilliValue(), cores2.Format) + + // Output: + // memorySize = 5368709120 (BinarySI) + // diskSize = 5000000000 (DecimalSI) + // milliCores = 5300 (DecimalSI) + // milliCores = 5400 (DecimalSI) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_proto.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_proto.go new file mode 100644 index 0000000000..364ec80da2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_proto.go @@ -0,0 +1,284 @@ +/* +Copyright 2015 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 resource + +import ( + "fmt" + "io" + "math/bits" +) + +func (m *Quantity) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalToSizedBuffer(data[:size]) + if err != nil { + return nil, err + } + return data[:n], nil +} + +// MarshalTo is a customized version of the generated Protobuf unmarshaler for a struct +// with a single string field. +func (m *Quantity) MarshalTo(data []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(data[:size]) +} + +// MarshalToSizedBuffer is a customized version of the generated +// Protobuf unmarshaler for a struct with a single string field. +func (m *Quantity) MarshalToSizedBuffer(data []byte) (int, error) { + i := len(data) + _ = i + var l int + _ = l + + // BEGIN CUSTOM MARSHAL + out := m.String() + i -= len(out) + copy(data[i:], out) + i = encodeVarintGenerated(data, i, uint64(len(out))) + // END CUSTOM MARSHAL + i-- + data[i] = 0xa + + return len(data) - i, nil +} + +func encodeVarintGenerated(data []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return base +} + +func (m *Quantity) Size() (n int) { + var l int + _ = l + + // BEGIN CUSTOM SIZE + l = len(m.String()) + // END CUSTOM SIZE + + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + return (bits.Len64(x|1) + 6) / 7 +} + +// Unmarshal is a customized version of the generated Protobuf unmarshaler for a struct +// with a single string field. +func (m *Quantity) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Quantity: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Quantity: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field String_", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(data[iNdEx:postIndex]) + + // BEGIN CUSTOM DECODE + p, err := ParseQuantity(s) + if err != nil { + return err + } + *m = p + // END CUSTOM DECODE + + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(data[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +func skipGenerated(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_proto_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_proto_test.go new file mode 100644 index 0000000000..574a3cf5d7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_proto_test.go @@ -0,0 +1,103 @@ +/* +Copyright 2017 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 resource + +import ( + "testing" + + inf "gopkg.in/inf.v0" +) + +func TestQuantityProtoMarshal(t *testing.T) { + // Test when d is nil + table := []struct { + quantity string + expect Quantity + }{ + {"0", Quantity{i: int64Amount{value: 0, scale: 0}, s: "0", Format: DecimalSI}}, + {"100m", Quantity{i: int64Amount{value: 100, scale: -3}, s: "100m", Format: DecimalSI}}, + {"50m", Quantity{i: int64Amount{value: 50, scale: -3}, s: "50m", Format: DecimalSI}}, + {"10000T", Quantity{i: int64Amount{value: 10000, scale: 12}, s: "10000T", Format: DecimalSI}}, + } + for _, testCase := range table { + q := MustParse(testCase.quantity) + // Won't currently get an error as MarshalTo can't return one + result, _ := q.Marshal() + q.MarshalTo(result) + if q.Cmp(testCase.expect) != 0 { + t.Errorf("Expected: %v, Actual: %v", testCase.expect, q) + } + } + // Test when i is {0,0} + table2 := []struct { + dec *inf.Dec + expect Quantity + }{ + {dec(0, 0).Dec, Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(0, 0).Dec}, s: "0", Format: DecimalSI}}, + {dec(10, 0).Dec, Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(10, 0).Dec}, s: "10", Format: DecimalSI}}, + {dec(-10, 0).Dec, Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(-10, 0).Dec}, s: "-10", Format: DecimalSI}}, + } + for _, testCase := range table2 { + q := Quantity{d: infDecAmount{testCase.dec}, Format: DecimalSI} + // Won't currently get an error as MarshalTo can't return one + result, _ := q.Marshal() + q.Unmarshal(result) + if q.Cmp(testCase.expect) != 0 { + t.Errorf("Expected: %v, Actual: %v", testCase.expect, q) + } + } +} + +func TestQuantityProtoUnmarshal(t *testing.T) { + // Test when d is nil + table := []struct { + input Quantity + expect string + }{ + {Quantity{i: int64Amount{value: 0, scale: 0}, s: "0", Format: DecimalSI}, "0"}, + {Quantity{i: int64Amount{value: 100, scale: -3}, s: "100m", Format: DecimalSI}, "100m"}, + {Quantity{i: int64Amount{value: 50, scale: -3}, s: "50m", Format: DecimalSI}, "50m"}, + {Quantity{i: int64Amount{value: 10000, scale: 12}, s: "10000T", Format: DecimalSI}, "10000T"}, + } + for _, testCase := range table { + var inputQ Quantity + expectQ := MustParse(testCase.expect) + inputByteArray, _ := testCase.input.Marshal() + inputQ.Unmarshal(inputByteArray) + if inputQ.Cmp(expectQ) != 0 { + t.Errorf("Expected: %v, Actual: %v", inputQ, expectQ) + } + } + // Test when i is {0,0} + table2 := []struct { + input Quantity + expect *inf.Dec + }{ + {Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(0, 0).Dec}, s: "0", Format: DecimalSI}, dec(0, 0).Dec}, + {Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(10, 0).Dec}, s: "10", Format: DecimalSI}, dec(10, 0).Dec}, + {Quantity{i: int64Amount{value: 0, scale: 0}, d: infDecAmount{dec(-10, 0).Dec}, s: "-10", Format: DecimalSI}, dec(-10, 0).Dec}, + } + for _, testCase := range table2 { + var inputQ Quantity + expectQ := Quantity{d: infDecAmount{testCase.expect}, Format: DecimalSI} + inputByteArray, _ := testCase.input.Marshal() + inputQ.Unmarshal(inputByteArray) + if inputQ.Cmp(expectQ) != 0 { + t.Errorf("Expected: %v, Actual: %v", inputQ, expectQ) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_test.go new file mode 100644 index 0000000000..6553b0047d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/quantity_test.go @@ -0,0 +1,2035 @@ +/* +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 resource + +import ( + "encoding/json" + "fmt" + "math" + "math/big" + "math/rand" + "os" + "strings" + "testing" + "unicode" + + "github.com/google/go-cmp/cmp" + "github.com/spf13/pflag" + inf "gopkg.in/inf.v0" + "sigs.k8s.io/randfill" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "k8s.io/utils/ptr" +) + +var ( + bigMostPositive = big.NewInt(mostPositive) + bigMostNegative = big.NewInt(mostNegative) +) + +func dec(i int64, exponent int) infDecAmount { + // See the below test-- scale is the negative of an exponent. + return infDecAmount{inf.NewDec(i, inf.Scale(-exponent))} +} + +func bigDec(i *big.Int, exponent int) infDecAmount { + // See the below test-- scale is the negative of an exponent. + return infDecAmount{inf.NewDecBig(i, inf.Scale(-exponent))} +} + +func decQuantity(i int64, exponent int, format Format) Quantity { + return Quantity{d: dec(i, exponent), Format: format} +} + +func bigDecQuantity(i *big.Int, exponent int, format Format) Quantity { + return Quantity{d: bigDec(i, exponent), Format: format} +} + +func intQuantity(i int64, exponent Scale, format Format) Quantity { + return Quantity{i: int64Amount{value: i, scale: exponent}, Format: format} +} + +func TestDec(t *testing.T) { + table := []struct { + got infDecAmount + expect string + }{ + {dec(1, 0), "1"}, + {dec(1, 1), "10"}, + {dec(5, 2), "500"}, + {dec(8, 3), "8000"}, + {dec(2, 0), "2"}, + {dec(1, -1), "0.1"}, + {dec(3, -2), "0.03"}, + {dec(4, -3), "0.004"}, + } + + for _, item := range table { + if e, a := item.expect, item.got.Dec.String(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + } +} + +func TestBigDec(t *testing.T) { + table := []struct { + got infDecAmount + expect string + }{ + {bigDec(big.NewInt(1), 0), "1"}, + {bigDec(big.NewInt(1), 1), "10"}, + {bigDec(big.NewInt(5), 2), "500"}, + {bigDec(big.NewInt(8), 3), "8000"}, + {bigDec(big.NewInt(2), 0), "2"}, + {bigDec(big.NewInt(1), -1), "0.1"}, + {bigDec(big.NewInt(3), -2), "0.03"}, + {bigDec(big.NewInt(4), -3), "0.004"}, + {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 0), "9223372036854775808"}, + {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 1), "92233720368547758080"}, + {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 2), "922337203685477580800"}, + {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), -1), "922337203685477580.8"}, + {bigDec(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), -2), "92233720368547758.08"}, + {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), 0), "-9223372036854775809"}, + {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), 1), "-92233720368547758090"}, + {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), 2), "-922337203685477580900"}, + {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), -1), "-922337203685477580.9"}, + {bigDec(big.NewInt(0).Sub(bigMostNegative, big.NewInt(1)), -2), "-92233720368547758.09"}, + } + + for _, item := range table { + if e, a := item.expect, item.got.Dec.String(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + } +} + +// TestQuantityParseZero ensures that when a 0 quantity is passed, its string value is 0 +func TestQuantityParseZero(t *testing.T) { + zero := MustParse("0") + if expected, actual := "0", zero.String(); expected != actual { + t.Errorf("Expected %v, actual %v", expected, actual) + } +} + +// TestQuantityParseNonNumericPanic ensures that when a non-numeric string is parsed +// it panics +func TestQuantityParseNonNumericPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("MustParse did not panic") + } + }() + _ = MustParse("Non-Numeric") +} + +// TestQuantityAddZeroPreservesSuffix verifies that a suffix is preserved +// independent of the order of operations when adding a zero and non-zero val +func TestQuantityAddZeroPreservesSuffix(t *testing.T) { + testValues := []string{"100m", "1Gi"} + zero := MustParse("0") + for _, testValue := range testValues { + value := MustParse(testValue) + v1 := value.DeepCopy() + // ensure non-zero + zero = non-zero (suffix preserved) + v1.Add(zero) + // ensure zero + non-zero = non-zero (suffix preserved) + v2 := zero.DeepCopy() + v2.Add(value) + + if v1.String() != testValue { + t.Errorf("Expected %v, actual %v", testValue, v1.String()) + continue + } + if v2.String() != testValue { + t.Errorf("Expected %v, actual %v", testValue, v2.String()) + } + } +} + +// TestQuantitySubZeroPreservesSuffix verifies that a suffix is preserved +// independent of the order of operations when subtracting a zero and non-zero val +func TestQuantitySubZeroPreservesSuffix(t *testing.T) { + testValues := []string{"100m", "1Gi"} + zero := MustParse("0") + for _, testValue := range testValues { + value := MustParse(testValue) + v1 := value.DeepCopy() + // ensure non-zero - zero = non-zero (suffix preserved) + v1.Sub(zero) + // ensure we preserved the input value + if v1.String() != testValue { + t.Errorf("Expected %v, actual %v", testValue, v1.String()) + } + + // ensure zero - non-zero = -non-zero (suffix preserved) + v2 := zero.DeepCopy() + v2.Sub(value) + negVal := value.DeepCopy() + negVal.Neg() + if v2.String() != negVal.String() { + t.Errorf("Expected %v, actual %v", negVal.String(), v2.String()) + } + } +} + +// TestQuantityCanocicalizeZero verifies that you get 0 as canonical value if internal value is 0, and not 0 +func TestQuantityCanocicalizeZero(t *testing.T) { + val := MustParse("1000m") + val.i.Sub(int64Amount{value: 1}) + zero := Quantity{i: val.i, Format: DecimalSI} + if expected, actual := "0", zero.String(); expected != actual { + t.Errorf("Expected %v, actual %v", expected, actual) + } +} + +func TestQuantityCmp(t *testing.T) { + // Test when d is nil + table := []struct { + x string + y string + expect int + }{ + {"0", "0", 0}, + {"100m", "50m", 1}, + {"50m", "100m", -1}, + {"10000T", "100Gi", 1}, + } + for _, testCase := range table { + q1 := MustParse(testCase.x) + q2 := MustParse(testCase.y) + if result := q1.Cmp(q2); result != testCase.expect { + t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", testCase.x, testCase.y, testCase.expect, result) + } + } + // Test when i is {0,0} + table2 := []struct { + x *inf.Dec + y *inf.Dec + expect int + }{ + {dec(0, 0).Dec, dec(0, 0).Dec, 0}, + {nil, dec(0, 0).Dec, 0}, + {dec(0, 0).Dec, nil, 0}, + {nil, nil, 0}, + {nil, dec(10, 0).Dec, -1}, + {nil, dec(-10, 0).Dec, 1}, + {dec(10, 0).Dec, nil, 1}, + {dec(-10, 0).Dec, nil, -1}, + } + for _, testCase := range table2 { + q1 := Quantity{d: infDecAmount{testCase.x}, Format: DecimalSI} + q2 := Quantity{d: infDecAmount{testCase.y}, Format: DecimalSI} + if result := q1.Cmp(q2); result != testCase.expect { + t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", testCase.x, testCase.y, testCase.expect, result) + } + } +} + +func TestParseQuantityString(t *testing.T) { + table := []struct { + input string + positive bool + value string + num, denom, suffix string + }{ + {"0.025Ti", true, "0.025", "0", "025", "Ti"}, + {"1.025Ti", true, "1.025", "1", "025", "Ti"}, + {"-1.025Ti", false, "-1.025", "1", "025", "Ti"}, + {".", true, ".", "0", "", ""}, + {"-.", false, "-.", "0", "", ""}, + {"1E-3", true, "1", "1", "", "E-3"}, + } + for _, test := range table { + positive, value, num, denom, suffix, err := parseQuantityString(test.input) + if err != nil { + t.Errorf("%s: error: %v", test.input, err) + continue + } + if positive != test.positive || value != test.value || num != test.num || denom != test.denom || suffix != test.suffix { + t.Errorf("%s: unmatched: %t %q %q %q %q", test.input, positive, value, num, denom, suffix) + } + } +} + +func TestQuantityParse(t *testing.T) { + if _, err := ParseQuantity(""); err == nil { + t.Errorf("expected empty string to return error") + } + + table := []struct { + input string + expect Quantity + }{ + {"0", decQuantity(0, 0, DecimalSI)}, + {"0n", decQuantity(0, 0, DecimalSI)}, + {"0u", decQuantity(0, 0, DecimalSI)}, + {"0m", decQuantity(0, 0, DecimalSI)}, + {"0Ki", decQuantity(0, 0, BinarySI)}, + {"0k", decQuantity(0, 0, DecimalSI)}, + {"0Mi", decQuantity(0, 0, BinarySI)}, + {"0M", decQuantity(0, 0, DecimalSI)}, + {"0Gi", decQuantity(0, 0, BinarySI)}, + {"0G", decQuantity(0, 0, DecimalSI)}, + {"0Ti", decQuantity(0, 0, BinarySI)}, + {"0T", decQuantity(0, 0, DecimalSI)}, + + // Quantity less numbers are allowed + {"1", decQuantity(1, 0, DecimalSI)}, + + // Binary suffixes + {"1Ki", decQuantity(1024, 0, BinarySI)}, + {"8Ki", decQuantity(8*1024, 0, BinarySI)}, + {"7Mi", decQuantity(7*1024*1024, 0, BinarySI)}, + {"6Gi", decQuantity(6*1024*1024*1024, 0, BinarySI)}, + {"5Ti", decQuantity(5*1024*1024*1024*1024, 0, BinarySI)}, + {"4Pi", decQuantity(4*1024*1024*1024*1024*1024, 0, BinarySI)}, + {"3Ei", decQuantity(3*1024*1024*1024*1024*1024*1024, 0, BinarySI)}, + + {"10Ti", decQuantity(10*1024*1024*1024*1024, 0, BinarySI)}, + {"100Ti", decQuantity(100*1024*1024*1024*1024, 0, BinarySI)}, + + // Decimal suffixes + {"5n", decQuantity(5, -9, DecimalSI)}, + {"4u", decQuantity(4, -6, DecimalSI)}, + {"3m", decQuantity(3, -3, DecimalSI)}, + {"9", decQuantity(9, 0, DecimalSI)}, + {"8k", decQuantity(8, 3, DecimalSI)}, + {"50k", decQuantity(5, 4, DecimalSI)}, + {"7M", decQuantity(7, 6, DecimalSI)}, + {"6G", decQuantity(6, 9, DecimalSI)}, + {"5T", decQuantity(5, 12, DecimalSI)}, + {"40T", decQuantity(4, 13, DecimalSI)}, + {"300T", decQuantity(3, 14, DecimalSI)}, + {"2P", decQuantity(2, 15, DecimalSI)}, + {"1E", decQuantity(1, 18, DecimalSI)}, + + // Decimal exponents + {"1E-3", decQuantity(1, -3, DecimalExponent)}, + {"1e3", decQuantity(1, 3, DecimalExponent)}, + {"1E6", decQuantity(1, 6, DecimalExponent)}, + {"1e9", decQuantity(1, 9, DecimalExponent)}, + {"1E12", decQuantity(1, 12, DecimalExponent)}, + {"1e15", decQuantity(1, 15, DecimalExponent)}, + {"1E18", decQuantity(1, 18, DecimalExponent)}, + + // Nonstandard but still parsable + {"1e14", decQuantity(1, 14, DecimalExponent)}, + {"1e13", decQuantity(1, 13, DecimalExponent)}, + {"1e3", decQuantity(1, 3, DecimalExponent)}, + {"100.035k", decQuantity(100035, 0, DecimalSI)}, + + // Things that look like floating point + {"0.001", decQuantity(1, -3, DecimalSI)}, + {"0.0005k", decQuantity(5, -1, DecimalSI)}, + {"0.005", decQuantity(5, -3, DecimalSI)}, + {"0.05", decQuantity(5, -2, DecimalSI)}, + {"0.5", decQuantity(5, -1, DecimalSI)}, + {"0.00050k", decQuantity(5, -1, DecimalSI)}, + {"0.00500", decQuantity(5, -3, DecimalSI)}, + {"0.05000", decQuantity(5, -2, DecimalSI)}, + {"0.50000", decQuantity(5, -1, DecimalSI)}, + {"0.5e0", decQuantity(5, -1, DecimalExponent)}, + {"0.5e-1", decQuantity(5, -2, DecimalExponent)}, + {"0.5e-2", decQuantity(5, -3, DecimalExponent)}, + {"0.5e0", decQuantity(5, -1, DecimalExponent)}, + {"10.035M", decQuantity(10035, 3, DecimalSI)}, + + {"1.2e3", decQuantity(12, 2, DecimalExponent)}, + {"1.3E+6", decQuantity(13, 5, DecimalExponent)}, + {"1.40e9", decQuantity(14, 8, DecimalExponent)}, + {"1.53E12", decQuantity(153, 10, DecimalExponent)}, + {"1.6e15", decQuantity(16, 14, DecimalExponent)}, + {"1.7E18", decQuantity(17, 17, DecimalExponent)}, + + {"9.01", decQuantity(901, -2, DecimalSI)}, + {"8.1k", decQuantity(81, 2, DecimalSI)}, + {"7.123456M", decQuantity(7123456, 0, DecimalSI)}, + {"6.987654321G", decQuantity(6987654321, 0, DecimalSI)}, + {"5.444T", decQuantity(5444, 9, DecimalSI)}, + {"40.1T", decQuantity(401, 11, DecimalSI)}, + {"300.2T", decQuantity(3002, 11, DecimalSI)}, + {"2.5P", decQuantity(25, 14, DecimalSI)}, + {"1.01E", decQuantity(101, 16, DecimalSI)}, + + // Things that saturate/round + {"3.001n", decQuantity(4, -9, DecimalSI)}, + {"1.1E-9", decQuantity(2, -9, DecimalExponent)}, + {"0.0000000001", decQuantity(1, -9, DecimalSI)}, + {"0.0000000005", decQuantity(1, -9, DecimalSI)}, + {"0.00000000050", decQuantity(1, -9, DecimalSI)}, + {"0.5e-9", decQuantity(1, -9, DecimalExponent)}, + {"0.9n", decQuantity(1, -9, DecimalSI)}, + {"0.00000012345", decQuantity(124, -9, DecimalSI)}, + {"0.00000012354", decQuantity(124, -9, DecimalSI)}, + {"9Ei", Quantity{d: maxAllowed, Format: BinarySI}}, + {"9223372036854775807Ki", Quantity{d: maxAllowed, Format: BinarySI}}, + {"12E", decQuantity(12, 18, DecimalSI)}, + + // We'll accept fractional binary stuff, too. + {"100.035Ki", decQuantity(10243584, -2, BinarySI)}, + {"0.5Mi", decQuantity(.5*1024*1024, 0, BinarySI)}, + {"0.05Gi", decQuantity(536870912, -1, BinarySI)}, + {"0.025Ti", decQuantity(274877906944, -1, BinarySI)}, + + // Things written by trolls + {"0.000000000001Ki", decQuantity(2, -9, DecimalSI)}, // rounds up, changes format + {".001", decQuantity(1, -3, DecimalSI)}, + {".0001k", decQuantity(100, -3, DecimalSI)}, + {"1.", decQuantity(1, 0, DecimalSI)}, + {"1.G", decQuantity(1, 9, DecimalSI)}, + } + + for _, asDec := range []bool{false, true} { + for _, item := range table { + got, err := ParseQuantity(item.input) + if err != nil { + t.Errorf("%v: unexpected error: %v", item.input, err) + continue + } + if asDec { + got.AsDec() + } + + if e, a := item.expect, got; e.Cmp(a) != 0 { + t.Errorf("%v: expected %v, got %v", item.input, e.String(), a.String()) + } + if e, a := item.expect.Format, got.Format; e != a { + t.Errorf("%v: expected %#v, got %#v", item.input, e, a) + } + + if asDec { + if i, ok := got.AsInt64(); i != 0 || ok { + t.Errorf("%v: expected inf.Dec to return false for AsInt64: %d", item.input, i) + } + continue + } + i, ok := item.expect.AsInt64() + if !ok { + continue + } + j, ok := got.AsInt64() + if !ok { + if got.d.Dec == nil && got.i.scale >= 0 { + t.Errorf("%v: is an int64Amount, but can't return AsInt64: %v", item.input, got) + } + continue + } + if i != j { + t.Errorf("%v: expected equivalent representation as int64: %d %d", item.input, i, j) + } + } + + for _, item := range table { + got, err := ParseQuantity(item.input) + if err != nil { + t.Errorf("%v: unexpected error: %v", item.input, err) + continue + } + + if asDec { + got.AsDec() + } + + for _, format := range []Format{DecimalSI, BinarySI, DecimalExponent} { + // ensure we are not simply checking pointer equality by creating a new inf.Dec + var copied inf.Dec + copied.Add(inf.NewDec(0, inf.Scale(0)), got.AsDec()) + q := NewDecimalQuantity(copied, format) + if c := q.Cmp(got); c != 0 { + t.Errorf("%v: round trip from decimal back to quantity is not comparable: %d: %#v vs %#v", item.input, c, got, q) + } + } + + // verify that we can decompose the input and get the same result by building up from the base. + positive, _, num, denom, suffix, err := parseQuantityString(item.input) + if err != nil { + t.Errorf("%v: unexpected error: %v", item.input, err) + continue + } + if got.Sign() >= 0 && !positive || got.Sign() < 0 && positive { + t.Errorf("%v: positive was incorrect: %t", item.input, positive) + continue + } + var value string + if !positive { + value = "-" + } + value += num + if len(denom) > 0 { + value += "." + denom + } + value += suffix + if len(value) == 0 { + t.Errorf("%v: did not parse correctly, %q %q %q", item.input, num, denom, suffix) + } + expected, err := ParseQuantity(value) + if err != nil { + t.Errorf("%v: unexpected error for %s: %v", item.input, value, err) + continue + } + if expected.Cmp(got) != 0 { + t.Errorf("%v: not the same as %s", item.input, value) + continue + } + } + + // Try the negative version of everything + desired := &inf.Dec{} + expect := Quantity{d: infDecAmount{Dec: desired}} + for _, item := range table { + got, err := ParseQuantity("-" + strings.TrimLeftFunc(item.input, unicode.IsSpace)) + if err != nil { + t.Errorf("-%v: unexpected error: %v", item.input, err) + continue + } + if asDec { + got.AsDec() + } + + expected := item.expect + desired.Neg(expected.AsDec()) + + if e, a := expect, got; e.Cmp(a) != 0 { + t.Errorf("%v: expected %s, got %s", item.input, e.String(), a.String()) + } + if e, a := expected.Format, got.Format; e != a { + t.Errorf("%v: expected %#v, got %#v", item.input, e, a) + } + } + + // Try everything with an explicit + + for _, item := range table { + got, err := ParseQuantity("+" + strings.TrimLeftFunc(item.input, unicode.IsSpace)) + if err != nil { + t.Errorf("-%v: unexpected error: %v", item.input, err) + continue + } + if asDec { + got.AsDec() + } + + if e, a := item.expect, got; e.Cmp(a) != 0 { + t.Errorf("%v(%t): expected %s, got %s", item.input, asDec, e.String(), a.String()) + } + if e, a := item.expect.Format, got.Format; e != a { + t.Errorf("%v: expected %#v, got %#v", item.input, e, a) + } + } + } + + invalid := []string{ + "1.1.M", + "1+1.0M", + "0.1mi", + "0.1am", + "aoeu", + ".5i", + "1i", + "-3.01i", + "-3.01e-", + + // trailing whitespace is forbidden + " 1", + "1 ", + } + for _, item := range invalid { + _, err := ParseQuantity(item) + if err == nil { + t.Errorf("%v parsed unexpectedly", item) + } + } +} + +func TestQuantityRoundUp(t *testing.T) { + table := []struct { + in string + scale Scale + expect Quantity + ok bool + }{ + {"9.01", -3, decQuantity(901, -2, DecimalSI), true}, + {"9.01", -2, decQuantity(901, -2, DecimalSI), true}, + {"9.01", -1, decQuantity(91, -1, DecimalSI), false}, + {"9.01", 0, decQuantity(10, 0, DecimalSI), false}, + {"9.01", 1, decQuantity(10, 0, DecimalSI), false}, + {"9.01", 2, decQuantity(100, 0, DecimalSI), false}, + + {"-9.01", -3, decQuantity(-901, -2, DecimalSI), true}, + {"-9.01", -2, decQuantity(-901, -2, DecimalSI), true}, + {"-9.01", -1, decQuantity(-91, -1, DecimalSI), false}, + {"-9.01", 0, decQuantity(-10, 0, DecimalSI), false}, + {"-9.01", 1, decQuantity(-10, 0, DecimalSI), false}, + {"-9.01", 2, decQuantity(-100, 0, DecimalSI), false}, + } + + for _, asDec := range []bool{false, true} { + for _, item := range table { + got, err := ParseQuantity(item.in) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expect := item.expect.DeepCopy() + if asDec { + got.AsDec() + } + if ok := got.RoundUp(item.scale); ok != item.ok { + t.Errorf("%s(%d,%t): unexpected ok: %t", item.in, item.scale, asDec, ok) + } + if got.Cmp(expect) != 0 { + t.Errorf("%s(%d,%t): unexpected round: %s vs %s", item.in, item.scale, asDec, got.String(), expect.String()) + } + } + } +} + +func TestQuantityCmpInt64AndDec(t *testing.T) { + table := []struct { + a, b Quantity + cmp int + }{ + {intQuantity(901, -2, DecimalSI), intQuantity(901, -2, DecimalSI), 0}, + {intQuantity(90, -1, DecimalSI), intQuantity(901, -2, DecimalSI), -1}, + {intQuantity(901, -2, DecimalSI), intQuantity(900, -2, DecimalSI), 1}, + {intQuantity(0, 0, DecimalSI), intQuantity(0, 0, DecimalSI), 0}, + {intQuantity(0, 1, DecimalSI), intQuantity(0, -1, DecimalSI), 0}, + {intQuantity(0, -1, DecimalSI), intQuantity(0, 1, DecimalSI), 0}, + {intQuantity(800, -3, DecimalSI), intQuantity(1, 0, DecimalSI), -1}, + {intQuantity(800, -3, DecimalSI), intQuantity(79, -2, DecimalSI), 1}, + + {intQuantity(mostPositive, 0, DecimalSI), intQuantity(1, -1, DecimalSI), 1}, + {intQuantity(mostPositive, 1, DecimalSI), intQuantity(1, 0, DecimalSI), 1}, + {intQuantity(mostPositive, 1, DecimalSI), intQuantity(1, 1, DecimalSI), 1}, + {intQuantity(mostPositive, 1, DecimalSI), intQuantity(0, 1, DecimalSI), 1}, + {intQuantity(mostPositive, -16, DecimalSI), intQuantity(1, 3, DecimalSI), -1}, + + {intQuantity(mostNegative, 0, DecimalSI), intQuantity(0, 0, DecimalSI), -1}, + {intQuantity(mostNegative, -18, DecimalSI), intQuantity(-1, 0, DecimalSI), -1}, + {intQuantity(mostNegative, -19, DecimalSI), intQuantity(-1, 0, DecimalSI), 1}, + + {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(1, 1, DecimalSI), 0}, + {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(-10, 0, DecimalSI), 1}, + {intQuantity(-1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(-10, 0, DecimalSI), 0}, + {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(1, 0, DecimalSI), 1}, + + {intQuantity(1*1000000*1000000*1000000+1, -17, DecimalSI), intQuantity(1, 1, DecimalSI), 1}, + {intQuantity(1*1000000*1000000*1000000-1, -17, DecimalSI), intQuantity(1, 1, DecimalSI), -1}, + } + + for _, item := range table { + if cmp := item.a.Cmp(item.b); cmp != item.cmp { + t.Errorf("%#v: unexpected Cmp: %d", item, cmp) + } + if cmp := item.b.Cmp(item.a); cmp != -item.cmp { + t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) + } + } + + for _, item := range table { + a, b := item.a.DeepCopy(), item.b.DeepCopy() + a.AsDec() + if cmp := a.Cmp(b); cmp != item.cmp { + t.Errorf("%#v: unexpected Cmp: %d", item, cmp) + } + if cmp := b.Cmp(a); cmp != -item.cmp { + t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) + } + } + + for _, item := range table { + a, b := item.a.DeepCopy(), item.b.DeepCopy() + b.AsDec() + if cmp := a.Cmp(b); cmp != item.cmp { + t.Errorf("%#v: unexpected Cmp: %d", item, cmp) + } + if cmp := b.Cmp(a); cmp != -item.cmp { + t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) + } + } + + for _, item := range table { + a, b := item.a.DeepCopy(), item.b.DeepCopy() + a.AsDec() + b.AsDec() + if cmp := a.Cmp(b); cmp != item.cmp { + t.Errorf("%#v: unexpected Cmp: %d", item, cmp) + } + if cmp := b.Cmp(a); cmp != -item.cmp { + t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) + } + } +} + +func TestQuantityNeg(t *testing.T) { + table := []struct { + a Quantity + out string + }{ + {intQuantity(901, -2, DecimalSI), "-9010m"}, + {decQuantity(901, -2, DecimalSI), "-9010m"}, + } + + for i, item := range table { + out := item.a.DeepCopy() + out.Neg() + if out.Cmp(item.a) == 0 { + t.Errorf("%d: negating an item should not mutate the source: %s", i, out.String()) + } + if out.String() != item.out { + t.Errorf("%d: negating did not equal exact value: %s", i, out.String()) + } + } +} + +func TestQuantityString(t *testing.T) { + table := []struct { + in Quantity + expect string + alternate string + }{ + {decQuantity(1024*1024*1024, 0, BinarySI), "1Gi", "1024Mi"}, + {decQuantity(300*1024*1024, 0, BinarySI), "300Mi", "307200Ki"}, + {decQuantity(6*1024, 0, BinarySI), "6Ki", ""}, + {decQuantity(1001*1024*1024*1024, 0, BinarySI), "1001Gi", "1025024Mi"}, + {decQuantity(1024*1024*1024*1024, 0, BinarySI), "1Ti", "1024Gi"}, + {decQuantity(5, 0, BinarySI), "5", "5000m"}, + {decQuantity(500, -3, BinarySI), "500m", "0.5"}, + {decQuantity(1, 9, DecimalSI), "1G", "1000M"}, + {decQuantity(1000, 6, DecimalSI), "1G", "0.001T"}, + {decQuantity(1000000, 3, DecimalSI), "1G", ""}, + {decQuantity(1000000000, 0, DecimalSI), "1G", ""}, + {decQuantity(1, -3, DecimalSI), "1m", "1000u"}, + {decQuantity(80, -3, DecimalSI), "80m", ""}, + {decQuantity(1080, -3, DecimalSI), "1080m", "1.08"}, + {decQuantity(108, -2, DecimalSI), "1080m", "1080000000n"}, + {decQuantity(10800, -4, DecimalSI), "1080m", ""}, + {decQuantity(300, 6, DecimalSI), "300M", ""}, + {decQuantity(1, 12, DecimalSI), "1T", ""}, + {decQuantity(1234567, 6, DecimalSI), "1234567M", ""}, + {decQuantity(1234567, -3, BinarySI), "1234567m", ""}, + {decQuantity(3, 3, DecimalSI), "3k", ""}, + {decQuantity(1025, 0, BinarySI), "1025", ""}, + {decQuantity(0, 0, DecimalSI), "0", ""}, + {decQuantity(0, 0, BinarySI), "0", ""}, + {decQuantity(1, 9, DecimalExponent), "1e9", ".001e12"}, + {decQuantity(1, -3, DecimalExponent), "1e-3", "0.001e0"}, + {decQuantity(1, -9, DecimalExponent), "1e-9", "1000e-12"}, + {decQuantity(80, -3, DecimalExponent), "80e-3", ""}, + {decQuantity(300, 6, DecimalExponent), "300e6", ""}, + {decQuantity(1, 12, DecimalExponent), "1e12", ""}, + {decQuantity(1, 3, DecimalExponent), "1e3", ""}, + {decQuantity(3, 3, DecimalExponent), "3e3", ""}, + {decQuantity(3, 3, DecimalSI), "3k", ""}, + {decQuantity(0, 0, DecimalExponent), "0", "00"}, + {decQuantity(1, -9, DecimalSI), "1n", ""}, + {decQuantity(80, -9, DecimalSI), "80n", ""}, + {decQuantity(1080, -9, DecimalSI), "1080n", ""}, + {decQuantity(108, -8, DecimalSI), "1080n", ""}, + {decQuantity(10800, -10, DecimalSI), "1080n", ""}, + {decQuantity(1, -6, DecimalSI), "1u", ""}, + {decQuantity(80, -6, DecimalSI), "80u", ""}, + {decQuantity(1080, -6, DecimalSI), "1080u", ""}, + } + for _, item := range table { + got := item.in.String() + if e, a := item.expect, got; e != a { + t.Errorf("%#v: expected %v, got %v", item.in, e, a) + } + q, err := ParseQuantity(item.expect) + if err != nil { + t.Errorf("%#v: unexpected error: %v", item.expect, err) + } + if len(q.s) == 0 || q.s != item.expect { + t.Errorf("%#v: did not copy canonical string on parse: %s", item.expect, q.s) + } + if len(item.alternate) == 0 { + continue + } + q, err = ParseQuantity(item.alternate) + if err != nil { + t.Errorf("%#v: unexpected error: %v", item.expect, err) + continue + } + if len(q.s) != 0 { + t.Errorf("%#v: unexpected nested string: %v", item.expect, q.s) + } + if q.String() != item.expect { + t.Errorf("%#v: unexpected alternate canonical: %v", item.expect, q.String()) + } + if len(q.s) == 0 || q.s != item.expect { + t.Errorf("%#v: did not set canonical string on ToString: %s", item.expect, q.s) + } + } + desired := &inf.Dec{} // Avoid modifying the values in the table. + for _, item := range table { + if item.in.Cmp(Quantity{}) == 0 { + // Don't expect it to print "-0" ever + continue + } + q := item.in + q.d = infDecAmount{desired.Neg(q.AsDec())} + if e, a := "-"+item.expect, q.String(); e != a { + t.Errorf("%#v: expected %v, got %v", item.in, e, a) + } + } +} + +func TestQuantityParseEmit(t *testing.T) { + table := []struct { + in string + expect string + }{ + {"1Ki", "1Ki"}, + {"1Mi", "1Mi"}, + {"1Gi", "1Gi"}, + {"1024Mi", "1Gi"}, + {"1000M", "1G"}, + {".001Ki", "1024m"}, + {".000001Ki", "1024u"}, + {".000000001Ki", "1024n"}, + {".000000000001Ki", "2n"}, + } + + for _, item := range table { + q, err := ParseQuantity(item.in) + if err != nil { + t.Errorf("Couldn't parse %v", item.in) + continue + } + if e, a := item.expect, q.String(); e != a { + t.Errorf("%#v: expected %v, got %v", item.in, e, a) + } + } + for _, item := range table { + q, err := ParseQuantity("-" + item.in) + if err != nil { + t.Errorf("Couldn't parse %v", item.in) + continue + } + if q.Cmp(Quantity{}) == 0 { + continue + } + if e, a := "-"+item.expect, q.String(); e != a { + t.Errorf("%#v: expected %v, got %v (%#v)", item.in, e, a, q.i) + } + } +} + +var fuzzer = randfill.New().Funcs( + func(q *Quantity, c randfill.Continue) { + q.i = Zero + if c.Bool() { + q.Format = BinarySI + if c.Bool() { + dec := &inf.Dec{} + q.d = infDecAmount{Dec: dec} + dec.SetScale(0) + dec.SetUnscaled(c.Int63()) + return + } + // Be sure to test cases like 1Mi + dec := &inf.Dec{} + q.d = infDecAmount{Dec: dec} + dec.SetScale(0) + dec.SetUnscaled(c.Int63n(1024) << uint(10*c.Intn(5))) + return + } + if c.Bool() { + q.Format = DecimalSI + } else { + q.Format = DecimalExponent + } + if c.Bool() { + dec := &inf.Dec{} + q.d = infDecAmount{Dec: dec} + dec.SetScale(inf.Scale(c.Intn(4))) + dec.SetUnscaled(c.Int63()) + return + } + // Be sure to test cases like 1M + dec := &inf.Dec{} + q.d = infDecAmount{Dec: dec} + dec.SetScale(inf.Scale(3 - c.Intn(15))) + dec.SetUnscaled(c.Int63n(1000)) + }, +) + +func TestQuantityDeepCopy(t *testing.T) { + // Test when d is nil + slice := []string{"0", "100m", "50m", "10000T"} + for _, testCase := range slice { + q := MustParse(testCase) + if result := q.DeepCopy(); result != q { + t.Errorf("Expected: %v, Actual: %v", q, result) + } + } + table := []*inf.Dec{ + dec(0, 0).Dec, + dec(10, 0).Dec, + dec(-10, 0).Dec, + } + // Test when i is {0,0} + for _, testCase := range table { + q := Quantity{d: infDecAmount{testCase}, Format: DecimalSI} + result := q.DeepCopy() + if q.d.Cmp(result.AsDec()) != 0 { + t.Errorf("Expected: %v, Actual: %v", q.String(), result.String()) + } + result = Quantity{d: infDecAmount{dec(2, 0).Dec}, Format: DecimalSI} + if q.d.Cmp(result.AsDec()) == 0 { + t.Errorf("Modifying result has affected q") + } + } +} + +func TestJSON(t *testing.T) { + for i := 0; i < 500; i++ { + q := &Quantity{} + fuzzer.Fill(q) + b, err := json.Marshal(q) + if err != nil { + t.Errorf("error encoding %v: %v", q, err) + continue + } + q2 := &Quantity{} + err = json.Unmarshal(b, q2) + if err != nil { + t.Logf("%d: %s", i, string(b)) + t.Errorf("%v: error decoding %v: %v", q, string(b), err) + } + if q2.Cmp(*q) != 0 { + t.Errorf("Expected equal: %v, %v (json was '%v')", q, q2, string(b)) + } + } +} + +func TestJSONWhitespace(t *testing.T) { + q := Quantity{} + testCases := []struct { + in string + expect string + }{ + {`" 1"`, "1"}, + {`"1 "`, "1"}, + {`1`, "1"}, + {` 1`, "1"}, + {`1 `, "1"}, + {`10`, "10"}, + {`-1`, "-1"}, + {` -1`, "-1"}, + } + for _, test := range testCases { + if err := json.Unmarshal([]byte(test.in), &q); err != nil { + t.Errorf("%q: %v", test.in, err) + } + if q.String() != test.expect { + t.Errorf("unexpected string: %q", q.String()) + } + } +} + +func TestMilliNewSet(t *testing.T) { + table := []struct { + value int64 + format Format + expect string + exact bool + }{ + {1, DecimalSI, "1m", true}, + {1000, DecimalSI, "1", true}, + {1234000, DecimalSI, "1234", true}, + {1024, BinarySI, "1024m", false}, // Format changes + {1000000, "invalidFormatDefaultsToExponent", "1e3", true}, + {1024 * 1024, BinarySI, "1048576m", false}, // Format changes + } + + for _, item := range table { + q := NewMilliQuantity(item.value, item.format) + if e, a := item.expect, q.String(); e != a { + t.Errorf("Expected %v, got %v; %#v", e, a, q) + } + if !item.exact { + continue + } + q2, err := ParseQuantity(q.String()) + if err != nil { + t.Errorf("Round trip failed on %v", q) + } + if e, a := item.value, q2.MilliValue(); e != a { + t.Errorf("Expected %v, got %v", e, a) + } + } + + for _, item := range table { + q := NewQuantity(0, item.format) + q.SetMilli(item.value) + if e, a := item.expect, q.String(); e != a { + t.Errorf("Set: Expected %v, got %v; %#v", e, a, q) + } + } +} + +func TestNewSet(t *testing.T) { + table := []struct { + value int64 + format Format + expect string + }{ + {1, DecimalSI, "1"}, + {1000, DecimalSI, "1k"}, + {1234000, DecimalSI, "1234k"}, + {1024, BinarySI, "1Ki"}, + {1000000, "invalidFormatDefaultsToExponent", "1e6"}, + {1024 * 1024, BinarySI, "1Mi"}, + } + + for _, asDec := range []bool{false, true} { + for _, item := range table { + q := NewQuantity(item.value, item.format) + if asDec { + q.ToDec() + } + if e, a := item.expect, q.String(); e != a { + t.Errorf("Expected %v, got %v; %#v", e, a, q) + } + q2, err := ParseQuantity(q.String()) + if err != nil { + t.Errorf("Round trip failed on %v", q) + } + if e, a := item.value, q2.Value(); e != a { + t.Errorf("Expected %v, got %v", e, a) + } + } + + for _, item := range table { + q := NewQuantity(0, item.format) + q.Set(item.value) + if asDec { + q.ToDec() + } + if e, a := item.expect, q.String(); e != a { + t.Errorf("Set: Expected %v, got %v; %#v", e, a, q) + } + } + } +} + +func TestNewScaledSet(t *testing.T) { + table := []struct { + value int64 + scale Scale + expect string + }{ + {1, Nano, "1n"}, + {1000, Nano, "1u"}, + {1, Micro, "1u"}, + {1000, Micro, "1m"}, + {1, Milli, "1m"}, + {1000, Milli, "1"}, + {1, 0, "1"}, + {0, Nano, "0"}, + {0, Micro, "0"}, + {0, Milli, "0"}, + {0, 0, "0"}, + } + + for _, item := range table { + q := NewScaledQuantity(item.value, item.scale) + if e, a := item.expect, q.String(); e != a { + t.Errorf("Expected %v, got %v; %#v", e, a, q) + } + q2, err := ParseQuantity(q.String()) + if err != nil { + t.Errorf("Round trip failed on %v", q) + } + if e, a := item.value, q2.ScaledValue(item.scale); e != a { + t.Errorf("Expected %v, got %v", e, a) + } + q3 := NewQuantity(0, DecimalSI) + q3.SetScaled(item.value, item.scale) + if q.Cmp(*q3) != 0 { + t.Errorf("Expected %v and %v to be equal", q, q3) + } + } +} + +func TestScaledValue(t *testing.T) { + table := []struct { + fromScale Scale + toScale Scale + expected int64 + }{ + {Nano, Nano, 1}, + {Nano, Micro, 1}, + {Nano, Milli, 1}, + {Nano, 0, 1}, + {Micro, Nano, 1000}, + {Micro, Micro, 1}, + {Micro, Milli, 1}, + {Micro, 0, 1}, + {Milli, Nano, 1000 * 1000}, + {Milli, Micro, 1000}, + {Milli, Milli, 1}, + {Milli, 0, 1}, + {0, Nano, 1000 * 1000 * 1000}, + {0, Micro, 1000 * 1000}, + {0, Milli, 1000}, + {0, 0, 1}, + {2, -2, 100 * 100}, + } + + for _, item := range table { + q := NewScaledQuantity(1, item.fromScale) + if e, a := item.expected, q.ScaledValue(item.toScale); e != a { + t.Errorf("%v to %v: Expected %v, got %v", item.fromScale, item.toScale, e, a) + } + } +} + +func TestUninitializedNoCrash(t *testing.T) { + var q Quantity + + q.Value() + q.MilliValue() + q.DeepCopy() + _ = q.String() + q.MarshalJSON() +} + +func TestDeepCopy(t *testing.T) { + q := NewQuantity(5, DecimalSI) + c := q.DeepCopy() + c.Set(6) + if q.Value() == 6 { + t.Errorf("Copy didn't") + } +} + +func TestSub(t *testing.T) { + tests := []struct { + a Quantity + b Quantity + expected Quantity + }{ + {decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(0, 0, DecimalSI)}, + {decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(9, 0, DecimalSI)}, + {decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(9, 0, BinarySI)}, + {Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(-50, 0, DecimalSI)}, + {decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI)}, + {Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)}, + } + + for i, test := range tests { + test.a.Sub(test.b) + if test.a.Cmp(test.expected) != 0 { + t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String()) + } + } +} + +func TestNeg(t *testing.T) { + tests := []struct { + a Quantity + b Quantity + expected Quantity + }{ + {a: intQuantity(0, 0, DecimalSI), expected: intQuantity(0, 0, DecimalSI)}, + {a: Quantity{}, expected: Quantity{}}, + {a: intQuantity(10, 0, BinarySI), expected: intQuantity(-10, 0, BinarySI)}, + {a: intQuantity(-10, 0, BinarySI), expected: intQuantity(10, 0, BinarySI)}, + {a: decQuantity(0, 0, DecimalSI), expected: intQuantity(0, 0, DecimalSI)}, + {a: decQuantity(10, 0, BinarySI), expected: intQuantity(-10, 0, BinarySI)}, + {a: decQuantity(-10, 0, BinarySI), expected: intQuantity(10, 0, BinarySI)}, + } + + for i, test := range tests { + a := test.a.DeepCopy() + a.Neg() + // ensure value is same + if a.Cmp(test.expected) != 0 { + t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), a.String()) + } + } +} + +func TestAdd(t *testing.T) { + tests := []struct { + a Quantity + b Quantity + expected Quantity + }{ + {decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(20, 0, DecimalSI)}, + {decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(11, 0, DecimalSI)}, + {decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(11, 0, BinarySI)}, + {Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(50, 0, DecimalSI)}, + {decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI)}, + {Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)}, + } + + for i, test := range tests { + test.a.Add(test.b) + if test.a.Cmp(test.expected) != 0 { + t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String()) + } + } +} + +func TestMul(t *testing.T) { + tests := []struct { + a Quantity + b int64 + expected Quantity + ok bool + }{ + {decQuantity(10, 0, DecimalSI), 10, decQuantity(100, 0, DecimalSI), true}, + {decQuantity(10, 0, DecimalSI), 1, decQuantity(10, 0, DecimalSI), true}, + {decQuantity(10, 0, BinarySI), 1, decQuantity(10, 0, BinarySI), true}, + {Quantity{Format: DecimalSI}, 50, decQuantity(0, 0, DecimalSI), true}, + {decQuantity(50, 0, DecimalSI), 0, decQuantity(0, 0, DecimalSI), true}, + {Quantity{Format: DecimalSI}, 0, decQuantity(0, 0, DecimalSI), true}, + + {decQuantity(10, 0, DecimalSI), -10, decQuantity(-100, 0, DecimalSI), true}, + {decQuantity(-10, 0, DecimalSI), 1, decQuantity(-10, 0, DecimalSI), true}, + {decQuantity(10, 0, BinarySI), -1, decQuantity(-10, 0, BinarySI), true}, + {decQuantity(-50, 0, DecimalSI), 0, decQuantity(0, 0, DecimalSI), true}, + {decQuantity(-50, 0, DecimalSI), -50, decQuantity(2500, 0, DecimalSI), true}, + {Quantity{Format: DecimalSI}, -50, decQuantity(0, 0, DecimalSI), true}, + {decQuantity(mostPositive, 0, DecimalSI), 0, decQuantity(0, 1, DecimalSI), true}, + {decQuantity(mostPositive, 0, DecimalSI), 1, decQuantity(mostPositive, 0, DecimalSI), true}, + {decQuantity(mostPositive, 0, DecimalSI), -1, decQuantity(-mostPositive, 0, DecimalSI), true}, + {decQuantity(mostPositive/2, 0, DecimalSI), 2, decQuantity((mostPositive/2)*2, 0, DecimalSI), true}, + {decQuantity(mostPositive/-2, 0, DecimalSI), -2, decQuantity((mostPositive/2)*2, 0, DecimalSI), true}, + {decQuantity(mostPositive, 0, DecimalSI), 2, + bigDecQuantity(big.NewInt(0).Mul(bigMostPositive, big.NewInt(2)), 0, DecimalSI), false}, + {decQuantity(mostPositive, 0, DecimalSI), 10, decQuantity(mostPositive, 1, DecimalSI), false}, + {decQuantity(mostPositive, 0, DecimalSI), -10, decQuantity(-mostPositive, 1, DecimalSI), false}, + {decQuantity(mostNegative, 0, DecimalSI), 0, decQuantity(0, 1, DecimalSI), true}, + {decQuantity(mostNegative, 0, DecimalSI), 1, decQuantity(mostNegative, 0, DecimalSI), true}, + {decQuantity(mostNegative, 0, DecimalSI), -1, + bigDecQuantity(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 0, DecimalSI), false}, + {decQuantity(mostNegative/2, 0, DecimalSI), 2, decQuantity(mostNegative, 0, DecimalSI), true}, + {decQuantity(mostNegative/-2, 0, DecimalSI), -2, decQuantity(mostNegative, 0, DecimalSI), true}, + {decQuantity(mostNegative, 0, DecimalSI), 2, + bigDecQuantity(big.NewInt(0).Mul(bigMostNegative, big.NewInt(2)), 0, DecimalSI), false}, + {decQuantity(mostNegative, 0, DecimalSI), 10, decQuantity(mostNegative, 1, DecimalSI), false}, + {decQuantity(mostNegative, 0, DecimalSI), -10, + bigDecQuantity(big.NewInt(0).Add(bigMostPositive, big.NewInt(1)), 1, DecimalSI), false}, + } + + for i, test := range tests { + if ok := test.a.Mul(test.b); test.ok != ok { + t.Errorf("[%d] Expected ok: %t, got ok: %t", i, test.ok, ok) + } + if test.a.Cmp(test.expected) != 0 { + t.Errorf("[%d] Expected %q, got %q", i, test.expected.AsDec().String(), test.a.AsDec().String()) + } + } +} + +func TestAddSubRoundTrip(t *testing.T) { + for k := -10; k <= 10; k++ { + q := Quantity{Format: DecimalSI} + var order []int64 + for i := 0; i < 100; i++ { + j := rand.Int63() + order = append(order, j) + q.Add(*NewScaledQuantity(j, Scale(k))) + } + for _, j := range order { + q.Sub(*NewScaledQuantity(j, Scale(k))) + } + if !q.IsZero() { + t.Errorf("addition and subtraction did not cancel: %s", &q) + } + } +} + +func TestAddSubRoundTripAcrossScales(t *testing.T) { + q := Quantity{Format: DecimalSI} + var order []int64 + for i := 0; i < 100; i++ { + j := rand.Int63() + order = append(order, j) + q.Add(*NewScaledQuantity(j, Scale(j%20-10))) + } + for _, j := range order { + q.Sub(*NewScaledQuantity(j, Scale(j%20-10))) + } + if !q.IsZero() { + t.Errorf("addition and subtraction did not cancel: %s", &q) + } +} + +func TestNegateRoundTrip(t *testing.T) { + for _, asDec := range []bool{false, true} { + for k := -10; k <= 10; k++ { + for i := 0; i < 100; i++ { + j := rand.Int63() + q := *NewScaledQuantity(j, Scale(k)) + if asDec { + q.AsDec() + } + + b := q.DeepCopy() + b.Neg() + b.Neg() + if b.Cmp(q) != 0 { + t.Errorf("double negation did not cancel: %s", &q) + } + } + } + } +} + +func TestQuantityAsApproximateFloat64(t *testing.T) { + // NOTE: this table should be kept in sync with TestQuantityAsFloat64Slow + table := []struct { + in Quantity + out float64 + }{ + {decQuantity(0, 0, DecimalSI), 0.0}, + {decQuantity(0, 0, DecimalExponent), 0.0}, + {decQuantity(0, 0, BinarySI), 0.0}, + + {decQuantity(1, 0, DecimalSI), 1}, + {decQuantity(1, 0, DecimalExponent), 1}, + {decQuantity(1, 0, BinarySI), 1}, + + // Binary suffixes + {decQuantity(1024, 0, BinarySI), 1024}, + {decQuantity(8*1024, 0, BinarySI), 8 * 1024}, + {decQuantity(7*1024*1024, 0, BinarySI), 7 * 1024 * 1024}, + {decQuantity(7*1024*1024, 1, BinarySI), (7 * 1024 * 1024) * 10}, + {decQuantity(7*1024*1024, 4, BinarySI), (7 * 1024 * 1024) * 10000}, + {decQuantity(7*1024*1024, 8, BinarySI), (7 * 1024 * 1024) * 100000000}, + {decQuantity(7*1024*1024, -1, BinarySI), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way + {decQuantity(7*1024*1024, -8, BinarySI), (7 * 1024 * 1024) / float64(100000000)}, + + {decQuantity(1024, 0, DecimalSI), 1024}, + {decQuantity(8*1024, 0, DecimalSI), 8 * 1024}, + {decQuantity(7*1024*1024, 0, DecimalSI), 7 * 1024 * 1024}, + {decQuantity(7*1024*1024, 1, DecimalSI), (7 * 1024 * 1024) * 10}, + {decQuantity(7*1024*1024, 4, DecimalSI), (7 * 1024 * 1024) * 10000}, + {decQuantity(7*1024*1024, 8, DecimalSI), (7 * 1024 * 1024) * 100000000}, + {decQuantity(7*1024*1024, -1, DecimalSI), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way + {decQuantity(7*1024*1024, -8, DecimalSI), (7 * 1024 * 1024) / float64(100000000)}, + + {decQuantity(1024, 0, DecimalExponent), 1024}, + {decQuantity(8*1024, 0, DecimalExponent), 8 * 1024}, + {decQuantity(7*1024*1024, 0, DecimalExponent), 7 * 1024 * 1024}, + {decQuantity(7*1024*1024, 1, DecimalExponent), (7 * 1024 * 1024) * 10}, + {decQuantity(7*1024*1024, 4, DecimalExponent), (7 * 1024 * 1024) * 10000}, + {decQuantity(7*1024*1024, 8, DecimalExponent), (7 * 1024 * 1024) * 100000000}, + {decQuantity(7*1024*1024, -1, DecimalExponent), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way + {decQuantity(7*1024*1024, -8, DecimalExponent), (7 * 1024 * 1024) / float64(100000000)}, + + // very large numbers + {Quantity{d: maxAllowed, Format: DecimalSI}, math.MaxInt64}, + {Quantity{d: maxAllowed, Format: BinarySI}, math.MaxInt64}, + {decQuantity(12, 18, DecimalSI), 1.2e19}, + + // infinities caused due to float64 overflow + {decQuantity(12, 500, DecimalSI), math.Inf(0)}, + {decQuantity(-12, 500, DecimalSI), math.Inf(-1)}, + } + + for i, item := range table { + t.Run(fmt.Sprintf("%s %s", item.in.Format, item.in.String()), func(t *testing.T) { + out := item.in.AsApproximateFloat64() + if out != item.out { + t.Fatalf("test %d expected %v, got %v", i+1, item.out, out) + } + if item.in.d.Dec != nil { + if i, ok := item.in.AsInt64(); ok { + q := intQuantity(i, 0, item.in.Format) + out := q.AsApproximateFloat64() + if out != item.out { + t.Fatalf("as int quantity: expected %v, got %v", item.out, out) + } + } + } + }) + } +} + +func TestQuantityAsFloat64Slow(t *testing.T) { + // NOTE: this table should be kept in sync with TestQuantityAsApproximateFloat64 + table := []struct { + in Quantity + out float64 + }{ + {decQuantity(0, 0, DecimalSI), 0.0}, + {decQuantity(0, 0, DecimalExponent), 0.0}, + {decQuantity(0, 0, BinarySI), 0.0}, + + {decQuantity(1, 0, DecimalSI), 1}, + {decQuantity(1, 0, DecimalExponent), 1}, + {decQuantity(1, 0, BinarySI), 1}, + + // Binary suffixes + {decQuantity(1024, 0, BinarySI), 1024}, + {decQuantity(8*1024, 0, BinarySI), 8 * 1024}, + {decQuantity(7*1024*1024, 0, BinarySI), 7 * 1024 * 1024}, + {decQuantity(7*1024*1024, 1, BinarySI), (7 * 1024 * 1024) * 10}, + {decQuantity(7*1024*1024, 4, BinarySI), (7 * 1024 * 1024) * 10000}, + {decQuantity(7*1024*1024, 8, BinarySI), (7 * 1024 * 1024) * 100000000}, + {decQuantity(7*1024*1024, -1, BinarySI), (7 * 1024 * 1024) / float64(10)}, + {decQuantity(7*1024*1024, -8, BinarySI), (7 * 1024 * 1024) / float64(100000000)}, + + {decQuantity(1024, 0, DecimalSI), 1024}, + {decQuantity(8*1024, 0, DecimalSI), 8 * 1024}, + {decQuantity(7*1024*1024, 0, DecimalSI), 7 * 1024 * 1024}, + {decQuantity(7*1024*1024, 1, DecimalSI), (7 * 1024 * 1024) * 10}, + {decQuantity(7*1024*1024, 4, DecimalSI), (7 * 1024 * 1024) * 10000}, + {decQuantity(7*1024*1024, 8, DecimalSI), (7 * 1024 * 1024) * 100000000}, + {decQuantity(7*1024*1024, -1, DecimalSI), (7 * 1024 * 1024) / float64(10)}, + {decQuantity(7*1024*1024, -8, DecimalSI), (7 * 1024 * 1024) / float64(100000000)}, + + {decQuantity(1024, 0, DecimalExponent), 1024}, + {decQuantity(8*1024, 0, DecimalExponent), 8 * 1024}, + {decQuantity(7*1024*1024, 0, DecimalExponent), 7 * 1024 * 1024}, + {decQuantity(7*1024*1024, 1, DecimalExponent), (7 * 1024 * 1024) * 10}, + {decQuantity(7*1024*1024, 4, DecimalExponent), (7 * 1024 * 1024) * 10000}, + {decQuantity(7*1024*1024, 8, DecimalExponent), (7 * 1024 * 1024) * 100000000}, + {decQuantity(7*1024*1024, -1, DecimalExponent), (7 * 1024 * 1024) / float64(10)}, + {decQuantity(7*1024*1024, -8, DecimalExponent), (7 * 1024 * 1024) / float64(100000000)}, + + // very large numbers + {Quantity{d: maxAllowed, Format: DecimalSI}, math.MaxInt64}, + {Quantity{d: maxAllowed, Format: BinarySI}, math.MaxInt64}, + {decQuantity(12, 18, DecimalSI), 1.2e19}, + + // infinities caused due to float64 overflow + {decQuantity(12, 500, DecimalSI), math.Inf(0)}, + {decQuantity(-12, 500, DecimalSI), math.Inf(-1)}, + } + + for i, item := range table { + t.Run(fmt.Sprintf("%s %s", item.in.Format, item.in.String()), func(t *testing.T) { + out := item.in.AsFloat64Slow() + if out != item.out { + t.Fatalf("test %d expected %v, got %v", i+1, item.out, out) + } + if item.in.d.Dec != nil { + if i, ok := item.in.AsInt64(); ok { + q := intQuantity(i, 0, item.in.Format) + out := q.AsFloat64Slow() + if out != item.out { + t.Fatalf("as int quantity: expected %v, got %v", item.out, out) + } + } + } + }) + } +} + +func TestStringQuantityAsApproximateFloat64(t *testing.T) { + table := []struct { + in string + out float64 + }{ + {"2Ki", 2048}, + {"1.1Ki", 1126.4e+0}, + {"1Mi", 1.048576e+06}, + {"2Gi", 2.147483648e+09}, + } + + for _, item := range table { + t.Run(item.in, func(t *testing.T) { + in, err := ParseQuantity(item.in) + if err != nil { + t.Fatal(err) + } + out := in.AsApproximateFloat64() + if out != item.out { + t.Fatalf("expected %v, got %v", item.out, out) + } + if in.d.Dec != nil { + if i, ok := in.AsInt64(); ok { + q := intQuantity(i, 0, in.Format) + out := q.AsApproximateFloat64() + if out != item.out { + t.Fatalf("as int quantity: expected %v, got %v", item.out, out) + } + } + } + }) + } +} + +func TestStringQuantityAsFloat64Slow(t *testing.T) { + table := []struct { + in string + out float64 + }{ + {"2Ki", 2048}, + {"1.1Ki", 1126.4e+0}, + {"1Mi", 1.048576e+06}, + {"2Gi", 2.147483648e+09}, + } + + for _, item := range table { + t.Run(item.in, func(t *testing.T) { + in, err := ParseQuantity(item.in) + if err != nil { + t.Fatal(err) + } + out := in.AsFloat64Slow() + if out != item.out { + t.Fatalf("expected %v, got %v", item.out, out) + } + if in.d.Dec != nil { + if i, ok := in.AsInt64(); ok { + q := intQuantity(i, 0, in.Format) + out := q.AsFloat64Slow() + if out != item.out { + t.Fatalf("as int quantity: expected %v, got %v", item.out, out) + } + } + } + }) + } +} + +func benchmarkQuantities() []Quantity { + return []Quantity{ + intQuantity(1024*1024*1024, 0, BinarySI), + intQuantity(1024*1024*1024*1024, 0, BinarySI), + intQuantity(1000000, 3, DecimalSI), + intQuantity(1000000000, 0, DecimalSI), + intQuantity(1, -3, DecimalSI), + intQuantity(80, -3, DecimalSI), + intQuantity(1080, -3, DecimalSI), + intQuantity(0, 0, BinarySI), + intQuantity(1, 9, DecimalExponent), + intQuantity(1, -9, DecimalSI), + intQuantity(1000000, 10, DecimalSI), + } +} + +func BenchmarkQuantityString(b *testing.B) { + values := benchmarkQuantities() + b.ResetTimer() + var s string + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + q.s = "" + s = q.String() + } + b.StopTimer() + if len(s) == 0 { + b.Fatal(s) + } +} + +func BenchmarkQuantityStringPrecalc(b *testing.B) { + values := benchmarkQuantities() + for i := range values { + _ = values[i].String() + } + b.ResetTimer() + var s string + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + s = q.String() + } + b.StopTimer() + if len(s) == 0 { + b.Fatal(s) + } +} + +func BenchmarkQuantityStringBinarySI(b *testing.B) { + values := benchmarkQuantities() + for i := range values { + values[i].Format = BinarySI + } + b.ResetTimer() + var s string + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + q.s = "" + s = q.String() + } + b.StopTimer() + if len(s) == 0 { + b.Fatal(s) + } +} + +func BenchmarkQuantityMarshalJSON(b *testing.B) { + values := benchmarkQuantities() + b.ResetTimer() + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + q.s = "" + if _, err := q.MarshalJSON(); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + +func BenchmarkQuantityUnmarshalJSON(b *testing.B) { + values := benchmarkQuantities() + var json [][]byte + for _, v := range values { + data, _ := v.MarshalJSON() + json = append(json, data) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + var q Quantity + if err := q.UnmarshalJSON(json[i%len(values)]); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + +func BenchmarkParseQuantity(b *testing.B) { + values := benchmarkQuantities() + var strings []string + for _, v := range values { + strings = append(strings, v.String()) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := ParseQuantity(strings[i%len(values)]); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + +func BenchmarkCanonicalize(b *testing.B) { + values := benchmarkQuantities() + b.ResetTimer() + buffer := make([]byte, 0, 100) + for i := 0; i < b.N; i++ { + s, _ := values[i%len(values)].CanonicalizeBytes(buffer) + if len(s) == 0 { + b.Fatal(s) + } + } + b.StopTimer() +} + +func BenchmarkQuantityRoundUp(b *testing.B) { + values := benchmarkQuantities() + b.ResetTimer() + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + copied := q + copied.RoundUp(-3) + } + b.StopTimer() +} + +func BenchmarkQuantityCopy(b *testing.B) { + values := benchmarkQuantities() + b.ResetTimer() + for i := 0; i < b.N; i++ { + values[i%len(values)].DeepCopy() + } + b.StopTimer() +} + +func BenchmarkQuantityAdd(b *testing.B) { + values := benchmarkQuantities() + base := &Quantity{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + base.d.Dec = nil + base.i = int64Amount{value: 100} + base.Add(q) + } + b.StopTimer() +} + +func BenchmarkQuantityCmp(b *testing.B) { + values := benchmarkQuantities() + b.ResetTimer() + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + if q.Cmp(q) != 0 { + b.Fatal(q) + } + } + b.StopTimer() +} + +func BenchmarkQuantityAsApproximateFloat64(b *testing.B) { + values := benchmarkQuantities() + b.ResetTimer() + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + if q.AsApproximateFloat64() == -1 { + b.Fatal(q) + } + } + b.StopTimer() +} + +func BenchmarkQuantityAsFloat64Slow(b *testing.B) { + values := benchmarkQuantities() + b.ResetTimer() + for i := 0; i < b.N; i++ { + q := values[i%len(values)] + if q.AsFloat64Slow() == -1 { + b.Fatal(q) + } + } + b.StopTimer() +} + +var _ pflag.Value = &QuantityValue{} + +func TestQuantityValueSet(t *testing.T) { + q := QuantityValue{} + + if err := q.Set("invalid"); err == nil { + + t.Error("'invalid' did not trigger a parse error") + } + + if err := q.Set("1Mi"); err != nil { + t.Errorf("parsing 1Mi should have worked, got: %v", err) + } + if q.Value() != 1024*1024 { + t.Errorf("quantity should have been set to 1Mi, got: %v", q) + } + + data, err := json.Marshal(q) + if err != nil { + t.Errorf("unexpected encoding error: %v", err) + } + expected := `"1Mi"` + if string(data) != expected { + t.Errorf("expected 1Mi value to be encoded as %q, got: %q", expected, string(data)) + } +} + +func ExampleQuantityValue() { + q := QuantityValue{ + Quantity: MustParse("1Mi"), + } + fs := pflag.FlagSet{} + fs.SetOutput(os.Stdout) + fs.Var(&q, "mem", "sets amount of memory") + fs.PrintDefaults() + // Output: + // --mem quantity sets amount of memory (default 1Mi) +} + +func TestQuantityUnmarshalCBOR(t *testing.T) { + for _, tc := range []struct { + name string + in []byte + want Quantity + errMessage string + }{ + { + name: "null", + in: []byte{0xf6}, // null + want: Quantity{}, + }, + { + name: "text string input", + in: []byte("\x621M"), // "1M" + want: Quantity{i: int64Amount{value: 1, scale: 6}}, + }, + { + name: "byte string input", + in: []byte("\x421M"), // '1M' + want: Quantity{i: int64Amount{value: 1, scale: 6}}, + }, + { + name: "whitespace", + in: []byte("\x4a \t\n\r1M \t\n\r"), // h'20090a0d314d20090a0d' + want: Quantity{i: int64Amount{value: 1, scale: 6}}, + }, + { + name: "empty byte string", + in: []byte{0x40}, + errMessage: ErrFormatWrong.Error(), + }, + { + name: "empty text string", + in: []byte{0x60}, + errMessage: ErrFormatWrong.Error(), + }, + { + name: "unsupported input type", + in: []byte{0x07}, // 7 + errMessage: "cbor: cannot unmarshal positive integer into Go value of type string", + }, + } { + t.Run(tc.name, func(t *testing.T) { + var got Quantity + if err := got.UnmarshalCBOR(tc.in); err != nil { + if tc.errMessage == "" { + t.Fatalf("want nil error, got: %v", err) + } else if gotMessage := err.Error(); tc.errMessage != gotMessage { + t.Fatalf("want error: %q, got: %q", tc.errMessage, gotMessage) + } + } else if tc.errMessage != "" { + t.Fatalf("got nil error, want: %s", tc.errMessage) + } + + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} + +func TestQuantityRoundtripCBOR(t *testing.T) { + for i := 0; i < 500; i++ { + var initial, final Quantity + fuzzer.Fill(&initial) + b, err := cbor.Marshal(initial) + if err != nil { + t.Errorf("error encoding %v: %v", initial, err) + continue + } + err = cbor.Unmarshal(b, &final) + if err != nil { + t.Errorf("%v: error decoding %v: %v", initial, string(b), err) + } + if final.Cmp(initial) != 0 { + diag, err := cbor.Diagnose(b) + if err != nil { + t.Logf("failed to produce diagnostic encoding of 0x%x: %v", b, err) + } + t.Errorf("Expected equal: %v, %v (cbor was '%s')", initial, final, diag) + } + } +} + +func TestParseQuantity(t *testing.T) { + ptrDec := func(s string) *infDecAmount { + d, ok := new(inf.Dec).SetString(s) + if !ok { + t.Fatalf("invalid dec: %s", s) + } + return &infDecAmount{d} + } + + tests := []struct { + input string + wantAsInt64 *int64 + wantAsDec *infDecAmount + canonical string + }{ + // min/max 18 digits + {input: "-999999999999999999", wantAsInt64: ptr.To[int64](-999999999999999999), wantAsDec: ptrDec("-999999999999999999")}, + {input: "999999999999999999", wantAsInt64: ptr.To[int64](999999999999999999), wantAsDec: ptrDec("999999999999999999")}, + // .0 + {input: "-999999999999999999.0", wantAsInt64: nil, wantAsDec: ptrDec("-999999999999999999"), canonical: "-999999999999999999"}, + {input: "999999999999999999.0", wantAsInt64: nil, wantAsDec: ptrDec("999999999999999999"), canonical: "999999999999999999"}, + // .1 + {input: "-999999999999999999.1", wantAsInt64: nil, wantAsDec: ptrDec("-999999999999999999.1"), canonical: "-999999999999999999100m"}, + {input: "999999999999999999.1", wantAsInt64: nil, wantAsDec: ptrDec("999999999999999999.1"), canonical: "999999999999999999100m"}, + + // min/max 19 digits + {input: "-9999999999999999999", wantAsInt64: nil, wantAsDec: ptrDec("-9999999999999999999")}, + {input: "9999999999999999999", wantAsInt64: nil, wantAsDec: ptrDec("9999999999999999999")}, + {input: "-1E", wantAsInt64: ptr.To[int64](-1000000000000000000), wantAsDec: ptrDec("-1000000000000000000")}, + {input: "1E", wantAsInt64: ptr.To[int64](1000000000000000000), wantAsDec: ptrDec("1000000000000000000")}, + {input: "-1000000000000000000", wantAsInt64: nil, wantAsDec: ptrDec("-1000000000000000000"), canonical: "-1E"}, // should be wantAsInt64: + {input: "1000000000000000000", wantAsInt64: nil, wantAsDec: ptrDec("1000000000000000000"), canonical: "1E"}, // should be wantAsInt64: + // .0 + {input: "-9999999999999999999.0", wantAsInt64: nil, wantAsDec: ptrDec("-9999999999999999999"), canonical: "-9999999999999999999"}, + {input: "9999999999999999999.0", wantAsInt64: nil, wantAsDec: ptrDec("9999999999999999999"), canonical: "9999999999999999999"}, + {input: "-1.0E", wantAsInt64: ptr.To[int64](-1000000000000000000), wantAsDec: ptrDec("-1000000000000000000"), canonical: "-1E"}, + {input: "1.0E", wantAsInt64: ptr.To[int64](1000000000000000000), wantAsDec: ptrDec("1000000000000000000"), canonical: "1E"}, + {input: "-1000000000000000000.0", wantAsInt64: nil, wantAsDec: ptrDec("-1000000000000000000"), canonical: "-1E"}, // should be wantAsInt64: + {input: "1000000000000000000.0", wantAsInt64: nil, wantAsDec: ptrDec("1000000000000000000"), canonical: "1E"}, // should be wantAsInt64: + // 000m + {input: "-9999999999999999999000m", wantAsInt64: nil, wantAsDec: ptrDec("-9999999999999999999"), canonical: "-9999999999999999999"}, + {input: "9999999999999999999000m", wantAsInt64: nil, wantAsDec: ptrDec("9999999999999999999"), canonical: "9999999999999999999"}, + // .1 + {input: "-9999999999999999999.1", wantAsInt64: nil, wantAsDec: ptrDec("-9999999999999999999.1"), canonical: "-9999999999999999999100m"}, + {input: "9999999999999999999.1", wantAsInt64: nil, wantAsDec: ptrDec("9999999999999999999.1"), canonical: "9999999999999999999100m"}, + {input: "-1.0000000000000000001E", wantAsInt64: nil, wantAsDec: ptrDec("-1000000000000000000.1"), canonical: "-1000000000000000000100m"}, + {input: "1.0000000000000000001E", wantAsInt64: nil, wantAsDec: ptrDec("1000000000000000000.1"), canonical: "1000000000000000000100m"}, + {input: "-1000000000000000000.1", wantAsInt64: nil, wantAsDec: ptrDec("-1000000000000000000.1"), canonical: "-1000000000000000000100m"}, + {input: "1000000000000000000.1", wantAsInt64: nil, wantAsDec: ptrDec("1000000000000000000.1"), canonical: "1000000000000000000100m"}, + // +1 + {input: "-1.000000000000000001E", wantAsInt64: nil, wantAsDec: ptrDec("-1000000000000000001"), canonical: "-1000000000000000001"}, // should be wantAsInt64: + {input: "1.000000000000000001E", wantAsInt64: nil, wantAsDec: ptrDec("1000000000000000001"), canonical: "1000000000000000001"}, // should be wantAsInt64: + {input: "-1000000000000000001", wantAsInt64: nil, wantAsDec: ptrDec("-1000000000000000001")}, // should be wantAsInt64: + {input: "1000000000000000001", wantAsInt64: nil, wantAsDec: ptrDec("1000000000000000001")}, // should be wantAsInt64: + + // min/max 20 digits + {input: "-10E", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000000")}, + {input: "10E", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000000")}, + {input: "-10000000000000000000", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000000"), canonical: "-10E"}, + {input: "10000000000000000000", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000000"), canonical: "10E"}, + // .0 + {input: "-10.0E", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000000"), canonical: "-10E"}, + {input: "10.0E", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000000"), canonical: "10E"}, + {input: "-10000000000000000000.0", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000000"), canonical: "-10E"}, + {input: "10000000000000000000.0", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000000"), canonical: "10E"}, + // 000m + {input: "-10000000000000000000000m", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000000"), canonical: "-10E"}, + {input: "10000000000000000000000m", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000000"), canonical: "10E"}, + // .1 + {input: "-10.0000000000000000001E", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000000.1"), canonical: "-10000000000000000000100m"}, + {input: "10.0000000000000000001E", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000000.1"), canonical: "10000000000000000000100m"}, + {input: "-10000000000000000000.1", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000000.1"), canonical: "-10000000000000000000100m"}, + {input: "10000000000000000000.1", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000000.1"), canonical: "10000000000000000000100m"}, + // +1 + {input: "-10.000000000000000001E", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000001"), canonical: "-10000000000000000001"}, + {input: "10.000000000000000001E", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000001"), canonical: "10000000000000000001"}, + {input: "-10000000000000000001", wantAsInt64: nil, wantAsDec: ptrDec("-10000000000000000001")}, + {input: "10000000000000000001", wantAsInt64: nil, wantAsDec: ptrDec("10000000000000000001")}, + + // min/max int64 - 1 + {input: "-9223372036854775809", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775809")}, + {input: "9223372036854775806", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775806")}, // should be wantAsInt64: + // .0 + {input: "-9223372036854775809.0", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775809"), canonical: "-9223372036854775809"}, + {input: "9223372036854775806.0", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775806"), canonical: "9223372036854775806"}, // should be wantAsInt64: + // 000m + {input: "-9223372036854775809000m", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775809"), canonical: "-9223372036854775809"}, + {input: "9223372036854775806000m", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775806"), canonical: "9223372036854775806"}, // should be wantAsInt64: + // .1 + {input: "-9223372036854775809.1", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775809.1"), canonical: "-9223372036854775809100m"}, + {input: "9223372036854775806.1", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775806.1"), canonical: "9223372036854775806100m"}, + + // min/max int64 + {input: "-9223372036854775808", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775808")}, // should be wantAsInt64: + {input: "9223372036854775807", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775807")}, // should be wantAsInt64: + // .0 + {input: "-9223372036854775808.0", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775808"), canonical: "-9223372036854775808"}, // should be wantAsInt64: + {input: "9223372036854775807.0", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775807"), canonical: "9223372036854775807"}, // should be wantAsInt64: + // 000m + {input: "-9223372036854775808000m", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775808"), canonical: "-9223372036854775808"}, // should be wantAsInt64: + {input: "9223372036854775807000m", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775807"), canonical: "9223372036854775807"}, // should be wantAsInt64: + // .1 + {input: "-9223372036854775808.1", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775808.1"), canonical: "-9223372036854775808100m"}, + {input: "9223372036854775807.1", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775807.1"), canonical: "9223372036854775807100m"}, + + // min/max int64 + 1 + {input: "-9223372036854775807", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775807")}, // should be wantAsInt64: + {input: "9223372036854775808", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775808")}, + // .0 + {input: "-9223372036854775807.0", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775807"), canonical: "-9223372036854775807"}, // should be wantAsInt64: + {input: "9223372036854775808.0", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775808"), canonical: "9223372036854775808"}, + // 000m + {input: "-9223372036854775807000m", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775807"), canonical: "-9223372036854775807"}, // should be wantAsInt64: + {input: "9223372036854775808000m", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775808"), canonical: "9223372036854775808"}, + // .1 + {input: "-9223372036854775807.1", wantAsInt64: nil, wantAsDec: ptrDec("-9223372036854775807.1"), canonical: "-9223372036854775807100m"}, + {input: "9223372036854775808.1", wantAsInt64: nil, wantAsDec: ptrDec("9223372036854775808.1"), canonical: "9223372036854775808100m"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + q, err := ParseQuantity(tt.input) + if err != nil { + t.Fatalf("unexpected error for input %q: %v", tt.input, err) + } + + val, ok := q.AsInt64() + if tt.wantAsInt64 != nil { + if !ok { + t.Errorf("AsInt64() returned ok=false for input %q, want ok=true and value %d", tt.input, *tt.wantAsInt64) + } else if val != *tt.wantAsInt64 { + t.Errorf("AsInt64() returned value %d for input %q, want value %d", val, tt.input, *tt.wantAsInt64) + } + } else { + if ok { + t.Errorf("AsInt64() returned ok=true and value %d for input %q, want ok=false", val, tt.input) + } + } + + if tt.wantAsDec != nil { + if q.AsDec().Cmp(tt.wantAsDec.Dec) != 0 { + t.Errorf("AsDec() returned %s for input %q, want %s", q.AsDec().String(), tt.input, tt.wantAsDec.Dec.String()) + } + } + + serialized := q.String() + expectedString := tt.input + if tt.canonical != "" { + if tt.canonical == tt.input { + t.Errorf("unnecessary identical explicit canonical value in testcase") + } + expectedString = tt.canonical + } + if serialized != expectedString { + t.Errorf("expected input %q to reserialize to %q but got %q", tt.input, expectedString, serialized) + } + }) + } +} + +func TestQuantityPtrEqual(t *testing.T) { + q1 := MustParse("100m") + q2 := MustParse("100m") + q3 := MustParse("200m") + + tests := []struct { + name string + a *Quantity + b *Quantity + expect bool + }{ + { + name: "both nil", + a: nil, + b: nil, + expect: true, + }, + { + name: "first nil", + a: nil, + b: &q1, + expect: false, + }, + { + name: "second nil", + a: &q1, + b: nil, + expect: false, + }, + { + name: "equal quantities", + a: &q1, + b: &q2, + expect: true, + }, + { + name: "unequal quantities", + a: &q1, + b: &q3, + expect: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := QuantityPtrEqual(tt.a, tt.b); got != tt.expect { + t.Errorf("QuantityPtrEqual() = %v, want %v", got, tt.expect) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/scale_int.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/scale_int.go new file mode 100644 index 0000000000..55e177b0e9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/scale_int.go @@ -0,0 +1,95 @@ +/* +Copyright 2015 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 resource + +import ( + "math" + "math/big" + "sync" +) + +var ( + // A sync pool to reduce allocation. + intPool sync.Pool + maxInt64 = big.NewInt(math.MaxInt64) +) + +func init() { + intPool.New = func() interface{} { + return &big.Int{} + } +} + +// scaledValue scales given unscaled value from scale to new Scale and returns +// an int64. It ALWAYS rounds up the result when scale down. The final result might +// overflow. +// +// scale, newScale represents the scale of the unscaled decimal. +// The mathematical value of the decimal is unscaled * 10**(-scale). +func scaledValue(unscaled *big.Int, scale, newScale int) int64 { + dif := scale - newScale + if dif == 0 { + return unscaled.Int64() + } + + // Handle scale up + // This is an easy case, we do not need to care about rounding and overflow. + // If any intermediate operation causes overflow, the result will overflow. + if dif < 0 { + return unscaled.Int64() * int64(math.Pow10(-dif)) + } + + // Handle scale down + // We have to be careful about the intermediate operations. + + // fast path when unscaled < max.Int64 and exp(10,dif) < max.Int64 + const log10MaxInt64 = 19 + if unscaled.Cmp(maxInt64) < 0 && dif < log10MaxInt64 { + divide := int64(math.Pow10(dif)) + result := unscaled.Int64() / divide + mod := unscaled.Int64() % divide + if mod != 0 { + return result + 1 + } + return result + } + + // We should only convert back to int64 when getting the result. + divisor := intPool.Get().(*big.Int) + exp := intPool.Get().(*big.Int) + result := intPool.Get().(*big.Int) + defer func() { + intPool.Put(divisor) + intPool.Put(exp) + intPool.Put(result) + }() + + // divisor = 10^(dif) + // TODO: create loop up table if exp costs too much. + divisor.Exp(bigTen, exp.SetInt64(int64(dif)), nil) + // reuse exp + remainder := exp + + // result = unscaled / divisor + // remainder = unscaled % divisor + result.DivMod(unscaled, divisor, remainder) + if remainder.Sign() != 0 { + return result.Int64() + 1 + } + + return result.Int64() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/scale_int_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/scale_int_test.go new file mode 100644 index 0000000000..b150fa514c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/scale_int_test.go @@ -0,0 +1,85 @@ +/* +Copyright 2015 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 resource + +import ( + "math" + "math/big" + "testing" +) + +func TestScaledValueInternal(t *testing.T) { + tests := []struct { + unscaled *big.Int + scale int + newScale int + + want int64 + }{ + // remain scale + {big.NewInt(1000), 0, 0, 1000}, + + // scale down + {big.NewInt(1000), 0, -3, 1}, + {big.NewInt(1000), 3, 0, 1}, + {big.NewInt(0), 3, 0, 0}, + + // always round up + {big.NewInt(999), 3, 0, 1}, + {big.NewInt(500), 3, 0, 1}, + {big.NewInt(499), 3, 0, 1}, + {big.NewInt(1), 3, 0, 1}, + // large scaled value does not lose precision + {big.NewInt(0).Sub(maxInt64, bigOne), 1, 0, (math.MaxInt64-1)/10 + 1}, + // large intermediate result. + {big.NewInt(1).Exp(big.NewInt(10), big.NewInt(100), nil), 100, 0, 1}, + + // scale up + {big.NewInt(0), 0, 3, 0}, + {big.NewInt(1), 0, 3, 1000}, + {big.NewInt(1), -3, 0, 1000}, + {big.NewInt(1000), -3, 2, 100000000}, + {big.NewInt(0).Div(big.NewInt(math.MaxInt64), bigThousand), 0, 3, + (math.MaxInt64 / 1000) * 1000}, + } + + for i, tt := range tests { + old := (&big.Int{}).Set(tt.unscaled) + got := scaledValue(tt.unscaled, tt.scale, tt.newScale) + if got != tt.want { + t.Errorf("#%d: got = %v, want %v", i, got, tt.want) + } + if tt.unscaled.Cmp(old) != 0 { + t.Errorf("#%d: unscaled = %v, want %v", i, tt.unscaled, old) + } + } +} + +func BenchmarkScaledValueSmall(b *testing.B) { + s := big.NewInt(1000) + for i := 0; i < b.N; i++ { + scaledValue(s, 3, 0) + } +} + +func BenchmarkScaledValueLarge(b *testing.B) { + s := big.NewInt(math.MaxInt64) + s.Mul(s, big.NewInt(1000)) + for i := 0; i < b.N; i++ { + scaledValue(s, 10, 0) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/suffix.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/suffix.go new file mode 100644 index 0000000000..6ec527f9c0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/suffix.go @@ -0,0 +1,198 @@ +/* +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 resource + +import ( + "strconv" +) + +type suffix string + +// suffixer can interpret and construct suffixes. +type suffixer interface { + interpret(suffix) (base, exponent int32, fmt Format, ok bool) + construct(base, exponent int32, fmt Format) (s suffix, ok bool) + constructBytes(base, exponent int32, fmt Format) (s []byte, ok bool) +} + +// quantitySuffixer handles suffixes for all three formats that quantity +// can handle. +var quantitySuffixer = newSuffixer() + +type bePair struct { + base, exponent int32 +} + +type listSuffixer struct { + suffixToBE map[suffix]bePair + beToSuffix map[bePair]suffix + beToSuffixBytes map[bePair][]byte +} + +func (ls *listSuffixer) addSuffix(s suffix, pair bePair) { + if ls.suffixToBE == nil { + ls.suffixToBE = map[suffix]bePair{} + } + if ls.beToSuffix == nil { + ls.beToSuffix = map[bePair]suffix{} + } + if ls.beToSuffixBytes == nil { + ls.beToSuffixBytes = map[bePair][]byte{} + } + ls.suffixToBE[s] = pair + ls.beToSuffix[pair] = s + ls.beToSuffixBytes[pair] = []byte(s) +} + +func (ls *listSuffixer) lookup(s suffix) (base, exponent int32, ok bool) { + pair, ok := ls.suffixToBE[s] + if !ok { + return 0, 0, false + } + return pair.base, pair.exponent, true +} + +func (ls *listSuffixer) construct(base, exponent int32) (s suffix, ok bool) { + s, ok = ls.beToSuffix[bePair{base, exponent}] + return +} + +func (ls *listSuffixer) constructBytes(base, exponent int32) (s []byte, ok bool) { + s, ok = ls.beToSuffixBytes[bePair{base, exponent}] + return +} + +type suffixHandler struct { + decSuffixes listSuffixer + binSuffixes listSuffixer +} + +type fastLookup struct { + *suffixHandler +} + +func (l fastLookup) interpret(s suffix) (base, exponent int32, format Format, ok bool) { + switch s { + case "": + return 10, 0, DecimalSI, true + case "n": + return 10, -9, DecimalSI, true + case "u": + return 10, -6, DecimalSI, true + case "m": + return 10, -3, DecimalSI, true + case "k": + return 10, 3, DecimalSI, true + case "M": + return 10, 6, DecimalSI, true + case "G": + return 10, 9, DecimalSI, true + } + return l.suffixHandler.interpret(s) +} + +func newSuffixer() suffixer { + sh := &suffixHandler{} + + // IMPORTANT: if you change this section you must change fastLookup + + sh.binSuffixes.addSuffix("Ki", bePair{2, 10}) + sh.binSuffixes.addSuffix("Mi", bePair{2, 20}) + sh.binSuffixes.addSuffix("Gi", bePair{2, 30}) + sh.binSuffixes.addSuffix("Ti", bePair{2, 40}) + sh.binSuffixes.addSuffix("Pi", bePair{2, 50}) + sh.binSuffixes.addSuffix("Ei", bePair{2, 60}) + // Don't emit an error when trying to produce + // a suffix for 2^0. + sh.decSuffixes.addSuffix("", bePair{2, 0}) + + sh.decSuffixes.addSuffix("n", bePair{10, -9}) + sh.decSuffixes.addSuffix("u", bePair{10, -6}) + sh.decSuffixes.addSuffix("m", bePair{10, -3}) + sh.decSuffixes.addSuffix("", bePair{10, 0}) + sh.decSuffixes.addSuffix("k", bePair{10, 3}) + sh.decSuffixes.addSuffix("M", bePair{10, 6}) + sh.decSuffixes.addSuffix("G", bePair{10, 9}) + sh.decSuffixes.addSuffix("T", bePair{10, 12}) + sh.decSuffixes.addSuffix("P", bePair{10, 15}) + sh.decSuffixes.addSuffix("E", bePair{10, 18}) + + return fastLookup{sh} +} + +func (sh *suffixHandler) construct(base, exponent int32, fmt Format) (s suffix, ok bool) { + switch fmt { + case DecimalSI: + return sh.decSuffixes.construct(base, exponent) + case BinarySI: + return sh.binSuffixes.construct(base, exponent) + case DecimalExponent: + if base != 10 { + return "", false + } + if exponent == 0 { + return "", true + } + return suffix("e" + strconv.FormatInt(int64(exponent), 10)), true + } + return "", false +} + +func (sh *suffixHandler) constructBytes(base, exponent int32, format Format) (s []byte, ok bool) { + switch format { + case DecimalSI: + return sh.decSuffixes.constructBytes(base, exponent) + case BinarySI: + return sh.binSuffixes.constructBytes(base, exponent) + case DecimalExponent: + if base != 10 { + return nil, false + } + if exponent == 0 { + return nil, true + } + result := make([]byte, 8) + result[0] = 'e' + number := strconv.AppendInt(result[1:1], int64(exponent), 10) + if &result[1] == &number[0] { + return result[:1+len(number)], true + } + result = append(result[:1], number...) + return result, true + } + return nil, false +} + +func (sh *suffixHandler) interpret(suffix suffix) (base, exponent int32, fmt Format, ok bool) { + // Try lookup tables first + if b, e, ok := sh.decSuffixes.lookup(suffix); ok { + return b, e, DecimalSI, true + } + if b, e, ok := sh.binSuffixes.lookup(suffix); ok { + return b, e, BinarySI, true + } + + if len(suffix) > 1 && (suffix[0] == 'E' || suffix[0] == 'e') { + parsed, err := strconv.ParseInt(string(suffix[1:]), 10, 64) + if err != nil { + return 0, 0, DecimalExponent, false + } + return 10, int32(parsed), DecimalExponent, true + } + + return 0, 0, DecimalExponent, false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go new file mode 100644 index 0000000000..abb00f38e2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/zz_generated.deepcopy.go @@ -0,0 +1,45 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package resource + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Quantity) DeepCopyInto(out *Quantity) { + *out = in.DeepCopy() + return +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QuantityValue) DeepCopyInto(out *QuantityValue) { + *out = *in + out.Quantity = in.Quantity.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuantityValue. +func (in *QuantityValue) DeepCopy() *QuantityValue { + if in == nil { + return nil + } + out := new(QuantityValue) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/zz_generated.model_name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/zz_generated.model_name.go new file mode 100644 index 0000000000..2575a2e8c8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/resource/zz_generated.model_name.go @@ -0,0 +1,32 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package resource + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Quantity) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.api.resource.Quantity" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in QuantityValue) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.api.resource.QuantityValue" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/safe/safe.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/safe/safe.go new file mode 100644 index 0000000000..6d6d1f88fe --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/safe/safe.go @@ -0,0 +1,59 @@ +/* +Copyright 2024 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 safe + +// Field takes a pointer to any value (which may or may not be nil) and a +// function that traverses to a target type R (a typical use case is to +// dereference a field), and returns the result of the traversal, or the zero +// value of the target type. +// +// This is roughly equivalent to: +// +// value != nil ? fn(value) : zero-value +// +// ...in languages that support the ternary operator. +func Field[V any, R any](value *V, fn func(*V) R) R { + if value == nil { + var zero R + return zero + } + o := fn(value) + return o +} + +// Cast takes any value, attempts to cast it to T, and returns the T value if +// the cast is successful, or else the zero value of T. +func Cast[T any](value any) T { + result, _ := value.(T) + return result +} + +// Value takes a pointer to any value (which may or may not be nil) and a +// function that returns a pointer to the same type. If the value is not nil, +// it is returned, otherwise the result of the function is returned. +// +// This is roughly equivalent to: +// +// value != nil ? value : fn() +// +// ...in languages that support the ternary operator. +func Value[T any](value *T, fn func() *T) *T { + if value != nil { + return value + } + return fn() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/README.md b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/README.md new file mode 100644 index 0000000000..52ca031d44 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/README.md @@ -0,0 +1,64 @@ +# API validation + +This package holds functions which validate fields and types in the Kubernetes +API. It may be useful beyond API validation, but this is the primary goal. + +Most of the public functions here have signatures which adhere to the following +pattern, which is assumed by automation and code-generation: + +``` +import ( + "context" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func (ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue , ) field.ErrorList +``` + +The name of validator functions should consider that callers will generally be +spelling out the package name and the function name, and so should aim for +legibility. E.g. `validate.Concept()`. + +The `ctx` argument is Go's usual Context. + +The `opCtx` argument provides information about the API operation in question. + +The `fldPath` argument indicates the path to the field in question, to be used +in errors. + +The `value` and `oldValue` arguments are the thing(s) being validated. For +CREATE operations (`opCtx.Operation == operation.Create`), the `oldValue` +argument will be nil. Many validators functions only look at the current value +(`value`) and disregard `oldValue`. + +The `value` and `oldValue` arguments are always nilable - pointers to primitive +types, slices of any type, or maps of any type. Validator functions should +avoid dereferencing nil. Callers are expected to not pass a nil `value` unless the +API field itself was nilable. `oldValue` is always nil for CREATE operations and +is also nil for UPDATE operations if the `value` is not correlated with an `oldValue`. + +Simple content-validators may have no ``, but validator functions +may take additional arguments. Some validator functions will be built as +generics, e.g. to allow any integer type or to handle arbitrary slices. + +Examples: + +``` +// NonEmpty validates that a string is not empty. +func NonEmpty(ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList + +// Even validates that a slice has an even number of items. +func Even[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList + +// KeysMaxLen validates that all of the string keys in a map are under the +// specified length. +func KeysMaxLen[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ map[string]T, maxLen int) field.ErrorList +``` + +Validator functions always return an `ErrorList` where each item is a distinct +validation failure and a zero-length return value (not just nil) indicates +success. + +Good validation failure messages follow the Kubernetes API conventions, for +example using "must" instead of "should". diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/common.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/common.go new file mode 100644 index 0000000000..14a6f0da7f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/common.go @@ -0,0 +1,28 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateFunc is a function that validates a value, possibly considering the +// old value (if any). +type ValidateFunc[T any] func(ctx context.Context, op operation.Operation, fldPath *field.Path, newValue, oldValue T) field.ErrorList diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/constraints/constraints.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/constraints/constraints.go new file mode 100644 index 0000000000..1689d3c079 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/constraints/constraints.go @@ -0,0 +1,32 @@ +/* +Copyright 2025 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 constraints + +// Signed is a constraint that permits any signed integer type. +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +// Unsigned is a constraint that permits any unsigned integer type. +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +// Integer is a constraint that permits any integer type. +type Integer interface { + Signed | Unsigned +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/decimal_int.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/decimal_int.go new file mode 100644 index 0000000000..5622ca15a8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/decimal_int.go @@ -0,0 +1,62 @@ +/* +Copyright 2025 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 content + +const decimalIntegerErrMsg string = "must be a valid decimal integer in canonical form" + +// IsDecimalInteger validates that a string represents a decimal integer in strict canonical form. +// This means the string must be formatted exactly as a human would naturally write an integer, +// without any programming language conventions like leading zeros, plus signs, or alternate bases. +// +// valid values:"0" or Non-zero integers (i.e., "123", "-456") where the first digit is 1-9, +// followed by any digits 0-9. +// +// This validator is stricter than strconv.ParseInt, which accepts leading zeros values (i.e, "0700") +// and interprets them as decimal 700, potentially causing confusion with octal notation. +func IsDecimalInteger(value string) []string { + n := len(value) + if n == 0 { + return []string{EmptyError()} + } + + i := 0 + if value[0] == '-' { + if n == 1 { + return []string{decimalIntegerErrMsg} + } + i = 1 + } + + if value[i] == '0' { + if n == 1 && i == 0 { + return nil + } + return []string{decimalIntegerErrMsg} + } + + if value[i] < '1' || value[i] > '9' { + return []string{decimalIntegerErrMsg} + } + + for i++; i < n; i++ { + if value[i] < '0' || value[i] > '9' { + return []string{decimalIntegerErrMsg} + } + } + + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/decimal_int_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/decimal_int_test.go new file mode 100644 index 0000000000..369417d26a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/decimal_int_test.go @@ -0,0 +1,234 @@ +/* +Copyright 2025 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 content + +import ( + "strconv" + "strings" + "testing" +) + +func TestIsDecimalInteger(t *testing.T) { + testCases := []struct { + name string + input string + shouldPass bool + errContains string + }{ + // valid + {name: "zero", input: "0", shouldPass: true}, + {name: "positive single digit 1", input: "1", shouldPass: true}, + {name: "positive single digit 2", input: "2", shouldPass: true}, + {name: "positive single digit 5", input: "5", shouldPass: true}, + {name: "positive single digit 9", input: "9", shouldPass: true}, + {name: "negative single digit", input: "-5", shouldPass: true}, + {name: "negative single digit 1", input: "-1", shouldPass: true}, + {name: "negative single digit 9", input: "-9", shouldPass: true}, + + {name: "number starting with 1", input: "100", shouldPass: true}, + {name: "number starting with 2", input: "234", shouldPass: true}, + {name: "number starting with 3", input: "345", shouldPass: true}, + {name: "number starting with 4", input: "456", shouldPass: true}, + {name: "number starting with 5", input: "567", shouldPass: true}, + {name: "number starting with 6", input: "678", shouldPass: true}, + {name: "number starting with 7", input: "789", shouldPass: true}, + {name: "number starting with 8", input: "890", shouldPass: true}, + {name: "number starting with 9", input: "999", shouldPass: true}, + + {name: "positive multi-digit", input: "123", shouldPass: true}, + {name: "negative multi-digit", input: "-456", shouldPass: true}, + {name: "negative starting with 1", input: "-100", shouldPass: true}, + {name: "negative starting with 9", input: "-987", shouldPass: true}, + {name: "large positive number", input: "9223372036854775807", shouldPass: true}, // max int64 + {name: "large negative number", input: "-9223372036854775808", shouldPass: true}, // min int64 + {name: "very long valid number", input: "12345678901234567890", shouldPass: true}, + {name: "all nines", input: "999999999999", shouldPass: true}, + + // invalid + {name: "negative zero", input: "-0", shouldPass: false}, + {name: "double zero", input: "00", shouldPass: false}, + {name: "triple zero", input: "000", shouldPass: false}, + {name: "many zeros", input: "0000000", shouldPass: false}, + {name: "leading zero single digit", input: "01", shouldPass: false}, + {name: "leading zero digit 2", input: "02", shouldPass: false}, + {name: "leading zero digit 9", input: "09", shouldPass: false}, + {name: "leading zero multi-digit", input: "0123", shouldPass: false}, + {name: "octal-like format", input: "0700", shouldPass: false}, + {name: "octal-like format 2", input: "0950", shouldPass: false}, + {name: "multiple leading zeros", input: "00123", shouldPass: false}, + {name: "negative with leading zero", input: "-01", shouldPass: false}, + {name: "negative with leading zeros", input: "-0123", shouldPass: false}, + {name: "negative double zero", input: "-00", shouldPass: false}, + {name: "plus sign", input: "+123", shouldPass: false}, + {name: "positive plus sign", input: "+5", shouldPass: false}, + {name: "plus zero", input: "+0", shouldPass: false}, + + // Invalid cases - empty and whitespace + {name: "empty string", input: "", shouldPass: false, errContains: "non-empty"}, + {name: "just minus sign", input: "-", shouldPass: false}, + {name: "just plus sign", input: "+", shouldPass: false}, + {name: "single space", input: " ", shouldPass: false}, + {name: "multiple spaces", input: " ", shouldPass: false}, + {name: "leading space", input: " 123", shouldPass: false}, + {name: "trailing space", input: "123 ", shouldPass: false}, + {name: "space in middle", input: "12 3", shouldPass: false}, + {name: "spaces around", input: " 123 ", shouldPass: false}, + + {name: "decimal number", input: "12.3", shouldPass: false}, + {name: "decimal zero", input: "0.0", shouldPass: false}, + {name: "negative decimal", input: "-12.5", shouldPass: false}, + {name: "trailing dot", input: "123.", shouldPass: false}, + {name: "leading dot", input: ".123", shouldPass: false}, + + {name: "alphabetic", input: "abc", shouldPass: false}, + {name: "alphanumeric", input: "12a3", shouldPass: false}, + {name: "letter at start", input: "a123", shouldPass: false}, + {name: "letter at end", input: "123a", shouldPass: false}, + {name: "uppercase letters", input: "ABC", shouldPass: false}, + {name: "mixed case", input: "12A3", shouldPass: false}, + + {name: "hexadecimal", input: "0x123", shouldPass: false}, + {name: "hex uppercase", input: "0X123", shouldPass: false}, + {name: "octal prefix", input: "0o777", shouldPass: false}, + {name: "binary prefix", input: "0b101", shouldPass: false}, + {name: "scientific notation", input: "1e5", shouldPass: false}, + {name: "scientific negative exp", input: "1e-5", shouldPass: false}, + {name: "scientific uppercase", input: "1E5", shouldPass: false}, + + {name: "underscore separator", input: "1_000", shouldPass: false}, + {name: "comma separator", input: "1,000", shouldPass: false}, + {name: "period separator", input: "1.000", shouldPass: false}, + {name: "apostrophe separator", input: "1'000", shouldPass: false}, + + {name: "double minus", input: "--123", shouldPass: false}, + {name: "double plus", input: "++123", shouldPass: false}, + {name: "plus minus", input: "+-123", shouldPass: false}, + {name: "minus plus", input: "-+123", shouldPass: false}, + {name: "minus at end", input: "123-", shouldPass: false}, + {name: "minus in middle", input: "12-3", shouldPass: false}, + {name: "plus at end", input: "123+", shouldPass: false}, + {name: "plus in middle", input: "12+3", shouldPass: false}, + + {name: "tab character at start", input: "\t123", shouldPass: false}, + {name: "tab character at end", input: "123\t", shouldPass: false}, + {name: "newline character", input: "123\n", shouldPass: false}, + {name: "carriage return", input: "123\r", shouldPass: false}, + {name: "null character", input: "123\x00", shouldPass: false}, + {name: "vertical tab", input: "123\v", shouldPass: false}, + {name: "form feed", input: "123\f", shouldPass: false}, + + {name: "parentheses", input: "(123)", shouldPass: false}, + {name: "brackets", input: "[123]", shouldPass: false}, + {name: "braces", input: "{123}", shouldPass: false}, + {name: "dollar sign", input: "$123", shouldPass: false}, + {name: "percent sign", input: "123%", shouldPass: false}, + {name: "hash", input: "#123", shouldPass: false}, + {name: "at sign", input: "@123", shouldPass: false}, + {name: "ampersand", input: "&123", shouldPass: false}, + {name: "asterisk", input: "*123", shouldPass: false}, + {name: "slash", input: "12/3", shouldPass: false}, + {name: "backslash", input: "12\\3", shouldPass: false}, + {name: "pipe", input: "12|3", shouldPass: false}, + {name: "semicolon", input: "12;3", shouldPass: false}, + {name: "colon", input: "12:3", shouldPass: false}, + {name: "question mark", input: "12?3", shouldPass: false}, + {name: "exclamation", input: "12!3", shouldPass: false}, + {name: "tilde", input: "~123", shouldPass: false}, + {name: "backtick", input: "`123", shouldPass: false}, + {name: "single quote", input: "'123'", shouldPass: false}, + {name: "double quote", input: "\"123\"", shouldPass: false}, + + {name: "unicode minus", input: "−123", shouldPass: false}, // U+2212 minus sign + {name: "unicode digit", input: "123", shouldPass: false}, // fullwidth digits + {name: "arabic digits", input: "١٢٣", shouldPass: false}, // Arabic-Indic digits + {name: "chinese characters", input: "一二三", shouldPass: false}, + {name: "superscript", input: "123⁴", shouldPass: false}, + {name: "subscript", input: "123₄", shouldPass: false}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + errs := IsDecimalInteger(tc.input) + if tc.shouldPass { + if len(errs) != 0 { + t.Errorf("IsDecimalInteger(%q) = %v, want no errors", tc.input, errs) + } + } else { + if len(errs) == 0 { + t.Errorf("IsDecimalInteger(%q) = no errors, want errors", tc.input) + } else if tc.errContains != "" { + found := false + for _, err := range errs { + if strings.Contains(err, tc.errContains) { + found = true + break + } + } + if !found { + t.Errorf("IsDecimalInteger(%q) errors %v should contain %q", tc.input, errs, tc.errContains) + } + } + } + }) + } + + // Additional verification: valid strings should parse with strconv.ParseInt + validCases := []string{ + "0", "1", "2", "5", "9", + "-1", "-5", "-9", + "123", "-456", "100", "999", + "9223372036854775807", "-9223372036854775808", + "12345678901234567890", + } + for _, validCase := range validCases { + if errs := IsDecimalInteger(validCase); len(errs) != 0 { + t.Errorf("Valid case %q should return no errors, got: %v", validCase, errs) + } + // Verify it can also be parsed by strconv.ParseInt (within range) + if len(validCase) <= 19 { // Only test cases that fit in int64 + if _, err := strconv.ParseInt(validCase, 10, 64); err != nil { + t.Errorf("Valid case %q should be parseable by strconv.ParseInt: %v", validCase, err) + } + } + } + + // Verify that our function rejects what we intend to reject (even if strconv.ParseInt accepts it) + rejectedCases := []string{ + "0700", "0950", "01", "02", "09", + "+123", "+5", "+0", + "-0", "00", "000", + "-01", "-00", + } + for _, rejectedCase := range rejectedCases { + if errs := IsDecimalInteger(rejectedCase); len(errs) == 0 { + t.Errorf("Case %q should be rejected by strict validation", rejectedCase) + } + } + + // Edge case: verify strconv.ParseInt accepts things we reject (proving we're stricter) + strconvAcceptsButWeReject := []string{"+123", "0700", "01"} + for _, case_ := range strconvAcceptsButWeReject { + // strconv.ParseInt should accept it + if _, err := strconv.ParseInt(case_, 10, 64); err != nil { + t.Errorf("strconv.ParseInt should accept %q but got error: %v", case_, err) + } + // But our function should reject it + if errs := IsDecimalInteger(case_); len(errs) == 0 { + t.Errorf("IsDecimalInteger should reject %q (stricter than strconv)", case_) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/dns.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/dns.go new file mode 100644 index 0000000000..bd20720794 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/dns.go @@ -0,0 +1,101 @@ +/* +Copyright 2025 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 content + +import ( + "regexp" +) + +const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" + +const dns1123LabelErrMsg string = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character" + +// DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123) +const DNS1123LabelMaxLength int = 63 + +var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$") + +// IsDNS1123Label tests for a string that conforms to the definition of a label in +// DNS (RFC 1123). +func IsDNS1123Label(value string) []string { + var errs []string + if len(value) > DNS1123LabelMaxLength { + errs = append(errs, MaxLenError(DNS1123LabelMaxLength)) + } + if !dns1123LabelRegexp.MatchString(value) { + if dns1123SubdomainRegexp.MatchString(value) { + // It was a valid subdomain and not a valid label. Since we + // already checked length, it must be dots. + errs = append(errs, "must not contain dots") + } else { + errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc")) + } + } + return errs +} + +const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*" +const dns1123SubdomainFmtCaseless string = "(?i)" + dns1123SubdomainFmt +const dns1123SubdomainErrorMsg string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" +const dns1123SubdomainCaselessErrorMsg string = "an RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" + +// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123) +const DNS1123SubdomainMaxLength int = 253 + +var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$") +var dns1123SubdomainCaselessRegexp = regexp.MustCompile("^" + dns1123SubdomainFmtCaseless + "$") + +// IsDNS1123Subdomain tests for a string that conforms to the definition of a +// subdomain in DNS (RFC 1123) lowercase. +func IsDNS1123Subdomain(value string) []string { + return isDNS1123Subdomain(value, false) +} + +// IsDNS1123SubdomainCaseless tests for a string that conforms to the definition of a +// subdomain in DNS (RFC 1123). +// +// Deprecated: API validation should never be caseless. Caseless validation is a vector +// for bugs and failed uniqueness assumptions. For example, names like "foo.com" and +// "FOO.COM" are both accepted as valid, but they are typically not treated as equal by +// consumers (e.g. CSI and DRA driver names). This fails the "least surprise" principle and +// can cause inconsistent behaviors. +// +// Note: This allows uppercase names but is not caseless — uppercase and lowercase are +// treated as different values. Use IsDNS1123Subdomain for strict, lowercase validation +// instead. +func IsDNS1123SubdomainCaseless(value string) []string { + return isDNS1123Subdomain(value, true) +} + +func isDNS1123Subdomain(value string, caseless bool) []string { + var errs []string + if len(value) > DNS1123SubdomainMaxLength { + errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) + } + errorMsg := dns1123SubdomainErrorMsg + example := "example.com" + regexp := dns1123SubdomainRegexp + if caseless { + errorMsg = dns1123SubdomainCaselessErrorMsg + example = "Example.com" + regexp = dns1123SubdomainCaselessRegexp + } + if !regexp.MatchString(value) { + errs = append(errs, RegexError(errorMsg, dns1123SubdomainFmt, example)) + } + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/dns_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/dns_test.go new file mode 100644 index 0000000000..c241d722c1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/dns_test.go @@ -0,0 +1,126 @@ +/* +Copyright 2025 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 content + +import ( + "strings" + "testing" +) + +func TestIsDNS1123Label(t *testing.T) { + goodValues := []string{ + "a", "ab", "abc", "a1", "a-1", "a--1--2--b", + "0", "01", "012", "1a", "1-a", "1--a--b--2", + strings.Repeat("a", 63), + } + for _, val := range goodValues { + if msgs := IsDNS1123Label(val); len(msgs) != 0 { + t.Errorf("expected true for '%s': %v", val, msgs) + } + } + + badValues := []string{ + "", "A", "ABC", "aBc", "A1", "A-1", "1-A", + "-", "a-", "-a", "1-", "-1", + "_", "a_", "_a", "a_b", "1_", "_1", "1_2", + ".", "a.", ".a", "a.b", "1.", ".1", "1.2", + " ", "a ", " a", "a b", "1 ", " 1", "1 2", + strings.Repeat("a", 64), + } + for _, val := range badValues { + if msgs := IsDNS1123Label(val); len(msgs) == 0 { + t.Errorf("expected false for '%s'", val) + } + } +} + +func TestIsDNS1123Subdomain(t *testing.T) { + goodValues := []string{ + "a", "ab", "abc", "a1", "a-1", "a--1--2--b", + "0", "01", "012", "1a", "1-a", "1--a--b--2", + "a.a", "ab.a", "abc.a", "a1.a", "a-1.a", "a--1--2--b.a", + "a.1", "ab.1", "abc.1", "a1.1", "a-1.1", "a--1--2--b.1", + "0.a", "01.a", "012.a", "1a.a", "1-a.a", "1--a--b--2", + "0.1", "01.1", "012.1", "1a.1", "1-a.1", "1--a--b--2.1", + "a.b.c.d.e", "aa.bb.cc.dd.ee", "1.2.3.4.5", "11.22.33.44.55", + strings.Repeat("a", 253), + } + for _, val := range goodValues { + if msgs := IsDNS1123Subdomain(val); len(msgs) != 0 { + t.Errorf("expected true for '%s': %v", val, msgs) + } + } + + badValues := []string{ + "", "A", "ABC", "aBc", "A1", "A-1", "1-A", + "-", "a-", "-a", "1-", "-1", + "_", "a_", "_a", "a_b", "1_", "_1", "1_2", + ".", "a.", ".a", "a..b", "1.", ".1", "1..2", + " ", "a ", " a", "a b", "1 ", " 1", "1 2", + "A.a", "aB.a", "ab.A", "A1.a", "a1.A", + "A.1", "aB.1", "A1.1", "1A.1", + "0.A", "01.A", "012.A", "1A.a", "1a.A", + "A.B.C.D.E", "AA.BB.CC.DD.EE", "a.B.c.d.e", "aa.bB.cc.dd.ee", + "a@b", "a,b", "a_b", "a;b", + "a:b", "a%b", "a?b", "a$b", + strings.Repeat("a", 254), + } + for _, val := range badValues { + if msgs := IsDNS1123Subdomain(val); len(msgs) == 0 { + t.Errorf("expected false for '%s'", val) + } + } +} + +func TestIsDNS1123SubdomainCaseless(t *testing.T) { + goodValues := []string{ + "a", "ab", "abc", "a1", "a-1", "a--1--2--b", + "0", "01", "012", "1a", "1-a", "1--a--b--2", + "a.a", "ab.a", "abc.a", "a1.a", "a-1.a", "a--1--2--b.a", + "a.1", "ab.1", "abc.1", "a1.1", "a-1.1", "a--1--2--b.1", + "0.a", "01.a", "012.a", "1a.a", "1-a.a", "1--a--b--2", + "0.1", "01.1", "012.1", "1a.1", "1-a.1", "1--a--b--2.1", + "a.b.c.d.e", "aa.bb.cc.dd.ee", "1.2.3.4.5", "11.22.33.44.55", + "A", "AB", "ABC", "A1", "A-1", + "A.A", "AB.A", "ABC.A", "A1.A", "A-1.A", + "A.B.C.D.E", "AA.BB.CC.DD.EE", + "a.B.c.d.e", "aa.bB.cc.dd.ee", + strings.Repeat("a", 253), + strings.Repeat("A", 253), + } + for _, val := range goodValues { + if msgs := IsDNS1123SubdomainCaseless(val); len(msgs) != 0 { + t.Errorf("expected true for '%s': %v", val, msgs) + } + } + + badValues := []string{ + "", + "-", "a-", "-a", "1-", "-1", + "_", "a_", "_a", "a_b", "1_", "_1", "1_2", + ".", "a.", ".a", "a..b", "1.", ".1", "1..2", + " ", "a ", " a", "a b", "1 ", " 1", "1 2", + "a@b", "a,b", "a_b", "a;b", + "a:b", "a%b", "a?b", "a$b", + strings.Repeat("a", 254), + } + for _, val := range badValues { + if msgs := IsDNS1123SubdomainCaseless(val); len(msgs) == 0 { + t.Errorf("expected false for '%s'", val) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/errors.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/errors.go new file mode 100644 index 0000000000..13eeced1a0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/errors.go @@ -0,0 +1,72 @@ +/* +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 content + +import ( + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/api/validate/constraints" +) + +// MinError returns a string explanation of a "must be greater than or equal" +// validation failure. +func MinError[T constraints.Integer](min T) string { + return fmt.Sprintf("must be greater than or equal to %d", min) +} + +// MaxError returns a string explanation of a "must be less than or equal" +// validation failure. +func MaxError[T constraints.Integer](max T) string { + return fmt.Sprintf("must be less than or equal to %d", max) +} + +// MaxLenError returns a string explanation of a "string too long" validation +// failure. +func MaxLenError(length int) string { + return fmt.Sprintf("must be no more than %d bytes", length) +} + +// EmptyError returns a string explanation of an "empty string" validation. +func EmptyError() string { + return "must be non-empty" +} + +// RegexError returns a string explanation of a regex validation failure. +func RegexError(msg string, re string, examples ...string) string { + if len(examples) == 0 { + return msg + " (regex used for validation is '" + re + "')" + } + msg += " (e.g. " + for i := range examples { + if i > 0 { + msg += " or " + } + msg += "'" + examples[i] + "', " + } + msg += "regex used for validation is '" + re + "')" + return msg +} + +// NEQError returns a string explanation of a "must not be equal to" validation failure. +func NEQError[T any](disallowed T) string { + format := "%v" + if reflect.ValueOf(disallowed).Kind() == reflect.String { + format = "%q" + } + return fmt.Sprintf("must not be equal to "+format, disallowed) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/identifier.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/identifier.go new file mode 100644 index 0000000000..3913ec9916 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/identifier.go @@ -0,0 +1,35 @@ +/* +Copyright 2025 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 content + +import ( + "regexp" +) + +const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*" +const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'" + +var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$") + +// IsCIdentifier tests for a string that conforms the definition of an identifier +// in C. This checks the format, but not the length. +func IsCIdentifier(value string) []string { + if !cIdentifierRegexp.MatchString(value) { + return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")} + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/identifier_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/identifier_test.go new file mode 100644 index 0000000000..e5c1bf84ab --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/identifier_test.go @@ -0,0 +1,46 @@ +/* +Copyright 2025 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 content + +import ( + "testing" +) + +func TestIsCIdentifier(t *testing.T) { + goodValues := []string{ + "a", "ab", "abc", "a1", "_a", "a_", "a_b", "a_1", "a__1__2__b", "__abc_123", + "A", "AB", "AbC", "A1", "_A", "A_", "A_B", "A_1", "A__1__2__B", "__123_ABC", + } + for _, val := range goodValues { + if msgs := IsCIdentifier(val); len(msgs) != 0 { + t.Errorf("expected true for '%s': %v", val, msgs) + } + } + + badValues := []string{ + "", "1", "123", "1a", + "-", "a-", "-a", "1-", "-1", "1_", "1_2", + ".", "a.", ".a", "a.b", "1.", ".1", "1.2", + " ", "a ", " a", "a b", "1 ", " 1", "1 2", + "#a#", + } + for _, val := range badValues { + if msgs := IsCIdentifier(val); len(msgs) == 0 { + t.Errorf("expected false for '%s'", val) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/kube.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/kube.go new file mode 100644 index 0000000000..608073f708 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/kube.go @@ -0,0 +1,117 @@ +/* +Copyright 2025 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 content + +import ( + "regexp" + "strings" +) + +const labelKeyCharFmt string = "[A-Za-z0-9]" +const labelKeyExtCharFmt string = "[-A-Za-z0-9_.]" +const labelKeyFmt string = "(" + labelKeyCharFmt + labelKeyExtCharFmt + "*)?" + labelKeyCharFmt +const labelKeyErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character" +const labelKeyMaxLength int = 63 + +var labelKeyRegexp = regexp.MustCompile("^" + labelKeyFmt + "$") + +// IsQualifiedName tests whether the value passed is what Kubernetes calls a +// "qualified name", which is the same as a label key. +// +// Deprecated: use IsLabelKey instead. +var IsQualifiedName = IsLabelKey + +// IsLabelKey tests whether the value passed is a valid label key. This format +// is used to validate many fields in the Kubernetes API. +// Label keys consist of an optional prefix and a name, separated by a '/'. +// If the value is not valid, a list of error strings is returned. Otherwise, an +// empty list (or nil) is returned. +func IsLabelKey(value string) []string { + var errs []string + parts := strings.Split(value, "/") + var name string + switch len(parts) { + case 1: + name = parts[0] + case 2: + var prefix string + prefix, name = parts[0], parts[1] + if len(prefix) == 0 { + errs = append(errs, "prefix part "+EmptyError()) + } else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 { + errs = append(errs, prefixEach(msgs, "prefix part ")...) + } + default: + return append(errs, "a valid label key "+RegexError(labelKeyErrMsg, labelKeyFmt, "MyName", "my.name", "123-abc")+ + " with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')") + } + + if len(name) == 0 { + errs = append(errs, "name part "+EmptyError()) + } else if len(name) > labelKeyMaxLength { + errs = append(errs, "name part "+MaxLenError(labelKeyMaxLength)) + } + if !labelKeyRegexp.MatchString(name) { + errs = append(errs, "name part "+RegexError(labelKeyErrMsg, labelKeyFmt, "MyName", "my.name", "123-abc")) + } + return errs +} + +const labelValueFmt string = "(" + labelKeyFmt + ")?" +const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character" + +// LabelValueMaxLength is a label's max length +const LabelValueMaxLength int = 63 + +var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$") + +// IsLabelValue tests whether the value passed is a valid label value. If +// the value is not valid, a list of error strings is returned. Otherwise an +// empty list (or nil) is returned. +func IsLabelValue(value string) []string { + var errs []string + if len(value) > LabelValueMaxLength { + errs = append(errs, MaxLenError(LabelValueMaxLength)) + } + if !labelValueRegexp.MatchString(value) { + errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345")) + } + return errs +} + +func prefixEach(msgs []string, prefix string) []string { + for i := range msgs { + msgs[i] = prefix + msgs[i] + } + return msgs +} + +// IsPrefixedLabelKey tests whether the value passed is a valid label key with +// a domain prefix. This allows "example.com/key" but not "key". +// If the value is not valid, a list of error strings is returned. Otherwise, +// an empty list (or nil) is returned. +func IsPrefixedLabelKey(value string) []string { + if errs := IsLabelKey(value); len(errs) > 0 { + return errs + } + + segments := strings.Split(value, "/") + if len(segments) != 2 { + return []string{"must include a prefix (e.g. 'example.com/key')"} + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/kube_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/kube_test.go new file mode 100644 index 0000000000..59f6f2cd28 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/kube_test.go @@ -0,0 +1,145 @@ +/* +Copyright 2025 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 content + +import ( + "strings" + "testing" +) + +func TestIsLabelKey(t *testing.T) { + successCases := []string{ + "simple", + "now-with-dashes", + "1-starts-with-num", + "1234", + "simple/simple", + "now-with-dashes/simple", + "now-with-dashes/now-with-dashes", + "now.with.dots/simple", + "now-with.dashes-and.dots/simple", + "1-num.2-num/3-num", + "1234/5678", + "1.2.3.4/5678", + "Uppercase_Is_OK_123", + "example.com/Uppercase_Is_OK_123", + "requests.storage-foo", + strings.Repeat("a", 63), + strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63), + } + for i := range successCases { + if errs := IsLabelKey(successCases[i]); len(errs) != 0 { + t.Errorf("case[%d]: %q: expected success: %v", i, successCases[i], errs) + } + } + + errorCases := []string{ + "nospecialchars%^=@", + "cantendwithadash-", + "-cantstartwithadash-", + "only/one/slash", + "Example.com/abc", + "example_com/abc", + "example.com/", + "/simple", + strings.Repeat("a", 64), + strings.Repeat("a", 254) + "/abc", + } + for i := range errorCases { + if errs := IsLabelKey(errorCases[i]); len(errs) == 0 { + t.Errorf("case[%d]: %q: expected failure", i, errorCases[i]) + } + } +} + +func TestIsLabelValue(t *testing.T) { + successCases := []string{ + "simple", + "now-with-dashes", + "1-starts-with-num", + "end-with-num-1", + "1234", // only num + strings.Repeat("a", 63), // to the limit + "", // empty value + } + for i := range successCases { + if errs := IsLabelValue(successCases[i]); len(errs) != 0 { + t.Errorf("case %s expected success: %v", successCases[i], errs) + } + } + + errorCases := []string{ + "nospecialchars%^=@", + "Tama-nui-te-rā.is.Māori.sun", + "\\backslashes\\are\\bad", + "-starts-with-dash", + "ends-with-dash-", + ".starts.with.dot", + "ends.with.dot.", + strings.Repeat("a", 64), // over the limit + } + for i := range errorCases { + if errs := IsLabelValue(errorCases[i]); len(errs) == 0 { + t.Errorf("case[%d] expected failure", i) + } + } +} + +func TestIsPrefixedLabelKey(t *testing.T) { + successCases := []string{ + "simple/simple", + "now-with-dashes/simple", + "now-with-dashes/now-with-dashes", + "now.with.dots/simple", + "now-with.dashes-and.dots/simple", + "1-num.2-num/3-num", + "1234/5678", + "1.2.3.4/5678", + "example.com/Uppercase_Is_OK_123", + strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63), + } + for i := range successCases { + if errs := IsPrefixedLabelKey(successCases[i]); len(errs) != 0 { + t.Errorf("case[%d]: %q: expected success: %v", i, successCases[i], errs) + } + } + + errorCases := []string{ + "simple", + "now-with-dashes", + "1-starts-with-num", + "1234", + "Uppercase_Is_OK_123", + "requests.storage-foo", + strings.Repeat("a", 63), + "nospecialchars%^=@", + "cantendwithadash-", + "-cantstartwithadash-", + "only/one/slash", + "Example.com/abc", + "example_com/abc", + "example.com/", + "/simple", + strings.Repeat("a", 64), + strings.Repeat("a", 254) + "/abc", + } + for i := range errorCases { + if errs := IsPrefixedLabelKey(errorCases[i]); len(errs) == 0 { + t.Errorf("case[%d]: %q: expected failure", i, errorCases[i]) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/path.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/path.go new file mode 100644 index 0000000000..c41b1d4731 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/path.go @@ -0,0 +1,63 @@ +/* +Copyright 2015 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 content + +import ( + "fmt" + "strings" +) + +// Strings that cannot be used as names specified as path segments (like the +// REST API or etcd store). +var pathSegmentNameMayNotBe = []string{".", ".."} + +// Substrings that cannot be used in names specified as path segments (like the +// REST API or etcd store). +var pathSegmentNameMayNotContain = []string{"/", "%"} + +// IsPathSegmentName validates the name can be safely encoded as a path +// segment. +// +// Note that, for historical reason, this function does not check for +// empty strings or impose a limit on the length of the name. +func IsPathSegmentName(name string) []string { + for _, illegalName := range pathSegmentNameMayNotBe { + if name == illegalName { + return []string{fmt.Sprintf(`may not be '%s'`, illegalName)} + } + } + + return IsPathSegmentPrefix(name) +} + +// IsPathSegmentPrefix validates the name can be used as a prefix for a +// name which will be encoded as a path segment It does not check for exact +// matches with disallowed names, since an arbitrary suffix might make the name +// valid. +// +// Note that, for historical reason, this function does not check for +// empty strings or impose a limit on the length of the name. +func IsPathSegmentPrefix(name string) []string { + var errors []string + for _, illegalContent := range pathSegmentNameMayNotContain { + if strings.Contains(name, illegalContent) { + errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent)) + } + } + + return errors +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/path_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/path_test.go new file mode 100644 index 0000000000..16a1119e82 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/content/path_test.go @@ -0,0 +1,158 @@ +/* +Copyright 2015 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 content + +import ( + "strings" + "testing" +) + +func TestIsPathSegmentPrefix(t *testing.T) { + testcases := map[string]struct { + Name string + ExpectedMsg string + }{ + "empty": { + Name: "", + ExpectedMsg: "", // NOTE: this probably should fail + }, + // NOTE: no validation of max length + "valid short": { + Name: "foo", + ExpectedMsg: "", + }, + "valid long": { + Name: "foo.bar.baz", + ExpectedMsg: "", + }, + // Make sure mixed case, non DNS subdomain characters are tolerated + "valid complex": { + Name: "sha256:ABCDEF012345@ABCDEF012345", + ExpectedMsg: "", + }, + // Make sure non-ascii characters are tolerated + "valid extended charset": { + Name: "Iñtërnâtiônàlizætiøn", + ExpectedMsg: "", + }, + "dot": { + Name: ".", + ExpectedMsg: "", + }, + "dot leading": { + Name: ".test", + ExpectedMsg: "", + }, + "dot dot": { + Name: "..", + ExpectedMsg: "", + }, + "dot dot leading": { + Name: "..test", + ExpectedMsg: "", + }, + "slash": { + Name: "foo/bar", + ExpectedMsg: "/", + }, + "percent": { + Name: "foo%bar", + ExpectedMsg: "%", + }, + } + + for k, tc := range testcases { + msgs := IsPathSegmentPrefix(tc.Name) + if len(tc.ExpectedMsg) == 0 && len(msgs) > 0 { + t.Errorf("%s: expected no error, got %v", k, msgs) + } + if len(tc.ExpectedMsg) > 0 && len(msgs) == 0 { + t.Errorf("%s: expected error, got none", k) + } + if len(tc.ExpectedMsg) > 0 && !strings.Contains(msgs[0], tc.ExpectedMsg) { + t.Errorf("%s: expected error containing %q, got %v", k, tc.ExpectedMsg, msgs[0]) + } + } +} + +func TestIsPathSegmentName(t *testing.T) { + testcases := map[string]struct { + Name string + ExpectedMsg string + }{ + "empty": { + Name: "", + ExpectedMsg: "", // NOTE: this probably should fail + }, + // NOTE: no validation of max length + "valid short": { + Name: "foo", + ExpectedMsg: "", + }, + "valid long": { + Name: "foo.bar.baz", + ExpectedMsg: "", + }, + // Make sure mixed case, non DNS subdomain characters are tolerated + "valid complex": { + Name: "sha256:ABCDEF012345@ABCDEF012345", + ExpectedMsg: "", + }, + // Make sure non-ascii characters are tolerated + "valid extended charset": { + Name: "Iñtërnâtiônàlizætiøn", + ExpectedMsg: "", + }, + "dot": { + Name: ".", + ExpectedMsg: ".", + }, + "dot leading": { + Name: ".test", + ExpectedMsg: "", + }, + "dot dot": { + Name: "..", + ExpectedMsg: "..", + }, + "dot dot leading": { + Name: "..test", + ExpectedMsg: "", + }, + "slash": { + Name: "foo/bar", + ExpectedMsg: "/", + }, + "percent": { + Name: "foo%bar", + ExpectedMsg: "%", + }, + } + + for k, tc := range testcases { + msgs := IsPathSegmentName(tc.Name) + if len(tc.ExpectedMsg) == 0 && len(msgs) > 0 { + t.Errorf("%s: expected no error, got %v", k, msgs) + } + if len(tc.ExpectedMsg) > 0 && len(msgs) == 0 { + t.Errorf("%s: expected error, got none", k) + } + if len(tc.ExpectedMsg) > 0 && !strings.Contains(msgs[0], tc.ExpectedMsg) { + t.Errorf("%s: expected error containing %q, got %v", k, tc.ExpectedMsg, msgs[0]) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/dependentrequired.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/dependentrequired.go new file mode 100644 index 0000000000..69222c9752 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/dependentrequired.go @@ -0,0 +1,83 @@ +/* +Copyright 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 validate + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// DependentRequired verifies that when triggerIsSet(obj) is true, dependentIsSet(obj) +// is also true; otherwise reports an error at fldPath.Child(dependentName). +// On Update, the check is skipped if neither side's set-ness changed from oldObj, +// so unrelated updates can proceed past a pre-existing violation. +func DependentRequired[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *T, + triggerName string, triggerIsSet ExtractorFn[*T, bool], + dependentName string, dependentIsSet ExtractorFn[*T, bool], +) field.ErrorList { + if obj == nil { + return nil + } + if op.Type == operation.Update && oldObj != nil { + if triggerIsSet(obj) == triggerIsSet(oldObj) && dependentIsSet(obj) == dependentIsSet(oldObj) { + return nil + } + } + if !triggerIsSet(obj) { + return nil + } + if dependentIsSet(obj) { + return nil + } + return field.ErrorList{ + field.Required(fldPath.Child(dependentName), + fmt.Sprintf("must be set when %s is set", triggerName)). + WithOrigin("dependentRequired"), + } +} + +// DependentForbidden verifies that when triggerIsSet(obj) is true, dependentIsSet(obj) +// is false; otherwise reports an error at fldPath.Child(dependentName). +// On Update, the check is skipped if neither side's set-ness changed from oldObj, +// so unrelated updates can proceed past a pre-existing violation. +func DependentForbidden[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *T, + triggerName string, triggerIsSet ExtractorFn[*T, bool], + dependentName string, dependentIsSet ExtractorFn[*T, bool], +) field.ErrorList { + if obj == nil { + return nil + } + if op.Type == operation.Update && oldObj != nil { + if triggerIsSet(obj) == triggerIsSet(oldObj) && dependentIsSet(obj) == dependentIsSet(oldObj) { + return nil + } + } + if !triggerIsSet(obj) { + return nil + } + if !dependentIsSet(obj) { + return nil + } + return field.ErrorList{ + field.Forbidden(fldPath.Child(dependentName), + fmt.Sprintf("may not be set when %s is set", triggerName)). + WithOrigin("dependentForbidden"), + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/dependentrequired_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/dependentrequired_test.go new file mode 100644 index 0000000000..5727b9327a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/dependentrequired_test.go @@ -0,0 +1,194 @@ +/* +Copyright 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 validate + +import ( + "context" + "regexp" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestDependentRequired(t *testing.T) { + type obj struct { + Trigger *string + Dependent *string + OtherField *string + } + + triggerIsSet := func(o *obj) bool { return o != nil && o.Trigger != nil } + dependentIsSet := func(o *obj) bool { return o != nil && o.Dependent != nil } + + cases := []struct { + name string + op operation.Operation + obj *obj + oldObj *obj + err string // regex; empty means expect no error + }{{ + name: "create: trigger unset, dependent set", + op: operation.Operation{Type: operation.Create}, + obj: &obj{Dependent: new("d")}, + }, { + name: "create: trigger set, dependent set", + op: operation.Operation{Type: operation.Create}, + obj: &obj{Trigger: new("t"), Dependent: new("d")}, + }, { + name: "create: trigger set, dependent unset", + op: operation.Operation{Type: operation.Create}, + obj: &obj{Trigger: new("t")}, + err: `fldpath\.dependent: Required value: must be set when trigger is set`, + }, { + name: "create: nil obj", + op: operation.Operation{Type: operation.Create}, + obj: nil, + }, { + name: "ratchet: unrelated field changed, trigger and dependent set-ness unchanged", + op: operation.Operation{Type: operation.Update}, + obj: &obj{Trigger: new("t"), OtherField: new("new")}, + oldObj: &obj{Trigger: new("t"), OtherField: new("old")}, + }, { + name: "ratchet: trigger value changed, set-ness unchanged", + op: operation.Operation{Type: operation.Update}, + obj: &obj{Trigger: new("t")}, + oldObj: &obj{Trigger: new("old")}, + }, { + name: "update: trigger newly set", + op: operation.Operation{Type: operation.Update}, + obj: &obj{Trigger: new("t")}, + oldObj: &obj{}, + err: `fldpath\.dependent: Required value: must be set when trigger is set`, + }, { + name: "update: dependent newly cleared", + op: operation.Operation{Type: operation.Update}, + obj: &obj{Trigger: new("t")}, + oldObj: &obj{Trigger: new("t"), Dependent: new("d")}, + err: `fldpath\.dependent: Required value: must be set when trigger is set`, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + result := DependentRequired(context.Background(), tc.op, + field.NewPath("fldpath"), tc.obj, tc.oldObj, + "trigger", triggerIsSet, + "dependent", dependentIsSet) + if len(result) > 0 && tc.err == "" { + t.Fatalf("unexpected failure: %v", fmtErrs(result)) + } + if len(result) == 0 && tc.err != "" { + t.Fatalf("unexpected success: expected %q", tc.err) + } + if len(result) > 1 { + t.Fatalf("unexpected multi-error: %v", fmtErrs(result)) + } + if len(result) > 0 { + if !regexp.MustCompile(tc.err).MatchString(result[0].Error()) { + t.Errorf("wrong error\nexpected: %q\n got: %v", tc.err, fmtErrs(result)) + } + if result[0].Origin != "dependentRequired" { + t.Errorf("expected origin %q, got %q", "dependentRequired", result[0].Origin) + } + } + }) + } +} + +func TestDependentForbidden(t *testing.T) { + type obj struct { + Trigger *string + Dependent *string + OtherField *string + } + + triggerIsSet := func(o *obj) bool { return o != nil && o.Trigger != nil } + dependentIsSet := func(o *obj) bool { return o != nil && o.Dependent != nil } + + cases := []struct { + name string + op operation.Operation + obj *obj + oldObj *obj + err string // regex; empty means expect no error + }{{ + name: "create: trigger unset, dependent set", + op: operation.Operation{Type: operation.Create}, + obj: &obj{Dependent: new("d")}, + }, { + name: "create: trigger set, dependent unset", + op: operation.Operation{Type: operation.Create}, + obj: &obj{Trigger: new("t")}, + }, { + name: "create: trigger set, dependent set", + op: operation.Operation{Type: operation.Create}, + obj: &obj{Trigger: new("t"), Dependent: new("d")}, + err: `fldpath\.dependent: Forbidden: may not be set when trigger is set`, + }, { + name: "create: nil obj", + op: operation.Operation{Type: operation.Create}, + obj: nil, + }, { + name: "ratchet: unrelated field changed, trigger and dependent set-ness unchanged", + op: operation.Operation{Type: operation.Update}, + obj: &obj{Trigger: new("t"), Dependent: new("d"), OtherField: new("new")}, + oldObj: &obj{Trigger: new("t"), Dependent: new("d"), OtherField: new("old")}, + }, { + name: "ratchet: trigger value changed, set-ness unchanged", + op: operation.Operation{Type: operation.Update}, + obj: &obj{Trigger: new("t"), Dependent: new("d")}, + oldObj: &obj{Trigger: new("old"), Dependent: new("d")}, + }, { + name: "update: trigger newly set", + op: operation.Operation{Type: operation.Update}, + obj: &obj{Trigger: new("t"), Dependent: new("d")}, + oldObj: &obj{Dependent: new("d")}, + err: `fldpath\.dependent: Forbidden: may not be set when trigger is set`, + }, { + name: "update: dependent newly set", + op: operation.Operation{Type: operation.Update}, + obj: &obj{Trigger: new("t"), Dependent: new("d")}, + oldObj: &obj{Trigger: new("t")}, + err: `fldpath\.dependent: Forbidden: may not be set when trigger is set`, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + result := DependentForbidden(context.Background(), tc.op, + field.NewPath("fldpath"), tc.obj, tc.oldObj, + "trigger", triggerIsSet, + "dependent", dependentIsSet) + if len(result) > 0 && tc.err == "" { + t.Fatalf("unexpected failure: %v", fmtErrs(result)) + } + if len(result) == 0 && tc.err != "" { + t.Fatalf("unexpected success: expected %q", tc.err) + } + if len(result) > 1 { + t.Fatalf("unexpected multi-error: %v", fmtErrs(result)) + } + if len(result) > 0 { + if !regexp.MustCompile(tc.err).MatchString(result[0].Error()) { + t.Errorf("wrong error\nexpected: %q\n got: %v", tc.err, fmtErrs(result)) + } + if result[0].Origin != "dependentForbidden" { + t.Errorf("expected origin %q, got %q", "dependentForbidden", result[0].Origin) + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/discriminator.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/discriminator.go new file mode 100644 index 0000000000..6f4b3cddda --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/discriminator.go @@ -0,0 +1,74 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// DiscriminatedRule defines a validation to apply for a specific discriminator value. +type DiscriminatedRule[Tfield any, Tdisc comparable] struct { + Value Tdisc + Validation ValidateFunc[Tfield] +} + +// Discriminated validates a member field based on a discriminator value. +// It iterates through the rules and applies the first one that matches the discriminator. +// If no rule matches, it applies the defaultValidation if provided. +// +// It performs ratcheting: if the operation is an Update, and neither the discriminator +// nor the value (checked via equiv) have changed, validation is skipped. +// +// The equiv function can be called with nil arguments in the case of nilable +// fields. +func Discriminated[Tfield any, Tdisc comparable, Tstruct any](ctx context.Context, op operation.Operation, structPath *field.Path, + obj, oldObj *Tstruct, fieldName string, getMemberValue func(*Tstruct) Tfield, getDiscriminator func(*Tstruct) Tdisc, + equiv MatchFunc[Tfield], defaultValidation ValidateFunc[Tfield], rules []DiscriminatedRule[Tfield, Tdisc], +) field.ErrorList { + value := getMemberValue(obj) + discriminator := getDiscriminator(obj) + var oldValue Tfield + var oldDiscriminator Tdisc + + if oldObj != nil { + oldValue = getMemberValue(oldObj) + oldDiscriminator = getDiscriminator(oldObj) + } + + if op.Type == operation.Update && oldObj != nil && discriminator == oldDiscriminator && equiv(value, oldValue) { + return nil + } + + fldPath := structPath.Child(fieldName) + for _, rule := range rules { + if rule.Value == discriminator { + if rule.Validation == nil { + return nil + } + return rule.Validation(ctx, op, fldPath, value, oldValue) + } + } + + if defaultValidation != nil { + return defaultValidation(ctx, op, fldPath, value, oldValue) + } + + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/discriminator_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/discriminator_test.go new file mode 100644 index 0000000000..ae9621b2a9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/discriminator_test.go @@ -0,0 +1,265 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestDiscriminated(t *testing.T) { + errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")} + errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")} + + mockValid := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return nil + } + mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errMatch + } + mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errDefault + } + + // mockEqual compares pointer values by dereferencing, not by pointer identity. + mockEqual := func(a, b *string) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b + } + + testCases := []struct { + name string + opType operation.Type + discriminator string + oldDiscriminator string + value *string + oldValue *string + rules []DiscriminatedRule[*string, string] + defaultValidation ValidateFunc[*string] + expected field.ErrorList + }{ + { + name: "matches rule, returns valid", + opType: operation.Create, + discriminator: "A", + oldDiscriminator: "A", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: mockValid}, + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: nil, + }, + { + name: "matches rule, returns error", + opType: operation.Create, + discriminator: "B", + oldDiscriminator: "B", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: mockValid}, + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: errMatch, + }, + { + name: "ratcheting: update, unchanged, skips validation", + opType: operation.Update, + discriminator: "B", + oldDiscriminator: "B", // unchanged + value: nil, + oldValue: nil, // unchanged + rules: []DiscriminatedRule[*string, string]{ + {Value: "B", Validation: mockErrorMatch}, // would fail if run + }, + defaultValidation: mockErrorDefault, + expected: nil, + }, + { + name: "ratcheting: update, same value different pointers, skips validation", + opType: operation.Update, + discriminator: "B", + oldDiscriminator: "B", + value: strPtr("same"), + oldValue: strPtr("same"), // different pointer, same value + rules: []DiscriminatedRule[*string, string]{ + {Value: "B", Validation: mockErrorMatch}, // would fail if run + }, + defaultValidation: mockErrorDefault, + expected: nil, + }, + { + name: "ratcheting: update, discriminator changed, runs validation", + opType: operation.Update, + discriminator: "B", + oldDiscriminator: "A", // changed + value: nil, + oldValue: nil, + rules: []DiscriminatedRule[*string, string]{ + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: errMatch, + }, + { + name: "ratcheting: update, value changed, discriminator unchanged, runs validation", + opType: operation.Update, + discriminator: "B", + oldDiscriminator: "B", // unchanged + value: strPtr("new"), + oldValue: strPtr("old"), // changed + rules: []DiscriminatedRule[*string, string]{ + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: errMatch, + }, + { + name: "matches rule with nil validation, returns valid", + opType: operation.Create, + discriminator: "A", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: nil}, + }, + defaultValidation: mockErrorDefault, + expected: nil, + }, + { + name: "no match, runs default", + opType: operation.Create, + discriminator: "C", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: mockValid}, + {Value: "B", Validation: mockErrorMatch}, + }, + defaultValidation: mockErrorDefault, + expected: errDefault, + }, + { + name: "no match, nil default, returns valid", + opType: operation.Create, + discriminator: "C", + rules: []DiscriminatedRule[*string, string]{ + {Value: "A", Validation: mockValid}, + }, + defaultValidation: nil, + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + oldDisc := tc.oldDiscriminator + if oldDisc == "" { + oldDisc = tc.discriminator + } + + type StringP struct { + Val *string + Disc string + } + newObj := &StringP{Val: tc.value, Disc: tc.discriminator} + var oldObj *StringP + if tc.opType == operation.Update { + oldObj = &StringP{Val: tc.oldValue, Disc: oldDisc} + } + getVal := func(p *StringP) *string { return p.Val } + getDisc := func(p *StringP) string { return p.Disc } + got := Discriminated[*string, string, StringP](context.Background(), operation.Operation{Type: tc.opType}, field.NewPath("root"), newObj, oldObj, "field", getVal, getDisc, mockEqual, tc.defaultValidation, tc.rules) + + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + } +} + +func TestDiscriminatedIntDiscriminator(t *testing.T) { + errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")} + errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")} + + mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errMatch + } + mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errDefault + } + + mockEqual := func(a, b *string) bool { + return a == b + } + + type IntP struct { + Val *string + Disc int + } + newObj := &IntP{Val: nil, Disc: 1} + getVal := func(p *IntP) *string { return p.Val } + getDisc := func(p *IntP) int { return p.Disc } + + got := Discriminated[*string, int, IntP](context.Background(), operation.Operation{Type: operation.Create}, field.NewPath("root"), newObj, nil, "field", getVal, getDisc, mockEqual, mockErrorDefault, []DiscriminatedRule[*string, int]{ + {Value: 1, Validation: mockErrorMatch}, + }) + if !reflect.DeepEqual(got, errMatch) { + t.Errorf("int discriminator: got %v want %v", got, errMatch) + } +} + +func TestDiscriminatedBoolDiscriminator(t *testing.T) { + errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")} + errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")} + + mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errMatch + } + mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList { + return errDefault + } + + mockEqual := func(a, b *string) bool { + return a == b + } + + type BoolP struct { + Val *string + Disc bool + } + newObj := &BoolP{Val: nil, Disc: true} + getVal := func(p *BoolP) *string { return p.Val } + getDisc := func(p *BoolP) bool { return p.Disc } + + got := Discriminated[*string, bool, BoolP](context.Background(), operation.Operation{Type: operation.Create}, field.NewPath("root"), newObj, nil, "field", getVal, getDisc, mockEqual, mockErrorDefault, []DiscriminatedRule[*string, bool]{ + {Value: true, Validation: mockErrorMatch}, + }) + if !reflect.DeepEqual(got, errMatch) { + t.Errorf("bool discriminator: got %v want %v", got, errMatch) + } +} + +// strPtr returns a new pointer to a copy of s, guaranteeing a distinct allocation. +func strPtr(s string) *string { + return &s +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/doc.go new file mode 100644 index 0000000000..eee13e9b38 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/doc.go @@ -0,0 +1,50 @@ +/* +Copyright 2024 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 validate holds API validation functions which are designed for use +// with the k8s.io/code-generator/cmd/validation-gen tool. Each validation +// function has a similar fingerprint: +// +// func (ctx context.Context, +// op operation.Operation, +// fldPath *field.Path, +// value, oldValue , +// ) field.ErrorList +// +// The value and oldValue arguments will always be a nilable type. If the +// original value was a string, these will be a *string. If the original value +// was a slice or map, these will be the same slice or map type. +// +// For a CREATE operation, the oldValue will always be nil. For an UPDATE +// operation, either value or oldValue may be nil, e.g. when adding or removing +// a value in a list-map. Validators which care about UPDATE operations should +// look at the opCtx argument to know which operation is being executed. +// +// Tightened validation (also known as ratcheting validation) is supported by +// defining a new validation function. For example: +// +// func TightenedMaxLength(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *string) field.ErrorList { +// if oldValue != nil && len(MaxLength(ctx, op, fldPath, oldValue, nil)) > 0 { +// // old value is not valid, so this value skips the tightened validation +// return nil +// } +// return MaxLength(ctx, op, fldPath, value, nil) +// } +// +// In general, we cannot distinguish a non-specified slice or map from one that +// is specified but empty. Validators should not rely on nil values, but use +// len() instead. +package validate diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/each.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/each.go new file mode 100644 index 0000000000..affc44576b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/each.go @@ -0,0 +1,348 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "reflect" + "slices" + "sort" + + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// MatchFunc is a function that compares two values of the same type, +// according to some criteria, and returns true if they match. +type MatchFunc[T any] func(T, T) bool + +// EachValSliceVal performs validation on each element of a slice of values +// using the provided validation function. +// +// For update operations, the match function finds corresponding values in +// oldSlice for each value in newSlice. This comparison can be either full or +// partial (e.g., matching only specific struct fields that serve as a unique +// identifier). If match is nil, validation proceeds without considering old +// values, and the equiv function is not used. +// +// For update operations, the equiv function checks if a new value is +// equivalent to its corresponding old value, enabling validation ratcheting. +// If equiv is nil but match is provided, the match function is assumed to +// perform full value comparison. +// +// The match and equiv functions will never be called with nil arguments. +// +// Note: The slice element type must be non-nilable. +func EachValSliceVal[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newSlice, oldSlice []T, + match, equiv MatchFunc[*T], validator ValidateFunc[*T]) field.ErrorList { + var errs field.ErrorList + for i := range newSlice { + val := &newSlice[i] + + var old *T + if match != nil && len(oldSlice) > 0 { + old = lookup(oldSlice, val, match) + } + // If the operation is an update, for validation ratcheting, skip re-validating if the old + // value exists and either: + // 1. The match function provides full comparison (equiv is nil) + // 2. The equiv function confirms the values are equivalent (either directly or semantically) + // + // The equiv function provides equality comparison when match uses partial comparison. + if op.Type == operation.Update && old != nil && (equiv == nil || equiv(val, old)) { + continue + } + errs = append(errs, validator(ctx, op, fldPath.Index(i), val, old)...) + } + return errs +} + +// EachPtrSliceVal performs validation on each element of a slice of pointers +// using the provided validation function. +// +// For update operations, the match function finds corresponding values in +// oldSlice for each value in newSlice. This comparison can be either full or +// partial (e.g., matching only specific struct fields that serve as a unique +// identifier). If match is nil, validation proceeds without considering old +// values, and the equiv function is not used. +// +// For update operations, the equiv function checks if a new value is +// equivalent to its corresponding old value, enabling validation ratcheting. +// If equiv is nil but match is provided, the match function is assumed to +// perform full value comparison. +// +// The match and equiv functions will never be called with nil arguments. +func EachPtrSliceVal[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newSlice, oldSlice []*T, + match, equiv MatchFunc[*T], validator ValidateFunc[*T]) field.ErrorList { + var errs field.ErrorList + for i := range newSlice { + val := newSlice[i] + if val == nil { + // Ignore nil items; they are supposed to have been checked by PtrSliceNoNils. + continue + } + + var old *T + if match != nil && len(oldSlice) > 0 { + old = lookupPointer(oldSlice, val, match) + } + if op.Type == operation.Update && old != nil && (equiv == nil || equiv(val, old)) { + continue + } + errs = append(errs, validator(ctx, op, fldPath.Index(i), val, old)...) + } + return errs +} + +// lookup returns a pointer to the first element in the list that matches the +// target, according to the provided comparison function, or else nil. +func lookup[T any](list []T, target *T, match MatchFunc[*T]) *T { + for i := range list { + if match(&list[i], target) { + return &list[i] + } + } + return nil +} + +// lookupPointer returns the first non-nil element in the list that matches the +// target, according to the provided comparison function, or else nil. +// Nil elements in the list are skipped. +func lookupPointer[T any](list []*T, target *T, match MatchFunc[*T]) *T { + for i := range list { + if list[i] == nil { + // We can't really do anything about nil entries in the old list, + // just skip them. + continue + } + if match(list[i], target) { + return list[i] + } + } + return nil +} + +// EachMapVal validates each value in newMap using the specified validation +// function, passing the corresponding old value from oldMap if the key exists in oldMap. +// For update operations, it implements validation ratcheting by skipping validation +// when the old value exists and the equiv function confirms the values are equivalent. +// The value-type of the map is assumed to not be nilable. +// +// The equiv function will never be called with nil arguments. +// +// If equiv is nil, value-based ratcheting is disabled and all values will be validated. +func EachMapVal[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]V, + equiv MatchFunc[*V], validator ValidateFunc[*V]) field.ErrorList { + var errs field.ErrorList + for key, val := range newMap { + var old *V + if o, found := oldMap[key]; found { + old = &o + } + // If the operation is an update, for validation ratcheting, skip re-validating if the old + // value is found and the equiv function confirms the values are equivalent. + if op.Type == operation.Update && old != nil && equiv != nil && equiv(&val, old) { + continue + } + errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), &val, old)...) + } + return errs +} + +// EachPtrMapVal validates each value in newMap (which is a map of pointers) +// using the specified validation function, passing the corresponding old value +// from oldMap if the key exists in oldMap. +// For update operations, it implements validation ratcheting by skipping validation +// when the old value exists and the equiv function confirms the values are equivalent. +// +// The equiv function will never be called with nil arguments. +// +// If equiv is nil, value-based ratcheting is disabled and all values will be validated. +func EachPtrMapVal[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]*V, + equiv MatchFunc[*V], validator ValidateFunc[*V]) field.ErrorList { + var errs field.ErrorList + for key, val := range newMap { + if val == nil { + // Ignore nil items; they are supposed to have been checked by PtrMapNoNils. + continue + } + + var old *V + if o, found := oldMap[key]; found { + old = o + } + // If the operation is an update, for validation ratcheting, skip re-validating if the old + // value is found and the equiv function confirms the values are equivalent. + if op.Type == operation.Update && old != nil && equiv != nil && equiv(val, old) { + continue + } + errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), val, old)...) + } + return errs +} + +// EachMapKey validates each element of newMap with the specified +// validation function. +func EachMapKey[K ~string, T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]T, + validator ValidateFunc[*K]) field.ErrorList { + var errs field.ErrorList + for key := range newMap { + var old *K + if _, found := oldMap[key]; found { + old = &key + } + // If the operation is an update, for validation ratcheting, skip re-validating if + // the key is found in oldMap. + if op.Type == operation.Update && old != nil { + continue + } + // Note: the field path is the field, not the key. + errs = append(errs, validator(ctx, op, fldPath, &key, nil)...) + } + return errs +} + +// ValSliceUnique verifies that each element of a slice of values is unique, +// according to the match function. It compares every element of the slice with +// every other element and returns errors for non-unique items. +// +// The match function will never be called with nil arguments. +func ValSliceUnique[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, newSlice, _ []T, match MatchFunc[*T]) field.ErrorList { + var dups []int + for i := range newSlice { + for j := i + 1; j < len(newSlice); j++ { + if match(&newSlice[i], &newSlice[j]) { + if dups == nil { + dups = make([]int, 0, len(newSlice)) + } + if !slices.Contains(dups, j) { + dups = append(dups, j) + } + } + } + } + + var errs field.ErrorList + sort.Ints(dups) + for _, i := range dups { + var val any = newSlice[i] + // TODO: we don't want the whole item to be logged in the error, just + // the key(s). Unfortunately, the way errors are rendered, it comes out + // as something like "map[string]any{...}" which is not very nice. Once + // that is fixed, we can consider adding a way for this function to + // specify that just the keys should be rendered in the error. + errs = append(errs, field.Duplicate(fldPath.Index(i), val)) + } + return errs +} + +// PtrSliceUnique verifies that each element of a slice of pointers is unique, +// according to the match function. It compares every element of the slice with +// every other element and returns errors for non-unique items. +// +// The match function will never be called with nil arguments. +func PtrSliceUnique[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, newSlice, _ []*T, match MatchFunc[*T]) field.ErrorList { + var errs field.ErrorList + var dups []int + for i := range newSlice { + if newSlice[i] == nil { + // Ignore nil items; they are supposed to have been checked by PtrSliceNoNils. + continue + } + for j := i + 1; j < len(newSlice); j++ { + if newSlice[j] == nil { + continue + } + if match(newSlice[i], newSlice[j]) { + if dups == nil { + dups = make([]int, 0, len(newSlice)) + } + if !slices.Contains(dups, j) { + dups = append(dups, j) + } + } + } + } + + sort.Ints(dups) + for _, i := range dups { + var val any = newSlice[i] + errs = append(errs, field.Duplicate(fldPath.Index(i), val)) + } + return errs +} + +// SemanticDeepEqual is a MatchFunc that uses equality.Semantic.DeepEqual to +// compare two values. +// This wrapper is needed because MatchFunc requires a function that takes two +// arguments of specific type T, while equality.Semantic.DeepEqual takes +// arguments of type interface{}/any. The wrapper satisfies the type +// constraints of MatchFunc while leveraging the underlying semantic equality +// logic. It can be used by any other function that needs to call DeepEqual. +// +// Deprecated: Callers should use equality.Semantic.DeepEqual directly. +func SemanticDeepEqual[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} + +// ReflectDeepEqual is a MatchFunc that uses reflect.DeepEqual to compare two +// values. +// This wrapper is needed because MatchFunc requires a function that takes two +// arguments of specific type T, while reflect.DeepEqual takes arguments of +// type interface{}/any. It can be used by any other function that needs to +// call DeepEqual. +func ReflectDeepEqual[T any](a, b T) bool { + return reflect.DeepEqual(a, b) +} + +// DirectEqual is a MatchFunc that dereferences two pointers and uses the == +// operator to compare the values. If both pointers are nil, it returns true. +// If one pointer is nil and the other is not, it returns false. +// It can be used by any other function that needs to compare two pointees +// directly. +func DirectEqual[T comparable](a, b *T) bool { + if a == b { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b +} + +// PtrSliceNoNils returns a Required error for each nil element in a slice of +// pointers. +func PtrSliceNoNils[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, newSlice, _ []*T) (errs field.ErrorList) { + for i := range newSlice { + if newSlice[i] == nil { + errs = append(errs, field.Required(fldPath.Index(i), "")) + } + } + return +} + +// PtrMapNoNils returns a Required error for each nil element in a map of +// pointers. +func PtrMapNoNils[K ~string, V any](_ context.Context, _ operation.Operation, fldPath *field.Path, newMap, _ map[K]*V) (errs field.ErrorList) { + for key, val := range newMap { + if val == nil { + errs = append(errs, field.Required(fldPath.Key(string(key)), "")) + } + } + return +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/each_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/each_test.go new file mode 100644 index 0000000000..a8b73ae6fc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/each_test.go @@ -0,0 +1,846 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "fmt" + "reflect" + "slices" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +type TestStruct struct { + I int + D string +} + +type TestStructWithKey struct { + Key string + I int + D string +} + +type NonComparableKey struct { + I *int +} + +type NonComparableStruct struct { + I int + S []string +} + +type NonComparableStructWithKey struct { + Key string + I int + S []string +} + +type NonComparableStructWithPtr struct { + I int + P *int +} + +func TestEachValSliceVal(t *testing.T) { + testEachValSliceVal(t, "valid", []int{11, 12, 13}) + testEachValSliceVal(t, "valid", []string{"a", "b", "c"}) + testEachValSliceVal(t, "valid", []TestStruct{{11, "a"}, {12, "b"}, {13, "c"}}) + + testEachValSliceVal(t, "empty", []int{}) + testEachValSliceVal(t, "empty", []string{}) + testEachValSliceVal(t, "empty", []TestStruct{}) + + testEachValSliceVal[int](t, "nil", nil) + testEachValSliceVal[string](t, "nil", nil) + testEachValSliceVal[TestStruct](t, "nil", nil) + + testEachValSliceValUpdate(t, "valid", []int{11, 12, 13}) + testEachValSliceValUpdate(t, "valid", []string{"a", "b", "c"}) + testEachValSliceValUpdate(t, "valid", []TestStruct{{11, "a"}, {12, "b"}, {13, "c"}}) + + testEachValSliceValUpdate(t, "empty", []int{}) + testEachValSliceValUpdate(t, "empty", []string{}) + testEachValSliceValUpdate(t, "empty", []TestStruct{}) + + testEachValSliceValUpdate[int](t, "nil", nil) + testEachValSliceValUpdate[string](t, "nil", nil) + testEachValSliceValUpdate[TestStruct](t, "nil", nil) +} + +func testEachValSliceVal[T any](t *testing.T, name string, input []T) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList { + if oldVal != nil { + t.Errorf("expected nil oldVal, got %v", *oldVal) + } + calls++ + return nil + } + _ = EachValSliceVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, nil, vfn) + if calls != len(input) { + t.Errorf("expected %d calls, got %d", len(input), calls) + } + }) +} + +func testEachValSliceValUpdate[T any](t *testing.T, name string, input []T) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList { + if oldVal == nil { + t.Fatalf("expected non-nil oldVal") + } + if !reflect.DeepEqual(*newVal, *oldVal) { + t.Errorf("expected oldVal == newVal, got %v, %v", *oldVal, *newVal) + } + calls++ + return nil + } + old := make([]T, len(input)) + copy(old, input) + slices.Reverse(old) + match := func(a, b *T) bool { return reflect.DeepEqual(*a, *b) } + _ = EachValSliceVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, old, match, match, vfn) + if calls != len(input) { + t.Errorf("expected %d calls, got %d", len(input), calls) + } + }) +} + +func TestEachValSliceValRatcheting(t *testing.T) { + testEachValSliceValRatcheting(t, "ComparableStruct same data different order", + []TestStruct{ + {11, "a"}, {12, "b"}, {13, "c"}, + }, + []TestStruct{ + {11, "a"}, {13, "c"}, {12, "b"}, + }, + ReflectDeepEqual, + nil, + ) + testEachValSliceValRatcheting(t, "ComparableStruct less data in new, exist in old", + []TestStruct{ + {11, "a"}, {12, "b"}, {13, "c"}, + }, + []TestStruct{ + {11, "a"}, {13, "c"}, + }, + DirectEqual, + nil, + ) + testEachValSliceValRatcheting(t, "Comparable struct with key same data different order", + []TestStructWithKey{ + {Key: "a", I: 11, D: "a"}, {Key: "b", I: 12, D: "b"}, {Key: "c", I: 13, D: "c"}, + }, + []TestStructWithKey{ + {Key: "a", I: 11, D: "a"}, {Key: "c", I: 13, D: "c"}, {Key: "b", I: 12, D: "b"}, + }, + MatchFunc[*TestStructWithKey](func(a, b *TestStructWithKey) bool { + return a.Key == b.Key + }), + DirectEqual, + ) + testEachValSliceValRatcheting(t, "Comparable struct with key less data in new, exist in old", + []TestStructWithKey{ + {Key: "a", I: 11, D: "a"}, {Key: "b", I: 12, D: "b"}, {Key: "c", I: 13, D: "c"}, + }, + []TestStructWithKey{ + {Key: "a", I: 11, D: "a"}, {Key: "c", I: 13, D: "c"}, + }, + MatchFunc[*TestStructWithKey](func(a, b *TestStructWithKey) bool { + return a.Key == b.Key + }), + DirectEqual, + ) + testEachValSliceValRatcheting(t, "NonComparableStruct same data different order", + []NonComparableStruct{ + {I: 11, S: []string{"a"}}, {I: 12, S: []string{"b"}}, {I: 13, S: []string{"c"}}, + }, + []NonComparableStruct{ + {I: 11, S: []string{"a"}}, {I: 13, S: []string{"c"}}, {I: 12, S: []string{"b"}}, + }, + ReflectDeepEqual, + nil, + ) + testEachValSliceValRatcheting(t, "NonComparableStructWithKey same data different order", + []NonComparableStructWithKey{ + {Key: "a", I: 11, S: []string{"a"}}, {Key: "b", I: 12, S: []string{"b"}}, {Key: "c", I: 13, S: []string{"c"}}, + }, + []NonComparableStructWithKey{ + {Key: "a", I: 11, S: []string{"a"}}, {Key: "b", I: 12, S: []string{"b"}}, {Key: "c", I: 13, S: []string{"c"}}, + }, + MatchFunc[*NonComparableStructWithKey](func(a, b *NonComparableStructWithKey) bool { + return a.Key == b.Key + }), + ReflectDeepEqual, + ) + +} + +func testEachValSliceValRatcheting[T any](t *testing.T, name string, old, new []T, match, equiv MatchFunc[*T]) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) { + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList { + return field.ErrorList{field.Invalid(fldPath, *newVal, "expected no calls")} + } + errs := EachValSliceVal(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, match, equiv, vfn) + if len(errs) > 0 { + t.Errorf("expected no errors, got %d: %s", len(errs), fmtErrs(errs)) + } + }) +} + +func TestEachMapVal(t *testing.T) { + testEachMapVal(t, "valid", map[string]int{"one": 11, "two": 12, "three": 13}) + testEachMapVal(t, "valid", map[string]string{"A": "a", "B": "b", "C": "c"}) + testEachMapVal(t, "valid", map[string]TestStruct{"one": {11, "a"}, "two": {12, "b"}, "three": {13, "c"}}) + + testEachMapVal(t, "empty", map[string]int{}) + testEachMapVal(t, "empty", map[string]string{}) + testEachMapVal(t, "empty", map[string]TestStruct{}) + + testEachMapVal[int](t, "nil", nil) + testEachMapVal[string](t, "nil", nil) + testEachMapVal[TestStruct](t, "nil", nil) +} + +func testEachMapVal[T any](t *testing.T, name string, input map[string]T) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList { + if oldVal != nil { + t.Errorf("expected nil oldVal, got %v", *oldVal) + } + calls++ + return nil + } + _ = EachMapVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, vfn) + if calls != len(input) { + t.Errorf("expected %d calls, got %d", len(input), calls) + } + }) +} + +func TestEachMapValRatcheting(t *testing.T) { + testEachMapValRatcheting(t, "primitive same data", + map[string]int{"one": 11, "two": 12, "three": 13}, + map[string]int{"one": 11, "three": 13, "two": 12}, + DirectEqual, + 0, + ) + testEachMapValRatcheting(t, "primitive less data in new, exist in old", + map[string]int{"one": 11, "two": 12, "three": 13}, + map[string]int{"one": 11, "three": 13}, + DirectEqual, + 0, + ) + testEachMapValRatcheting(t, "primitive new data, not exist in old", + map[string]int{"one": 11, "two": 12, "three": 13}, + map[string]int{"one": 11, "three": 13, "two": 12, "four": 14}, + DirectEqual, + 1, + ) + testEachMapValRatcheting(t, "non comparable value, same data", + map[string]NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "two": {I: 12, S: []string{"b"}}, + "three": {I: 13, S: []string{"c"}}, + }, + map[string]NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "three": {I: 13, S: []string{"c"}}, + "two": {I: 12, S: []string{"b"}}, + }, + ReflectDeepEqual, + 0, + ) + testEachMapValRatcheting(t, "non comparable value, less data in new, exist in old", + map[string]NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "two": {I: 12, S: []string{"b"}}, + "three": {I: 13, S: []string{"c"}}, + }, + map[string]NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "three": {I: 13, S: []string{"c"}}, + }, + ReflectDeepEqual, + 0, + ) + testEachMapValRatcheting(t, "non comparable value, new data, not exist in old", + map[string]NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "two": {I: 12, S: []string{"b"}}, + "three": {I: 13, S: []string{"c"}}, + }, + map[string]NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "three": {I: 13, S: []string{"c"}}, + "two": {I: 12, S: []string{"b"}}, + "four": {I: 14, S: []string{"d"}}, + }, + ReflectDeepEqual, + 1, + ) + testEachMapValRatcheting(t, "struct with pointer field, same value different pointer", + map[string]NonComparableStructWithPtr{ + "one": {I: 11, P: new(1)}, + "two": {I: 12, P: new(2)}, + }, + map[string]NonComparableStructWithPtr{ + "one": {I: 11, P: new(1)}, + "two": {I: 12, P: new(2)}, + }, + ReflectDeepEqual, + 0, + ) + testEachMapValRatcheting(t, "nil map to empty map", + nil, + map[string]int{}, + DirectEqual, + 0, + ) + + testEachMapValRatcheting(t, "nil map to non-empty map", + nil, + map[string]int{"one": 1}, + DirectEqual, + 1, // Expect validation for new entry + ) + + testEachMapValRatcheting(t, "empty map to nil map", + map[string]int{}, + nil, + DirectEqual, + 0, + ) + + testEachMapValRatcheting(t, "non-empty map to nil map", + map[string]int{"one": 1}, + nil, + DirectEqual, + 0, + ) +} + +func testEachMapValRatcheting[K ~string, V any](t *testing.T, name string, old, new map[K]V, equiv MatchFunc[*V], wantCalls int) { + t.Helper() + var zero V + t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *V) field.ErrorList { + calls++ + return nil + } + _ = EachMapVal(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, equiv, vfn) + if calls != wantCalls { + t.Errorf("expected %d calls, got %d", wantCalls, calls) + } + }) +} + +func TestEachPtrMapVal(t *testing.T) { + testEachPtrMapVal(t, "valid", map[string]*int{"one": new(11), "two": new(12), "three": new(13)}) + testEachPtrMapVal(t, "valid", map[string]*string{"A": new("a"), "B": new("b"), "C": new("c")}) + testEachPtrMapVal(t, "valid", map[string]*TestStruct{"one": {11, "a"}, "two": {12, "b"}, "three": {13, "c"}}) + + testEachPtrMapVal(t, "empty", map[string]*int{}) + testEachPtrMapVal(t, "empty", map[string]*string{}) + testEachPtrMapVal(t, "empty", map[string]*TestStruct{}) + + testEachPtrMapVal[int](t, "nil", nil) + testEachPtrMapVal[string](t, "nil", nil) + testEachPtrMapVal[TestStruct](t, "nil", nil) + + t.Run("nil element ignored", func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *int) field.ErrorList { + calls++ + return nil + } + input := map[string]*int{"a": new(1), "b": nil, "c": new(3)} + errs := EachPtrMapVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, vfn) + if len(errs) != 0 { + t.Errorf("expected 0 errors, got %d: %v", len(errs), errs) + } + if calls != 2 { + t.Errorf("expected 2 calls, got %d", calls) + } + }) +} + +func testEachPtrMapVal[T any](t *testing.T, name string, input map[string]*T) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(*%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList { + if oldVal != nil { + t.Errorf("expected nil oldVal, got %v", *oldVal) + } + calls++ + return nil + } + errs := EachPtrMapVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, vfn) + if len(errs) != 0 { + t.Errorf("expected 0 errors, got %d: %v", len(errs), errs) + } + if calls != len(input) { + t.Errorf("expected %d calls, got %d", len(input), calls) + } + }) +} + +func TestEachPtrMapValRatcheting(t *testing.T) { + testEachPtrMapValRatcheting(t, "primitive same data", + map[string]*int{"one": new(11), "two": new(12), "three": new(13)}, + map[string]*int{"one": new(11), "three": new(13), "two": new(12)}, + DirectEqual, + 0, + ) + testEachPtrMapValRatcheting(t, "primitive less data in new, exist in old", + map[string]*int{"one": new(11), "two": new(12), "three": new(13)}, + map[string]*int{"one": new(11), "three": new(13)}, + DirectEqual, + 0, + ) + testEachPtrMapValRatcheting(t, "primitive new data, not exist in old", + map[string]*int{"one": new(11), "two": new(12)}, + map[string]*int{"one": new(11), "two": new(12), "three": new(13)}, + DirectEqual, + 1, + ) + testEachPtrMapValRatcheting(t, "non comparable value, same data", + map[string]*NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "two": {I: 12, S: []string{"b"}}, + "three": {I: 13, S: []string{"c"}}, + }, + map[string]*NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "three": {I: 13, S: []string{"c"}}, + "two": {I: 12, S: []string{"b"}}, + }, + ReflectDeepEqual, + 0, + ) + testEachPtrMapValRatcheting(t, "non comparable value, less data in new, exist in old", + map[string]*NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "two": {I: 12, S: []string{"b"}}, + "three": {I: 13, S: []string{"c"}}, + }, + map[string]*NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "three": {I: 13, S: []string{"c"}}, + }, + ReflectDeepEqual, + 0, + ) + testEachPtrMapValRatcheting(t, "non comparable value, new data, not exist in old", + map[string]*NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "two": {I: 12, S: []string{"b"}}, + "three": {I: 13, S: []string{"c"}}, + }, + map[string]*NonComparableStruct{ + "one": {I: 11, S: []string{"a"}}, + "three": {I: 13, S: []string{"c"}}, + "two": {I: 12, S: []string{"b"}}, + "four": {I: 14, S: []string{"d"}}, + }, + ReflectDeepEqual, + 1, + ) + testEachPtrMapValRatcheting(t, "struct with pointer field, same value different pointer", + map[string]*NonComparableStructWithPtr{ + "one": {I: 11, P: new(1)}, + "two": {I: 12, P: new(2)}, + }, + map[string]*NonComparableStructWithPtr{ + "one": {I: 11, P: new(1)}, + "two": {I: 12, P: new(2)}, + }, + ReflectDeepEqual, + 0, + ) + testEachPtrMapValRatcheting(t, "nil map to empty map", + nil, + map[string]*int{}, + DirectEqual, + 0, + ) + testEachPtrMapValRatcheting(t, "nil map to non-empty map", + nil, + map[string]*int{"one": new(1)}, + DirectEqual, + 1, + ) + testEachPtrMapValRatcheting(t, "empty map to nil map", + map[string]*int{}, + nil, + DirectEqual, + 0, + ) + testEachPtrMapValRatcheting(t, "non-empty map to nil map", + map[string]*int{"one": new(1)}, + nil, + DirectEqual, + 0, + ) +} + +func testEachPtrMapValRatcheting[K ~string, V any](t *testing.T, name string, old, new map[K]*V, equiv MatchFunc[*V], wantCalls int) { + t.Helper() + var zero V + t.Run(fmt.Sprintf("%s(*%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *V) field.ErrorList { + calls++ + return nil + } + _ = EachPtrMapVal(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, equiv, vfn) + if calls != wantCalls { + t.Errorf("expected %d calls, got %d", wantCalls, calls) + } + }) +} + +type StringType string + +func TestEachMapKey(t *testing.T) { + testEachMapKey(t, "valid", map[string]int{"one": 11, "two": 12, "three": 13}) + testEachMapKey(t, "valid", map[StringType]string{"A": "a", "B": "b", "C": "c"}) +} + +func testEachMapKey[K ~string, V any](t *testing.T, name string, input map[K]V) { + t.Helper() + var zero K + t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *K) field.ErrorList { + if oldVal != nil { + t.Errorf("expected nil oldVal, got %v", *oldVal) + } + calls++ + return nil + } + _ = EachMapKey(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, vfn) + if calls != len(input) { + t.Errorf("expected %d calls, got %d", len(input), calls) + } + }) +} + +func TestEachMapKeyRatcheting(t *testing.T) { + testEachMapKeyRatcheting(t, "same data, 0 validation calls", + map[string]int{"one": 11, "two": 12, "three": 13}, + map[string]int{"one": 11, "three": 13, "two": 12}, + 0, + ) + testEachMapKeyRatcheting(t, "less data in new, exist in old, 0 validation calls", + map[string]int{"one": 11, "two": 12, "three": 13}, + map[string]int{"one": 11, "three": 13}, + 0, + ) + testEachMapKeyRatcheting(t, "new data, not exist in old, 1 validation call", + map[string]int{"one": 11, "two": 12, "three": 13}, + map[string]int{"one": 11, "three": 13, "two": 12, "four": 14}, + 1, + ) +} + +func testEachMapKeyRatcheting[K ~string, V any](t *testing.T, name string, old, new map[K]V, wantCalls int) { + t.Helper() + var zero V + t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *K) field.ErrorList { + calls++ + return nil + } + _ = EachMapKey(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, vfn) + if calls != wantCalls { + t.Errorf("expected %d calls, got %d", wantCalls, calls) + } + }) +} + +func TestValSliceUniqueComparableValues(t *testing.T) { + testValSliceUnique(t, "int_nil", []int(nil), 0) + testValSliceUnique(t, "int_empty", []int{}, 0) + testValSliceUnique(t, "int_uniq", []int{1, 2, 3}, 0) + testValSliceUnique(t, "int_dup", []int{1, 2, 3, 2, 1}, 2) + + testValSliceUnique(t, "string_nil", []string(nil), 0) + testValSliceUnique(t, "string_empty", []string{}, 0) + testValSliceUnique(t, "string_uniq", []string{"a", "b", "c"}, 0) + testValSliceUnique(t, "string_dup", []string{"a", "a", "c", "b", "a"}, 2) + + type isComparable struct { + I int + S string + } + + testValSliceUnique(t, "struct_nil", []isComparable(nil), 0) + testValSliceUnique(t, "struct_empty", []isComparable{}, 0) + testValSliceUnique(t, "struct_uniq", []isComparable{{1, "a"}, {2, "b"}, {3, "c"}}, 0) + testValSliceUnique(t, "struct_dup", []isComparable{{1, "a"}, {2, "b"}, {3, "c"}, {2, "b"}, {1, "a"}}, 2) +} + +func testValSliceUnique[T comparable](t *testing.T, name string, input []T, wantErrs int) { + t.Helper() + t.Run(fmt.Sprintf("%s(direct)", name), func(t *testing.T) { + errs := ValSliceUnique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, DirectEqual) + if len(errs) != wantErrs { + t.Errorf("expected %d errors, got %d: %s", wantErrs, len(errs), fmtErrs(errs)) + } + }) + t.Run(fmt.Sprintf("%s(reflect)", name), func(t *testing.T) { + errs := ValSliceUnique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, ReflectDeepEqual) + if len(errs) != wantErrs { + t.Errorf("expected %d errors, got %d: %s", wantErrs, len(errs), fmtErrs(errs)) + } + }) +} + +func TestValSliceUniqueNonComparableValues(t *testing.T) { + type nonComparable struct { + I int + S []string + } + + testValSliceUniqueByReflect(t, "noncomp_nil", []nonComparable(nil), 0) + testValSliceUniqueByReflect(t, "noncomp_empty", []nonComparable{}, 0) + testValSliceUniqueByReflect(t, "noncomp_uniq", []nonComparable{{1, []string{"a"}}, {2, []string{"b"}}, {3, []string{"c"}}}, 0) + testValSliceUniqueByReflect(t, "noncomp_dup", []nonComparable{ + {1, []string{"a"}}, + {2, []string{"b"}}, + {3, []string{"c"}}, + {2, []string{"b"}}, + {1, []string{"a"}}}, 2) +} + +func testValSliceUniqueByReflect[T any](t *testing.T, name string, input []T, wantErrs int) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(%T)", name, zero), func(t *testing.T) { + errs := ValSliceUnique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, ReflectDeepEqual) + if len(errs) != wantErrs { + t.Errorf("expected %d errors, got %d: %s", wantErrs, len(errs), fmtErrs(errs)) + } + }) +} + +func TestEachPtrSliceVal(t *testing.T) { + testEachPtrSliceVal(t, "valid", []*int{new(11), new(12), new(13)}) + testEachPtrSliceVal(t, "valid", []*string{new("a"), new("b"), new("c")}) + testEachPtrSliceVal(t, "valid", []*TestStruct{{11, "a"}, {12, "b"}, {13, "c"}}) + + testEachPtrSliceVal(t, "empty", []*int{}) + testEachPtrSliceVal[int](t, "nil", nil) + + // Test nil elements + t.Run("nil elements", func(t *testing.T) { + input := []*int{new(11), nil, new(13)} + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *int) field.ErrorList { + calls++ + return nil + } + errs := EachPtrSliceVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, nil, vfn) + if len(errs) != 0 { + t.Errorf("expected 0 errors, got %d", len(errs)) + } + if calls != 2 { + t.Errorf("expected 2 calls, got %d", calls) + } + }) + + testEachPtrSliceValUpdate(t, "valid", []*int{new(11), new(12), new(13)}) +} + +func testEachPtrSliceVal[T any](t *testing.T, name string, input []*T) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(*%T)", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList { + if oldVal != nil { + t.Errorf("expected nil oldVal, got %v", *oldVal) + } + calls++ + return nil + } + errs := EachPtrSliceVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, nil, nil, vfn) + if len(errs) != 0 { + t.Errorf("expected 0 errors, got %d: %v", len(errs), errs) + } + if calls != len(input) { + t.Errorf("expected %d calls, got %d", len(input), calls) + } + }) +} + +func testEachPtrSliceValUpdate[T any](t *testing.T, name string, input []*T) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(*%T) update", name, zero), func(t *testing.T) { + calls := 0 + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList { + if oldVal == nil { + t.Fatalf("expected non-nil oldVal") + } + if !reflect.DeepEqual(*newVal, *oldVal) { + t.Errorf("expected oldVal == newVal, got %v, %v", *oldVal, *newVal) + } + calls++ + return nil + } + old := make([]*T, len(input)) + copy(old, input) + slices.Reverse(old) + match := func(a, b *T) bool { return reflect.DeepEqual(*a, *b) } + errs := EachPtrSliceVal(context.Background(), operation.Operation{}, field.NewPath("test"), input, old, match, match, vfn) + if len(errs) != 0 { + t.Errorf("expected 0 errors, got %d: %v", len(errs), errs) + } + if calls != len(input) { + t.Errorf("expected %d calls, got %d", len(input), calls) + } + }) +} + +func TestEachPtrSliceValRatcheting(t *testing.T) { + testEachPtrSliceValRatcheting(t, "ComparableStruct same data different order", + []*TestStruct{{11, "a"}, {12, "b"}, {13, "c"}}, + []*TestStruct{{11, "a"}, {13, "c"}, {12, "b"}}, + ReflectDeepEqual, + nil, + ) +} + +func testEachPtrSliceValRatcheting[T any](t *testing.T, name string, old, new []*T, match, equiv MatchFunc[*T]) { + t.Helper() + var zero T + t.Run(fmt.Sprintf("%s(*%T)", name, zero), func(t *testing.T) { + vfn := func(ctx context.Context, op operation.Operation, fldPath *field.Path, newVal, oldVal *T) field.ErrorList { + return field.ErrorList{field.Invalid(fldPath, *newVal, "expected no calls")} + } + errs := EachPtrSliceVal(context.Background(), operation.Operation{Type: operation.Update}, field.NewPath("test"), new, old, match, equiv, vfn) + if len(errs) != 0 { + t.Errorf("expected 0 errors, got %d: %v", len(errs), errs) + } + }) +} + +func TestPtrSliceUnique(t *testing.T) { + testPtrSliceUnique(t, "int_nil", []*int(nil), 0, 0) + testPtrSliceUnique(t, "int_empty", []*int{}, 0, 0) + testPtrSliceUnique(t, "int_uniq", []*int{new(1), new(2), new(3)}, 0, 0) + testPtrSliceUnique(t, "int_dup", []*int{new(1), new(2), new(3), new(2), new(1)}, 2, 0) + testPtrSliceUnique(t, "int_nil_element", []*int{new(1), nil, new(3), nil}, 0, 0) + testPtrSliceUnique(t, "int_dup_and_nil", []*int{new(1), nil, new(1), nil}, 1, 0) +} + +func testPtrSliceUnique[T comparable](t *testing.T, name string, input []*T, wantDupErrs, wantReqErrs int) { + t.Helper() + t.Run(fmt.Sprintf("%s(direct)", name), func(t *testing.T) { + errs := PtrSliceUnique(context.Background(), operation.Operation{}, field.NewPath("test"), input, nil, DirectEqual) + gotDup, gotReq := countErrors(errs) + if gotDup != wantDupErrs || gotReq != wantReqErrs { + t.Errorf("expected %d dup, %d req errors; got %d dup, %d req: %s", wantDupErrs, wantReqErrs, gotDup, gotReq, fmtErrs(errs)) + } + }) +} + +func countErrors(errs field.ErrorList) (dup, req int) { + for _, err := range errs { + switch err.Type { + case field.ErrorTypeDuplicate: + dup++ + case field.ErrorTypeRequired: + req++ + } + } + return +} + +func TestPtrSliceNoNils(t *testing.T) { + tests := []struct { + name string + input []*int + wantErrs int + }{ + {"nil", nil, 0}, + {"empty", []*int{}, 0}, + {"no_nil", []*int{new(1), new(2)}, 0}, + {"has_nil", []*int{new(1), nil, new(3), nil}, 2}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := PtrSliceNoNils(context.Background(), operation.Operation{}, field.NewPath("test"), tt.input, nil) + if len(errs) != tt.wantErrs { + t.Errorf("expected %d errors, got %d: %v", tt.wantErrs, len(errs), errs) + } + for _, err := range errs { + if err.Type != field.ErrorTypeRequired { + t.Errorf("expected Required error, got %v", err.Type) + } + } + }) + } +} + +func TestPtrMapNoNils(t *testing.T) { + tests := []struct { + name string + input map[string]*int + wantErrs int + }{ + {"nil", nil, 0}, + {"empty", map[string]*int{}, 0}, + {"no_nil", map[string]*int{"a": new(1), "b": new(2)}, 0}, + {"has_nil", map[string]*int{"a": new(1), "b": nil, "c": new(3), "d": nil}, 2}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := PtrMapNoNils(context.Background(), operation.Operation{}, field.NewPath("test"), tt.input, nil) + if len(errs) != tt.wantErrs { + t.Errorf("expected %d errors, got %d: %v", tt.wantErrs, len(errs), errs) + } + for _, err := range errs { + if err.Type != field.ErrorTypeRequired { + t.Errorf("expected Required error, got %v", err.Type) + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/enum.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/enum.go new file mode 100644 index 0000000000..4d8e2c0aa8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/enum.go @@ -0,0 +1,91 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "fmt" + "slices" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// Enum verifies that a given value is a member of a set of enum values. +// Exclude Rules that apply when options are enabled or disabled are also considered. +// If ANY exclude rule matches for a value, that value is excluded from the enum when validating. +func Enum[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T, validValues sets.Set[T], exclusions []EnumExclusion[T]) field.ErrorList { + if value == nil { + return nil + } + excluded, err := isExcluded(op, exclusions, *value) + if err != nil { + return field.ErrorList{field.InternalError(fldPath, err)} + } + if !validValues.Has(*value) || excluded { + supported, err := supportedValues(op, validValues, exclusions) + if err != nil { + return field.ErrorList{field.InternalError(fldPath, err)} + } + return field.ErrorList{field.NotSupported[T](fldPath, *value, supported)} + } + return nil +} + +// supportedValues returns a sorted list of supported values. +// Excluded enum values are not included in the list. +func supportedValues[T ~string](op operation.Operation, values sets.Set[T], exclusions []EnumExclusion[T]) ([]T, error) { + res := make([]T, 0, len(values)) + for key := range values { + excluded, err := isExcluded(op, exclusions, key) + if err != nil { + return nil, err + } + if excluded { + continue + } + res = append(res, key) + } + slices.Sort(res) + return res, nil +} + +// EnumExclusion represents a single enum exclusion rule. +type EnumExclusion[T ~string] struct { + // Value specifies the enum value to be conditionally excluded. + Value T + // ExcludeWhen determines the condition for exclusion. + // If true, the value is excluded if the option is present. + // If false, the value is excluded if the option is NOT present. + ExcludeWhen bool + // Option is the name of the feature option that controls the exclusion. + Option string +} + +func isExcluded[T ~string](op operation.Operation, exclusions []EnumExclusion[T], value T) (bool, error) { + for _, rule := range exclusions { + on, defined := op.HasOption(rule.Option) + if !defined { + return false, fmt.Errorf("undefined validation option %q", rule.Option) + } + if rule.Value == value && rule.ExcludeWhen == on { + return true, nil + } + } + return false, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/enum_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/enum_test.go new file mode 100644 index 0000000000..523cbabba4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/enum_test.go @@ -0,0 +1,241 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestEnum(t *testing.T) { + cases := []struct { + name string + value string + valid sets.Set[string] + expectErr string + }{{ + name: "valid value", + value: "a", + valid: sets.New("a", "b", "c"), + expectErr: "", + }, { + name: "invalid value", + value: "x", + valid: sets.New("a", "b", "c"), + expectErr: `fldpath: Unsupported value: "x": supported values: "a", "b", "c"`, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + op := operation.Operation{Type: operation.Create} + errs := Enum(context.Background(), op, field.NewPath("fldpath"), &tc.value, nil, tc.valid, nil) + + if tc.expectErr == "" { + if len(errs) > 0 { + t.Fatalf("expected no error, but got: %v", errs) + } + } else { + if len(errs) == 0 { + t.Fatal("expected an error, but got none") + } + if len(errs) > 1 { + t.Fatalf("expected a single error, but got: %v", errs) + } + if errs[0].Error() != tc.expectErr { + t.Errorf("expected error %q, but got %q", tc.expectErr, errs[0].Error()) + } + } + }) + } +} + +func TestEnumTypedef(t *testing.T) { + type StringType string + const ( + NotStringFoo StringType = "foo" + NotStringBar StringType = "bar" + NotStringQux StringType = "qux" + ) + + cases := []struct { + name string + value StringType + valid sets.Set[StringType] + expectErr string + }{{ + name: "valid value", + value: "foo", + valid: sets.New(NotStringFoo, NotStringBar, NotStringQux), + expectErr: "", + }, { + name: "invalid value", + value: "x", + valid: sets.New(NotStringFoo, NotStringBar, NotStringQux), + expectErr: `fldpath: Unsupported value: "x": supported values: "bar", "foo", "qux"`, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + op := operation.Operation{Type: operation.Create} + errs := Enum(context.Background(), op, field.NewPath("fldpath"), &tc.value, nil, tc.valid, nil) + + if tc.expectErr == "" { + if len(errs) > 0 { + t.Fatalf("expected no error, but got: %v", errs) + } + } else { + if len(errs) == 0 { + t.Fatal("expected an error, but got none") + } + if len(errs) > 1 { + t.Fatalf("expected a single error, but got: %v", errs) + } + if errs[0].Error() != tc.expectErr { + t.Errorf("expected error %q, but got %q", tc.expectErr, errs[0].Error()) + } + } + }) + } +} + +func TestEnumExclude(t *testing.T) { + type TestEnum string + const ( + ValueA TestEnum = "A" + ValueB TestEnum = "B" + ValueC TestEnum = "C" + ValueD TestEnum = "D" + ) + + const ( + FeatureA = "FeatureA" + FeatureB = "FeatureB" + ) + + testEnumValues := sets.New(ValueA, ValueB, ValueC, ValueD) + testEnumExclusions := []EnumExclusion[TestEnum]{ + {Value: ValueA, Option: FeatureA, ExcludeWhen: true}, + {Value: ValueB, Option: FeatureB, ExcludeWhen: false}, + {Value: ValueD, Option: FeatureA, ExcludeWhen: true}, + {Value: ValueD, Option: FeatureB, ExcludeWhen: false}, + } + + testCases := []struct { + name string + value TestEnum + opts map[string]bool + expectInternal bool + expectErr string + }{ + { + name: "no options, A is valid", + value: ValueA, + opts: map[string]bool{FeatureA: false, FeatureB: false}, + }, + { + name: "no options, B is invalid", + value: ValueB, + opts: map[string]bool{FeatureA: false, FeatureB: false}, + expectErr: `fld: Unsupported value: "B": supported values: "A", "C"`, + }, + { + name: "no options, D is invalid", + value: ValueD, + opts: map[string]bool{FeatureA: false, FeatureB: false}, + expectErr: `fld: Unsupported value: "D": supported values: "A", "C"`, + }, + { + name: "FeatureA enabled, A is invalid", + value: ValueA, + opts: map[string]bool{FeatureA: true, FeatureB: false}, + expectErr: `fld: Unsupported value: "A": supported values: "C"`, + }, + { + name: "FeatureA enabled, B is invalid", + value: ValueB, + opts: map[string]bool{FeatureA: true, FeatureB: false}, + expectErr: `fld: Unsupported value: "B": supported values: "C"`, + }, + { + name: "FeatureB enabled, A is valid", + value: ValueA, + opts: map[string]bool{FeatureA: false, FeatureB: true}, + }, + { + name: "FeatureB enabled, B is valid", + value: ValueB, + opts: map[string]bool{FeatureA: false, FeatureB: true}, + }, + { + name: "FeatureA and FeatureB enabled, A is invalid", + value: ValueA, + opts: map[string]bool{FeatureA: true, FeatureB: true}, + expectErr: `fld: Unsupported value: "A": supported values: "B", "C"`, + }, + { + name: "FeatureA and FeatureB enabled, B is valid", + value: ValueB, + opts: map[string]bool{FeatureA: true, FeatureB: true}, + }, + { + name: "FeatureA and FeatureB enabled, D is invalid", + value: ValueD, + opts: map[string]bool{FeatureA: true, FeatureB: true}, + expectErr: `fld: Unsupported value: "D": supported values: "B", "C"`, + }, + { + name: "undeclared option is an internal error", + value: ValueB, + opts: map[string]bool{FeatureA: false}, + expectInternal: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + op := operation.Operation{Type: operation.Create, Options: tc.opts} + errs := Enum(context.Background(), op, field.NewPath("fld"), &tc.value, nil, testEnumValues, testEnumExclusions) + + if tc.expectInternal { + if len(errs) != 1 || errs[0].Type != field.ErrorTypeInternal { + t.Fatalf("expected a single internal error, but got: %v", errs) + } + return + } + + if tc.expectErr == "" { + if len(errs) > 0 { + t.Fatalf("expected no error, but got: %v", errs) + } + } else { + if len(errs) == 0 { + t.Fatal("expected an error, but got none") + } + if len(errs) > 1 { + t.Fatalf("expected a single error, but got: %v", errs) + } + if errs[0].Error() != tc.expectErr { + t.Errorf("expected error %q, but got %q", tc.expectErr, errs[0].Error()) + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/equality.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/equality.go new file mode 100644 index 0000000000..12e99d0ec4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/equality.go @@ -0,0 +1,38 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/api/validate/content" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// NEQ validates that the specified comparable value is not equal to the disallowed value. +func NEQ[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, disallowed T) field.ErrorList { + if value == nil { + return nil + } + if *value == disallowed { + return field.ErrorList{ + field.Invalid(fldPath, *value, content.NEQError(disallowed)).WithOrigin("neq"), + } + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/errors.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/errors.go new file mode 100644 index 0000000000..a9dc4b01c0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/errors.go @@ -0,0 +1,96 @@ +/* +Copyright 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 validate + +import ( + "context" + "errors" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +type allDeclarativeEnforcedKeyType struct{} + +var allDeclarativeEnforcedKey = allDeclarativeEnforcedKeyType{} + +// WithAllDeclarativeEnforcedForTest returns a copy of parent context with allDeclarativeEnforcedKey set to true. +// This is used for testing to expose all declarative validation errors and filter all handwritten validation errors +// that are covered by declarative validation, regardless of the feature gate or maturity level. +// +// NOTE: This function is intended for testing purposes only and should not be used in production code. +func WithAllDeclarativeEnforcedForTest(ctx context.Context) context.Context { + return context.WithValue(ctx, allDeclarativeEnforcedKey, true) +} + +// AllDeclarativeEnforced returns true if the context contains allDeclarativeEnforcedKey set to true. +func AllDeclarativeEnforced(ctx context.Context) bool { + if ctx == nil { + return false + } + return ctx.Value(allDeclarativeEnforcedKey) == true +} + +// FilterCoveredHandwrittenErrors removes a CoveredByDeclarative handwritten error when a matching enforced +// beta declarative error exists (matched by type, field, and origin). In AllDeclarativeEnforced +// (testing-only) mode every covered handwritten error is removed. +func FilterCoveredHandwrittenErrors(ctx context.Context, imperativeErrs, enforcedDeclarativeErrs field.ErrorList, betaEnabled bool, rules ...field.NormalizationRule) field.ErrorList { + matcher := field.ErrorMatcher{}.ByType().ByOrigin().RequireOriginWhenInvalid().ByFieldNormalized(rules) + allDeclarativeEnforced := AllDeclarativeEnforced(ctx) + return imperativeErrs.Filter(func(e error) bool { + var fe *field.Error + if !errors.As(e, &fe) || !fe.CoveredByDeclarative { + return false + } + if allDeclarativeEnforced { + return true + } + for _, dErr := range enforcedDeclarativeErrs { + if dErr.IsBeta() && matcher.Matches(fe, dErr) { + return true + } + } + return false + }) +} + +// FilterEnforcedDeclarativeErrors collects the declarative errors that are enforced (i.e. surfaced to the user) in the +// current mode. A declarative error is enforced when any of the following holds: +// - AllDeclarativeEnforced is set (testing): every declarative error is enforced. +// - It is an internal error: always enforced, regardless of lifecycle. +// - It is a beta error and BetaEnabled is true. +// - It is a standard (unprefixed) error: always enforced. +// +// Alpha errors are never enforced; they remain shadowed by handwritten validation. +func FilterEnforcedDeclarativeErrors(ctx context.Context, declarativeErrs field.ErrorList, betaEnabled bool) field.ErrorList { + enforcedDeclarativeErrs := make(field.ErrorList, 0, len(declarativeErrs)) + allDeclarativeEnforced := AllDeclarativeEnforced(ctx) + for _, dvErr := range declarativeErrs { + switch { + case allDeclarativeEnforced: + enforcedDeclarativeErrs = append(enforcedDeclarativeErrs, dvErr) + case dvErr.Type == field.ErrorTypeInternal: + enforcedDeclarativeErrs = append(enforcedDeclarativeErrs, dvErr) + case dvErr.IsBeta(): + if betaEnabled { + enforcedDeclarativeErrs = append(enforcedDeclarativeErrs, dvErr) + } + case !dvErr.IsAlpha(): + enforcedDeclarativeErrs = append(enforcedDeclarativeErrs, dvErr) // Standard + } + } + return enforcedDeclarativeErrs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/errors_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/errors_test.go new file mode 100644 index 0000000000..a90f21898b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/errors_test.go @@ -0,0 +1,139 @@ +/* +Copyright 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 validate + +import ( + "context" + "errors" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestFilterCoveredHandwrittenErrors(t *testing.T) { + originRule := "test-origin" + + handwrittenCovered := field.Invalid(field.NewPath("spec", "name"), "invalid-value", "must be valid").WithOrigin(originRule).MarkCoveredByDeclarative() + + handwrittenNotCovered := field.Invalid(field.NewPath("spec", "name"), "invalid-value", "must be valid").WithOrigin(originRule) + + matchingBetaDeclarative := field.Invalid(field.NewPath("spec", "name"), "invalid-value", "must be valid").WithOrigin(originRule).MarkBeta() + + matchingAlphaDeclarative := field.Invalid(field.NewPath("spec", "name"), "invalid-value", "must be valid").WithOrigin(originRule).MarkAlpha() + + tests := []struct { + name string + errs field.ErrorList + enforcedDeclarative field.ErrorList + allDeclarativeEnforced bool + betaEnabled bool + rules []field.NormalizationRule + expectedLen int + }{ + { + name: "uncovered handwritten error is preserved", + errs: field.ErrorList{handwrittenNotCovered}, + enforcedDeclarative: field.ErrorList{matchingBetaDeclarative}, + allDeclarativeEnforced: false, + expectedLen: 1, + }, + { + name: "covered handwritten error filtered when matching beta declarative error exists", + errs: field.ErrorList{handwrittenCovered}, + enforcedDeclarative: field.ErrorList{matchingBetaDeclarative}, + allDeclarativeEnforced: false, + expectedLen: 0, + }, + { + name: "covered handwritten error not filtered when matching declarative is alpha", + errs: field.ErrorList{handwrittenCovered}, + enforcedDeclarative: field.ErrorList{matchingAlphaDeclarative}, + allDeclarativeEnforced: false, + expectedLen: 1, + }, + { + name: "covered handwritten error filtered in allDeclarativeEnforced mode regardless of matching declarative error", + errs: field.ErrorList{handwrittenCovered}, + enforcedDeclarative: nil, + allDeclarativeEnforced: true, + expectedLen: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + if tt.allDeclarativeEnforced { + ctx = WithAllDeclarativeEnforcedForTest(ctx) + } + res := FilterCoveredHandwrittenErrors(ctx, tt.errs, tt.enforcedDeclarative, tt.betaEnabled, tt.rules...) + if len(res) != tt.expectedLen { + t.Errorf("FilterCoveredHandwrittenErrors() returned %d errors, expected %d", len(res), tt.expectedLen) + } + }) + } +} + +func TestFilterEnforcedDeclarativeErrors(t *testing.T) { + internalErr := field.InternalError(field.NewPath("spec"), errors.New("internal error")) + alphaErr := field.Invalid(field.NewPath("spec"), "val", "alpha").MarkAlpha() + betaErr := field.Invalid(field.NewPath("spec"), "val", "beta").MarkBeta() + stdErr := field.Invalid(field.NewPath("spec"), "val", "standard") + + tests := []struct { + name string + declarativeErrs field.ErrorList + betaEnabled bool + allDeclarativeEnforced bool + expectedLen int + }{ + { + name: "all errors enforced in allDeclarativeEnforced mode", + declarativeErrs: field.ErrorList{internalErr, alphaErr, betaErr, stdErr}, + betaEnabled: false, + allDeclarativeEnforced: true, + expectedLen: 4, + }, + { + name: "beta disabled ignores alpha and beta", + declarativeErrs: field.ErrorList{internalErr, alphaErr, betaErr, stdErr}, + betaEnabled: false, + allDeclarativeEnforced: false, + expectedLen: 2, // internal + std + }, + { + name: "beta enabled includes beta but excludes alpha", + declarativeErrs: field.ErrorList{internalErr, alphaErr, betaErr, stdErr}, + betaEnabled: true, + allDeclarativeEnforced: false, + expectedLen: 3, // internal + beta + std + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + if tt.allDeclarativeEnforced { + ctx = WithAllDeclarativeEnforcedForTest(ctx) + } + res := FilterEnforcedDeclarativeErrors(ctx, tt.declarativeErrs, tt.betaEnabled) + if len(res) != tt.expectedLen { + t.Errorf("FilterEnforcedDeclarativeErrors() returned %d errors, expected %d", len(res), tt.expectedLen) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/immutable.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/immutable.go new file mode 100644 index 0000000000..01a879c98f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/immutable.go @@ -0,0 +1,40 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// Immutable verifies that the specified value has not changed in the course of +// an update operation. It does nothing if the old value is not provided. +// +// This function unconditionally returns a validation error as it +// relies on the default ratcheting mechanism to only be called when a +// change to the field has already been detected. This avoids a redundant +// equivalence check across ratcheting and this function. +func Immutable[T any](_ context.Context, op operation.Operation, fldPath *field.Path, _, _ T) field.ErrorList { + if op.Type != operation.Update { + return nil + } + return field.ErrorList{ + field.Invalid(fldPath, nil, "field is immutable").WithOrigin("immutable"), + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/immutable_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/immutable_test.go new file mode 100644 index 0000000000..60396b0cc6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/immutable_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestImmutable(t *testing.T) { + // The Immutable function relies on validation ratcheting to avoid being + // called when old and new values are equivalent. This unit test only needs + // to confirm two behaviors: + // 1. The function does nothing for non-update operations (e.g., create). + // 2. The function *always* returns an error for update operations, since + // ratcheting should have prevented the call if the values were unchanged. + + type simpleStruct struct { + S string + } + + for _, tc := range []struct { + name string + fn func(op operation.Operation, fldPath *field.Path) field.ErrorList + }{{ + name: "with primitive type", + fn: func(op operation.Operation, fld *field.Path) field.ErrorList { + return Immutable(context.Background(), op, fld, ptr.To(123), ptr.To(456)) + }, + }, { + name: "with struct type", + fn: func(op operation.Operation, fld *field.Path) field.ErrorList { + return Immutable(context.Background(), op, fld, &simpleStruct{S: "a"}, &simpleStruct{S: "b"}) + }, + }, { + name: "with nil values", + fn: func(op operation.Operation, fld *field.Path) field.ErrorList { + // Explicitly type the nil to satisfy the generic function signature. + return Immutable[*int](context.Background(), op, fld, nil, nil) + }, + }} { + t.Run(tc.name, func(t *testing.T) { + // Create operations should never return an error. + errs := tc.fn(operation.Operation{Type: operation.Create}, field.NewPath("field")) + if len(errs) != 0 { + t.Errorf("expected success for create operation, but got errors: %v", errs) + } + + // Update operations should always return exactly one error. + errs = tc.fn(operation.Operation{Type: operation.Update}, field.NewPath("field")) + if len(errs) == 0 { + t.Errorf("expected a failure for update operation, but got success") + } else if len(errs) > 1 { + t.Errorf("expected exactly one error for update operation, but got %d: %v", len(errs), errs) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/item.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/item.go new file mode 100644 index 0000000000..4ddb2860c5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/item.go @@ -0,0 +1,140 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// MatchItemFunc takes an item and returns true if it matches the criteria. +type MatchItemFunc[T any] func(T) bool + +// ValSliceItem finds the first item in a list of values which satisfies the +// match function, and if found, also looks for a matching item in oldList. If +// the value of the item is the same as the previous value, as per the equiv +// function, then no validation is performed. Otherwise, it invokes +// 'itemValidator' on these items. +// +// This function processes only the *first* matching item found in newList. It +// assumes that the match functions targets a unique identifier (primary key) +// and will match at most one element per list. If this assumption is violated, +// changes in list order can lead this function to have inconsistent behavior. +// +// The match and equiv functions will never be called with nil arguments. +// +// The fldPath passed to itemValidator is indexed to the matched item's +// position in newList. +// +// This function does not validate items that were removed (present in oldList +// but not in newList). +func ValSliceItem[TList ~[]TItem, TItem any]( + ctx context.Context, op operation.Operation, fldPath *field.Path, + newList, oldList TList, + match MatchItemFunc[*TItem], + equiv MatchFunc[*TItem], + itemValidator func(ctx context.Context, op operation.Operation, fldPath *field.Path, newObj, oldObj *TItem) field.ErrorList, +) field.ErrorList { + var matchedNew, matchedOld *TItem + var newIndex int + + for i := range newList { + if match(&newList[i]) { + matchedNew = &newList[i] + newIndex = i + break + } + } + if matchedNew == nil { + return nil + } + + for i := range oldList { + if match(&oldList[i]) { + matchedOld = &oldList[i] + break + } + } + + if op.Type == operation.Update && matchedOld != nil && equiv(matchedNew, matchedOld) { + return nil + } + + return itemValidator(ctx, op, fldPath.Index(newIndex), matchedNew, matchedOld) +} + +// PtrSliceItem finds the first item in a list of pointers which satisfies the +// match function, and if found, also looks for a matching item in oldList. If +// the value of the item is the same as the previous value, as per the equiv +// function, then no validation is performed. Otherwise, it invokes +// 'itemValidator' on these items. +// +// This function processes only the *first* matching item found in newList. It +// assumes that the match functions targets a unique identifier (primary key) +// and will match at most one element per list. If this assumption is violated, +// changes in list order can lead this function to have inconsistent behavior. +// +// The match and equiv functions will never be called with nil arguments. +// +// The fldPath passed to itemValidator is indexed to the matched item's +// position in newList. +// +// This function does not validate items that were removed (present in oldList +// but not in newList). +func PtrSliceItem[TList ~[]*TItem, TItem any]( + ctx context.Context, op operation.Operation, fldPath *field.Path, + newList, oldList TList, + match MatchItemFunc[*TItem], + equiv MatchFunc[*TItem], + itemValidator func(ctx context.Context, op operation.Operation, fldPath *field.Path, newObj, oldObj *TItem) field.ErrorList, +) field.ErrorList { + var matchedNew, matchedOld *TItem + var newIndex int + + for i := range newList { + if newList[i] == nil { + // Ignore nil items; they are supposed to have been checked by PtrSliceNoNils. + continue + } + if match(newList[i]) { + matchedNew = newList[i] + newIndex = i + break + } + } + if matchedNew == nil { + return nil + } + + for i := range oldList { + if oldList[i] == nil { + continue + } + if match(oldList[i]) { + matchedOld = oldList[i] + break + } + } + + if op.Type == operation.Update && matchedOld != nil && equiv(matchedNew, matchedOld) { + return nil + } + + return itemValidator(ctx, op, fldPath.Index(newIndex), matchedNew, matchedOld) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/item_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/item_test.go new file mode 100644 index 0000000000..180b8da62c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/item_test.go @@ -0,0 +1,237 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +type multiKeyItem struct { + K1 string `json:"k1"` + K2 string `json:"k2"` + V int `json:"v"` +} + +func TestValSliceItem(t *testing.T) { + testCases := []struct { + name string + new []multiKeyItem + old []multiKeyItem + match MatchItemFunc[*multiKeyItem] + validator func(context.Context, operation.Operation, *field.Path, *multiKeyItem, *multiKeyItem) field.ErrorList + expected field.ErrorList + }{ + { + name: "no match", + new: []multiKeyItem{ + {K1: "a", K2: "1", V: 1}, + }, + match: func(i *multiKeyItem) bool { + return i.K1 == "target" + }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, _, _ *multiKeyItem) field.ErrorList { + return field.ErrorList{field.Invalid(fp, nil, "err")} + }, + expected: nil, + }, + { + name: "new item with matching keys", + new: []multiKeyItem{ + {K1: "a", K2: "1", V: 1}, + {K1: "target", K2: "target2", V: 2}, + }, + match: func(i *multiKeyItem) bool { + return i.K1 == "target" && i.K2 == "target2" + }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList { + if n != nil && o == nil { + return field.ErrorList{field.Invalid(fp, n.K1, "added")} + } + return nil + }, + expected: field.ErrorList{field.Invalid(field.NewPath("").Index(1), "target", "added")}, + }, + { + name: "updated item - same keys different values", + new: []multiKeyItem{ + {K1: "a", K2: "1", V: 1}, + {K1: "update", K2: "target2", V: 20}, + }, + old: []multiKeyItem{ + {K1: "a", K2: "1", V: 1}, + {K1: "update", K2: "target2", V: 2}, + }, + match: func(i *multiKeyItem) bool { + return i.K1 == "update" && i.K2 == "target2" + }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList { + if n != nil && o != nil && n.V != o.V { + return field.ErrorList{field.Invalid(fp.Child("v"), n.V, "changed")} + } + return nil + }, + expected: field.ErrorList{field.Invalid(field.NewPath("").Index(1).Child("v"), 20, "changed")}, + }, + { + // For completeness as listType=map && listKey=... required tags prevents dupes. + name: "first match only - multiple items with same keys", + new: []multiKeyItem{ + {K1: "dup", K2: "target2", V: 1}, + {K1: "dup", K2: "target2", V: 2}, + }, + old: []multiKeyItem{ + {K1: "dup", K2: "target2", V: 10}, + }, + match: func(i *multiKeyItem) bool { + return i.K1 == "dup" && i.K2 == "target2" + }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList { + if n != nil && o != nil { + return field.ErrorList{field.Invalid(fp, n.V, "value")} + } + return nil + }, + expected: field.ErrorList{field.Invalid(field.NewPath("").Index(0), 1, "value")}, + }, + { + name: "nil new list", + new: nil, + old: []multiKeyItem{ + {K1: "exists", K2: "target2", V: 1}, + }, + match: func(i *multiKeyItem) bool { + return i.K1 == "exists" && i.K2 == "target2" + }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList { + if n == nil && o != nil { + return field.ErrorList{field.Invalid(fp, nil, "deleted")} + } + return nil + }, + expected: nil, + }, + { + name: "empty lists", + new: []multiKeyItem{}, + old: []multiKeyItem{}, + match: func(i *multiKeyItem) bool { return true }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, _, _ *multiKeyItem) field.ErrorList { + return field.ErrorList{field.Invalid(fp, nil, "err")} + }, + expected: nil, + }, + { + name: "nil lists", + new: nil, + old: nil, + match: func(i *multiKeyItem) bool { return true }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, _, _ *multiKeyItem) field.ErrorList { + return field.ErrorList{field.Invalid(fp, nil, "err")} + }, + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + op := operation.Operation{Type: operation.Update} + fp := field.NewPath("") + + got := ValSliceItem(ctx, op, fp, tc.new, tc.old, tc.match, ReflectDeepEqual, tc.validator) + + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + } +} + +func TestPtrSliceItem(t *testing.T) { + testCases := []struct { + name string + new []*multiKeyItem + old []*multiKeyItem + match MatchItemFunc[*multiKeyItem] + validator func(context.Context, operation.Operation, *field.Path, *multiKeyItem, *multiKeyItem) field.ErrorList + expected field.ErrorList + }{ + { + name: "no match", + new: []*multiKeyItem{ + {K1: "a", K2: "1", V: 1}, + }, + match: func(i *multiKeyItem) bool { + return i.K1 == "target" + }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, _, _ *multiKeyItem) field.ErrorList { + return field.ErrorList{field.Invalid(fp, nil, "err")} + }, + expected: nil, + }, + { + name: "new item with matching keys", + new: []*multiKeyItem{ + {K1: "a", K2: "1", V: 1}, + {K1: "target", K2: "target2", V: 2}, + }, + match: func(i *multiKeyItem) bool { + return i.K1 == "target" && i.K2 == "target2" + }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, o *multiKeyItem) field.ErrorList { + if n != nil && o == nil { + return field.ErrorList{field.Invalid(fp, n.K1, "added")} + } + return nil + }, + expected: field.ErrorList{field.Invalid(field.NewPath("").Index(1), "target", "added")}, + }, + { + name: "nil element in new list (ignored)", + new: []*multiKeyItem{ + nil, + {K1: "target", V: 1}, + }, + match: func(i *multiKeyItem) bool { + return i.K1 == "target" + }, + validator: func(_ context.Context, _ operation.Operation, fp *field.Path, n, _ *multiKeyItem) field.ErrorList { + return field.ErrorList{field.Invalid(fp, n.K1, "matched")} + }, + expected: field.ErrorList{field.Invalid(field.NewPath("").Index(1), "target", "matched")}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + op := operation.Operation{Type: operation.Update} + fp := field.NewPath("") + + got := PtrSliceItem(ctx, op, fp, tc.new, tc.old, tc.match, ReflectDeepEqual, tc.validator) + + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/limits.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/limits.go new file mode 100644 index 0000000000..768546f6bc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/limits.go @@ -0,0 +1,160 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "math" + "unicode/utf8" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/api/validate/constraints" + "k8s.io/apimachinery/pkg/api/validate/content" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// MaxLength verifies that the specified value is not longer than max +// characters. +func MaxLength[T ~string](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, max int) field.ErrorList { + if value == nil { + return nil + } + + // if the length of the value in bytes is less + // than the maximum size then we can confidently + // say that this value is within the bounds + // enforced by the maximum value regardless + // of the actual makeup of characters in the value + byteLength := len(*value) + if byteLength <= max { + return nil + } + + // because runes are up to 4 byte characters, if we assume all characters + // in the input are runes, the minimum number of characters that + // are specified is len(value)/4. If the minimum multi-byte + // character count is greater than our enforced maximum, we + // can confidently say that the value is invalid without having + // to actually perform the more expensive rune counting step + minimum := int(math.Ceil(float64(byteLength) / 4.0)) + if minimum > max || utf8.RuneCountInString(string(*value)) > max { + return field.ErrorList{field.TooLongCharacters(fldPath, *value, max).WithOrigin("maxLength")} + } + return nil +} + +// MaxBytes verifies that the specified value is not longer than max bytes. +func MaxBytes[T ~string](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, max int) field.ErrorList { + if value == nil { + return nil + } + + if len(*value) > max { + return field.ErrorList{field.TooLong(fldPath, *value, max).WithOrigin("maxBytes")} + } + + return nil +} + +// MaxItems verifies that the specified slice is not longer than max items. +func MaxItems[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T, max int) field.ErrorList { + if len(value) > max { + return field.ErrorList{field.TooMany(fldPath, len(value), max).WithOrigin("maxItems")} + } + return nil +} + +// MaxProperties verifies that the specified map has no more than max keys. +func MaxProperties[K comparable, V any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]V, max int) field.ErrorList { + if value == nil { + return nil + } + + if len(value) > max { + return field.ErrorList{field.TooMany(fldPath, len(value), max).WithOrigin("maxProperties")} + } + return nil +} + +// MinItems verifies that the specified slice is not shorter than min items. +func MinItems[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T, min int) field.ErrorList { + if len(value) < min { + return field.ErrorList{field.TooFew(fldPath, len(value), min).WithOrigin("minItems")} + } + return nil +} + +// MinProperties verifies that the specified map is not shorter than min properties. +func MinProperties[K comparable, V any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]V, min int) field.ErrorList { + if len(value) < min { + return field.ErrorList{field.TooFew(fldPath, len(value), min).WithOrigin("minProperties")} + } + return nil +} + +// Minimum verifies that the specified value is greater than or equal to min. +func Minimum[T constraints.Integer](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, min T) field.ErrorList { + if value == nil { + return nil + } + if *value < min { + return field.ErrorList{field.Invalid(fldPath, *value, content.MinError(min)).WithOrigin("minimum")} + } + return nil +} + +// Maximum verifies that the specified value is less than or equal to max. +func Maximum[T constraints.Integer](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, max T) field.ErrorList { + if value == nil { + return nil + } + if *value > max { + return field.ErrorList{field.Invalid(fldPath, *value, content.MaxError(max)).WithOrigin("maximum")} + } + return nil +} + +// MinLength verifies that the specified value is at least min characters, if non-nil. +func MinLength[T ~string](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, min int) field.ErrorList { + if value == nil { + return nil + } + + byteLength := len(*value) + + // because runes are up to 4 byte characters, if we assume all characters + // in the input are 4 byte runes, the minimum number of characters that + // are specified is len(value)/4. If the minimum multi-byte + // character count is greater than or equal to our enforced minimum, we + // can confidently say that the value is valid without having + // to actually perform the more expensive rune counting step + if int(math.Ceil(float64(byteLength)/4.0)) >= min { + return nil + } + + // if the length of the value in bytes is less + // than the minimum size then we can confidently + // say that this value is not within the bounds + // enforced by the maximum value regardless + // of the actual makeup of characters in the value. + // Otherwise, perform a rune count to determine if the + // number of characters is less than the minimum. + if byteLength < min || utf8.RuneCountInString(string(*value)) < min { + return field.ErrorList{field.TooShort(fldPath, *value, min).WithOrigin("minLength")} + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/limits_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/limits_test.go new file mode 100644 index 0000000000..934b876f8f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/limits_test.go @@ -0,0 +1,679 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/api/validate/constraints" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestMaxLength(t *testing.T) { + cases := []struct { + name string + value string + max int + wantErrs field.ErrorList + }{{ + name: "empty string", + value: "", + max: 0, + wantErrs: nil, + }, { + name: "zero length", + value: "0", + max: 0, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", 0).WithOrigin("maxLength"), + }, + }, { + name: "one character", + value: "0", + max: 1, + wantErrs: nil, + }, { + name: "two characters", + value: "01", + max: 1, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", 1).WithOrigin("maxLength"), + }, + }, { + value: "", + max: -1, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", -1).WithOrigin("maxLength"), + }, + }, { + name: "ascii-only characters, less characters than max (n-1)", + value: "abcdefghi", + max: 10, + wantErrs: nil, + }, { + name: "multi-byte characters, less characters than max (n-1)", + value: "©®©®©®©®©", + max: 10, + wantErrs: nil, + }, { + name: "ascii-only characters, more characters than max (n+1)", + value: "abcdefghijkl", + max: 10, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", 10).WithOrigin("maxLength"), + }, + }, { + name: "multi-byte characters, more characters than max (n+1)", + value: "©®©®©®©®©®©", + max: 10, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", 10).WithOrigin("maxLength"), + }, + }, { + name: "mixture of characters, minimum possible size of input is less than max, rune count exceed maximum", + value: "©abc®defghi", + max: 10, + wantErrs: field.ErrorList{ + field.TooLongCharacters(field.NewPath("fldpath"), "", 10).WithOrigin("maxLength"), + }, + }, { + name: "multi-byte characters, exact characters as max (n)", + value: "©®©®©®©®©®", + max: 10, + wantErrs: nil, + }, { + name: "ascii-only characters, exact characters as max (n)", + value: "abcdefghij", + max: 10, + wantErrs: nil, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + v := tc.value + gotErrs := MaxLength(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.max) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMinItems(t *testing.T) { + cases := []struct { + name string + items int + min int + wantErrs field.ErrorList + }{{ + name: "0 items, min 0", + items: 0, + min: 0, + }, { + name: "1 item, min 0", + items: 1, + min: 0, + }, { + name: "1 item, min 1", + items: 1, + min: 1, + }, { + name: "0 items, min 1", + items: 0, + min: 1, + wantErrs: field.ErrorList{ + field.TooFew(field.NewPath("fldpath"), 0, 1).WithOrigin("minItems"), + }, + }, { + name: "1 item, min 2", + items: 1, + min: 2, + wantErrs: field.ErrorList{ + field.TooFew(field.NewPath("fldpath"), 1, 2).WithOrigin("minItems"), + }, + }, { + name: "0 items, min -1", + items: 0, + min: -1, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + value := make([]bool, tc.items) + gotErrs := MinItems(context.Background(), operation.Operation{}, field.NewPath("fldpath"), value, nil, tc.min) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMinProperties(t *testing.T) { + cases := []struct { + name string + properties int + min int + wantErrs field.ErrorList + }{{ + name: "0 properties, min 0", + properties: 0, + min: 0, + }, { + name: "1 property, min 0", + properties: 1, + min: 0, + }, { + name: "1 property, min 1", + properties: 1, + min: 1, + }, { + name: "0 properties, min 1", + properties: 0, + min: 1, + wantErrs: field.ErrorList{ + field.TooFew(field.NewPath("fldpath"), 0, 1).WithOrigin("minProperties"), + }, + }, { + name: "1 property, min 2", + properties: 1, + min: 2, + wantErrs: field.ErrorList{ + field.TooFew(field.NewPath("fldpath"), 1, 2).WithOrigin("minProperties"), + }, + }, { + name: "0 properties, min 100000", + properties: 0, + min: 100000, + wantErrs: field.ErrorList{ + field.TooFew(field.NewPath("fldpath"), 0, 100000).WithOrigin("minProperties"), + }, + }, { + name: "99999 properties, min 100000", + properties: 99999, + min: 100000, + wantErrs: field.ErrorList{ + field.TooFew(field.NewPath("fldpath"), 99999, 100000).WithOrigin("minProperties"), + }, + }, { + name: "100000 properties, min 100000", + properties: 100000, + min: 100000, + }, { + // Note: While JSON Schema does not allow negative values for minProperties, + // we test that the validator handles it safely if it ever occurs at runtime. + name: "0 properties, min -1", + properties: 0, + min: -1, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + value := make(map[string]bool, tc.properties) + for i := 0; i < tc.properties; i++ { + value[fmt.Sprintf("k%d", i)] = true + } + gotErrs := MinProperties(context.Background(), operation.Operation{}, field.NewPath("fldpath"), value, nil, tc.min) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMaxItems(t *testing.T) { + cases := []struct { + name string + items int + max int + wantErrs field.ErrorList + }{{ + name: "0 items, max 0", + items: 0, + max: 0, + }, { + name: "1 item, max 0", + items: 1, + max: 0, + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 1, 0).WithOrigin("maxItems"), + }, + }, { + name: "1 item, max 1", + items: 1, + max: 1, + }, { + name: "2 items, max 1", + items: 2, + max: 1, + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 2, 1).WithOrigin("maxItems"), + }, + }, { + name: "0 items, max -1", + items: 0, + max: -1, + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 0, -1).WithOrigin("maxItems"), + }, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + value := make([]bool, tc.items) + gotErrs := MaxItems(context.Background(), operation.Operation{}, field.NewPath("fldpath"), value, nil, tc.max) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMaxProperties(t *testing.T) { + cases := []struct { + name string + properties int + max int + wantErrs field.ErrorList + }{{ + name: "0 properties, max 0", + properties: 0, + max: 0, + }, { + name: "1 property, max 0", + properties: 1, + max: 0, + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 1, 0).WithOrigin("maxProperties"), + }, + }, { + name: "1 property, max 1", + properties: 1, + max: 1, + }, { + name: "2 properties, max 1", + properties: 2, + max: 1, + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 2, 1).WithOrigin("maxProperties"), + }, + }, { + name: "100000 properties, max 100000", + properties: 100000, + max: 100000, + }, { + name: "100001 properties, max 100000", + properties: 100001, + max: 100000, + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 100001, 100000).WithOrigin("maxProperties"), + }, + }, { + // Note: While JSON Schema does not allow negative values for maxProperties, + // we test that the validator handles it safely if it ever occurs at runtime. + name: "0 properties, max -1", + properties: 0, + max: -1, + wantErrs: field.ErrorList{ + field.TooMany(field.NewPath("fldpath"), 0, -1).WithOrigin("maxProperties"), + }, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + value := make(map[string]string, tc.properties) + for i := 0; i < tc.properties; i++ { + value[fmt.Sprintf("%d", i)] = "value" + } + + gotErrs := MaxProperties(context.Background(), operation.Operation{}, field.NewPath("fldpath"), value, nil, tc.max) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMinimum(t *testing.T) { + testMinimumPositive[int](t) + testMinimumNegative[int](t) + testMinimumPositive[int8](t) + testMinimumNegative[int8](t) + testMinimumPositive[int16](t) + testMinimumNegative[int16](t) + testMinimumPositive[int32](t) + testMinimumNegative[int32](t) + testMinimumPositive[int64](t) + testMinimumNegative[int64](t) + + testMinimumPositive[uint](t) + testMinimumPositive[uint8](t) + testMinimumPositive[uint16](t) + testMinimumPositive[uint32](t) + testMinimumPositive[uint64](t) +} + +type minimumTestCase[T constraints.Integer] struct { + min T + value T + wantErrs field.ErrorList +} + +func testMinimumPositive[T constraints.Integer](t *testing.T) { + t.Helper() + cases := []minimumTestCase[T]{{ + min: 0, + value: 0, + }, { + min: 1, + value: 0, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be greater than or equal to").WithOrigin("minimum"), + }, + }, { + min: 1, + value: 1, + }, { + min: 2, + value: 1, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be greater than or equal to").WithOrigin("minimum"), + }, + }} + doTestMinimum[T](t, cases) +} + +func testMinimumNegative[T constraints.Signed](t *testing.T) { + t.Helper() + cases := []minimumTestCase[T]{{ + min: -1, + value: -1, + }, { + min: -1, + value: -2, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be greater than or equal to").WithOrigin("minimum"), + }, + }} + + doTestMinimum[T](t, cases) +} + +func doTestMinimum[T constraints.Integer](t *testing.T, cases []minimumTestCase[T]) { + t.Helper() + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + name := fmt.Sprintf("%T (%v >= %v)", tc.value, tc.value, tc.min) + t.Run(name, func(t *testing.T) { + v := tc.value + gotErrs := Minimum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.min) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMaximum(t *testing.T) { + testMaximumPositive[int](t) + testMaximumNegative[int](t) + testMaximumPositive[int8](t) + testMaximumNegative[int8](t) + testMaximumPositive[int16](t) + testMaximumNegative[int16](t) + testMaximumPositive[int32](t) + testMaximumNegative[int32](t) + testMaximumPositive[int64](t) + testMaximumNegative[int64](t) + + testMaximumPositive[uint](t) + testMaximumPositive[uint8](t) + testMaximumPositive[uint16](t) + testMaximumPositive[uint32](t) + testMaximumPositive[uint64](t) +} + +type maximumTestCase[T constraints.Integer] struct { + max T + value T + wantErrs field.ErrorList +} + +func testMaximumPositive[T constraints.Integer](t *testing.T) { + t.Helper() + cases := []maximumTestCase[T]{{ + max: 0, + value: 0, + }, { + max: 0, + value: 1, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be less than or equal to").WithOrigin("maximum"), + }, + }, { + max: 1, + value: 1, + }, { + max: 1, + value: 2, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be less than or equal to").WithOrigin("maximum"), + }, + }} + doTestMaximum[T](t, cases) +} + +func testMaximumNegative[T constraints.Signed](t *testing.T) { + t.Helper() + cases := []maximumTestCase[T]{{ + max: -1, + value: -1, + }, { + max: -2, + value: -1, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), nil, "must be less than or equal to").WithOrigin("maximum"), + }, + }} + + doTestMaximum[T](t, cases) +} + +func doTestMaximum[T constraints.Integer](t *testing.T, cases []maximumTestCase[T]) { + t.Helper() + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + name := fmt.Sprintf("%T (%v <= %v)", tc.value, tc.value, tc.max) + t.Run(name, func(t *testing.T) { + v := tc.value + gotErrs := Maximum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.max) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMaxBytes(t *testing.T) { + cases := []struct { + name string + value string + max int + wantErrs field.ErrorList + }{{ + name: "empty string", + value: "", + max: 0, + wantErrs: nil, + }, { + name: "zero length", + value: "0", + max: 0, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 0).WithOrigin("maxBytes"), + }, + }, { + name: "one character", + value: "0", + max: 1, + wantErrs: nil, + }, { + name: "two characters", + value: "01", + max: 1, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 1).WithOrigin("maxBytes"), + }, + }, { + value: "", + max: -1, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", -1).WithOrigin("maxBytes"), + }, + }, { + name: "ascii-only characters, less bytes than max", + value: "abcdefghi", + max: 10, + wantErrs: nil, + }, { + name: "multi-byte characters, less bytes than max", + value: "©®©®", + max: 10, + wantErrs: nil, + }, { + name: "ascii-only characters, more bytes than max", + value: "abcdefghijkl", + max: 10, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 10).WithOrigin("maxBytes"), + }, + }, { + name: "multi-byte characters, more bytes than max", + value: "©®©®©©", + max: 10, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 10).WithOrigin("maxBytes"), + }, + }, { + name: "mixture of characters, less bytes than max", + value: "©abc®®", + max: 10, + wantErrs: nil, + }, { + name: "mixture of characters, more bytes than max", + value: "©abc®®abc", + max: 10, + wantErrs: field.ErrorList{ + field.TooLong(field.NewPath("fldpath"), "", 10).WithOrigin("maxBytes"), + }, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + v := tc.value + gotErrs := MaxBytes(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.max) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestMinLength(t *testing.T) { + cases := []struct { + name string + value string + min int + wantErrs field.ErrorList // regex + }{{ + name: "empty string allowed", + value: "", + min: 0, + wantErrs: nil, + }, { + name: "minimum length of one, empty string", + value: "", + min: 1, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "", 1).WithOrigin("minLength"), + }, + }, { + name: "minimum length of one, non-empty string", + value: "test", + min: 1, + wantErrs: nil, + }, { + name: "minimum length of 10, 9 character string", + value: "012345678", + min: 10, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "012345678", 10).WithOrigin("minLength"), + }, + }, { + name: "minimum length of 10, 10 character string", + value: "0123456789", + min: 10, + wantErrs: nil, + }, { + name: "negative minimum value", + value: "", + min: -1, + wantErrs: nil, + }, { + name: "ascii-only characters, less characters than min (n-1)", + value: "abcdefghi", + min: 10, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "abcdefghi", 10).WithOrigin("minLength"), + }, + }, { + name: "multi-byte characters, less characters than min (n-1)", + value: "©®©®©®©®©", + min: 10, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "©®©®©®©®©", 10).WithOrigin("minLength"), + }, + }, { + name: "ascii-only characters, more characters than min (n+1)", + value: "abcdefghijkl", + min: 10, + wantErrs: nil, + }, { + name: "multi-byte characters, more characters than min (n+1)", + value: "©®©®©®©®©®©", + min: 10, + wantErrs: nil, + }, { + name: "mixture of characters, maximum size in bytes of input is greater than min, rune count less than min", + value: "©®©®©®", // 12 bytes, but 6 characters + min: 10, + wantErrs: field.ErrorList{ + field.TooShort(field.NewPath("fldpath"), "©®©®©®", 10).WithOrigin("minLength"), + }, + }, { + name: "multi-byte characters, exact characters as min (n)", + value: "©®©®©®©®©®", + min: 10, + wantErrs: nil, + }, { + name: "ascii-only characters, exact characters as min (n)", + value: "abcdefghij", + min: 10, + wantErrs: nil, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + v := tc.value + gotErrs := MinLength(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &v, nil, tc.min) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/monotonic.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/monotonic.go new file mode 100644 index 0000000000..f0526c9ffe --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/monotonic.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/api/validate/constraints" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// Monotonic validates that an integer value has not decreased on update. +func Monotonic[T constraints.Integer](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T) field.ErrorList { + if op.Type != operation.Update { + return nil + } + + if value == nil || oldValue == nil { + return nil + } + + if *value < *oldValue { + return field.ErrorList{field.Invalid(fldPath, *value, fmt.Sprintf("may not be decreased from %v", *oldValue)).WithOrigin("monotonic")} + } + + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/monotonic_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/monotonic_test.go new file mode 100644 index 0000000000..4f900ef6c9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/monotonic_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/api/validate/constraints" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestMonotonic(t *testing.T) { + testMonotonicPositive[int](t) + testMonotonicNegative[int](t) + testMonotonicPositive[int64](t) + testMonotonicPositive[uint64](t) +} + +type monotonicTestCase[T constraints.Integer] struct { + name string + op operation.Operation + value *T + oldValue *T + wantErrs field.ErrorList +} + +func testMonotonicPositive[T constraints.Integer](t *testing.T) { + t.Helper() + v0 := T(0) + v1 := T(1) + v2 := T(2) + + cases := []monotonicTestCase[T]{{ + name: "create (ignored)", + op: operation.Operation{Type: operation.Create}, + value: &v0, + oldValue: nil, + }, { + name: "update same value", + op: operation.Operation{Type: operation.Update}, + value: &v1, + oldValue: &v1, + }, { + name: "update increase", + op: operation.Operation{Type: operation.Update}, + value: &v2, + oldValue: &v1, + }, { + name: "update decrease", + op: operation.Operation{Type: operation.Update}, + value: &v1, + oldValue: &v2, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), v1, "").WithOrigin("monotonic"), + }, + }, { + name: "update nil value", + op: operation.Operation{Type: operation.Update}, + value: nil, + oldValue: &v1, + }, { + name: "update nil old value", + op: operation.Operation{Type: operation.Update}, + value: &v1, + oldValue: nil, + }} + + doTestMonotonic[T](t, cases) +} + +func testMonotonicNegative[T constraints.Signed](t *testing.T) { + t.Helper() + vM1 := T(-1) + vM2 := T(-2) + + cases := []monotonicTestCase[T]{{ + name: "update negative increase", + op: operation.Operation{Type: operation.Update}, + value: &vM1, + oldValue: &vM2, + }, { + name: "update negative decrease", + op: operation.Operation{Type: operation.Update}, + value: &vM2, + oldValue: &vM1, + wantErrs: field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), vM2, "").WithOrigin("monotonic"), + }, + }} + + doTestMonotonic[T](t, cases) +} + +func doTestMonotonic[T constraints.Integer](t *testing.T, cases []monotonicTestCase[T]) { + t.Helper() + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailSubstring().ByField().ByType() + for _, tc := range cases { + name := fmt.Sprintf("%T %s", *new(T), tc.name) + t.Run(name, func(t *testing.T) { + gotErrs := Monotonic(context.Background(), tc.op, field.NewPath("fldpath"), tc.value, tc.oldValue) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/options.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/options.go new file mode 100644 index 0000000000..143be3b64a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/options.go @@ -0,0 +1,40 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// IfOption conditionally evaluates a validation function. If the option and enabled are both true the validator +// is called. If the option and enabled are both false the validator is called. Otherwise, the validator is not called. +func IfOption[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue T, + optionName string, enabled bool, validator func(context.Context, operation.Operation, *field.Path, T, T) field.ErrorList, +) field.ErrorList { + on, defined := op.HasOption(optionName) + if !defined { + return field.ErrorList{field.InternalError(fldPath, fmt.Errorf("undefined validation option %q", optionName))} + } + if on == enabled { + return validator(ctx, op, fldPath, value, oldValue) + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/required.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/required.go new file mode 100644 index 0000000000..61a253a9d8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/required.go @@ -0,0 +1,133 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// RequiredValue verifies that the specified value is not the zero-value for +// its type. +func RequiredValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + var zero T + if *value != zero { + return nil + } + return field.ErrorList{field.Required(fldPath, "")} +} + +// RequiredPointer verifies that the specified pointer is not nil. +func RequiredPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value != nil { + return nil + } + return field.ErrorList{field.Required(fldPath, "")} +} + +// RequiredSlice verifies that the specified slice is not empty. +func RequiredSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList { + if len(value) > 0 { + return nil + } + return field.ErrorList{field.Required(fldPath, "")} +} + +// RequiredMap verifies that the specified map is not empty. +func RequiredMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList { + if len(value) > 0 { + return nil + } + return field.ErrorList{field.Required(fldPath, "")} +} + +// ForbiddenValue verifies that the specified value is the zero-value for its +// type. +func ForbiddenValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + var zero T + if *value == zero { + return nil + } + return field.ErrorList{field.Forbidden(fldPath, "")} +} + +// ForbiddenPointer verifies that the specified pointer is nil. +func ForbiddenPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + return field.ErrorList{field.Forbidden(fldPath, "")} +} + +// ForbiddenSlice verifies that the specified slice is empty. +func ForbiddenSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList { + if len(value) == 0 { + return nil + } + return field.ErrorList{field.Forbidden(fldPath, "")} +} + +// ForbiddenMap verifies that the specified map is empty. +func ForbiddenMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList { + if len(value) == 0 { + return nil + } + return field.ErrorList{field.Forbidden(fldPath, "")} +} + +// OptionalValue verifies that the specified value is not the zero-value for +// its type. This is identical to RequiredValue, but the caller should treat an +// error here as an indication that the optional value was not specified. +func OptionalValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + var zero T + if *value != zero { + return nil + } + return field.ErrorList{field.Required(fldPath, "optional value was not specified")} +} + +// OptionalPointer verifies that the specified pointer is not nil. This is +// identical to RequiredPointer, but the caller should treat an error here as an +// indication that the optional value was not specified. +func OptionalPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value != nil { + return nil + } + return field.ErrorList{field.Required(fldPath, "optional value was not specified")} +} + +// OptionalSlice verifies that the specified slice is not empty. This is +// identical to RequiredSlice, but the caller should treat an error here as an +// indication that the optional value was not specified. +func OptionalSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList { + if len(value) > 0 { + return nil + } + return field.ErrorList{field.Required(fldPath, "optional value was not specified")} +} + +// OptionalMap verifies that the specified map is not empty. This is identical +// to RequiredMap, but the caller should treat an error here as an indication that +// the optional value was not specified. +func OptionalMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList { + if len(value) > 0 { + return nil + } + return field.ErrorList{field.Required(fldPath, "optional value was not specified")} +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/required_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/required_test.go new file mode 100644 index 0000000000..6511271cd9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/required_test.go @@ -0,0 +1,924 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "regexp" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestRequiredValue(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "value" + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "" // zero-value + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 123 + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 0 // zero-value + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := true + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := false // zero-value + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{"value"} + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{} // zero-value + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := ptr.To("") + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := (*string)(nil) // zero-value + return RequiredValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Required value", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestRequiredPointer(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "" + return RequiredPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*string)(nil) + return RequiredPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 0 + return RequiredPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*int)(nil) + return RequiredPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := false + return RequiredPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*bool)(nil) + return RequiredPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{} + return RequiredPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*struct{ S string })(nil) + return RequiredPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := (*string)(nil) + return RequiredPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (**string)(nil) + return RequiredPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath: Required value", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestRequiredSlice(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []string{""} + return RequiredSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []string{} + return RequiredSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []int{0} + return RequiredSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []int{} + return RequiredSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []bool{false} + return RequiredSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []bool{} + return RequiredSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []*string{nil} + return RequiredSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []*string{} + return RequiredSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Required value", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestRequiredMap(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]string{"": ""} + return RequiredMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]string{} + return RequiredMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[int]int{0: 0} + return RequiredMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[int]int{} + return RequiredMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Required value", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[bool]bool{false: false} + return RequiredMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]bool{} + return RequiredMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Required value", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestOptionalValue(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "value" + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "" // zero-value + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 123 + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 0 // zero-value + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := true + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := false // zero-value + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{"value"} + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{} // zero-value + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := ptr.To("") + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := (*string)(nil) // zero-value + return OptionalValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath:.*optional value was not specified", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestOptionalPointer(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "" + return OptionalPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*string)(nil) + return OptionalPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 0 + return OptionalPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*int)(nil) + return OptionalPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := false + return OptionalPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*bool)(nil) + return OptionalPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{} + return OptionalPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*struct{ S string })(nil) + return OptionalPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := (*string)(nil) + return OptionalPointer(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (**string)(nil) + return OptionalPointer(context.Background(), op, fp, pointer, nil) + }, + err: "fldpath:.*optional value was not specified", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestOptionalSlice(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []string{""} + return OptionalSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []string{} + return OptionalSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []int{0} + return OptionalSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []int{} + return OptionalSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []bool{false} + return OptionalSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []bool{} + return OptionalSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []*string{nil} + return OptionalSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []*string{} + return OptionalSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath:.*optional value was not specified", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestOptionalMap(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]string{"": ""} + return OptionalMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]string{} + return OptionalMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[int]int{0: 0} + return OptionalMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[int]int{} + return OptionalMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath:.*optional value was not specified", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[bool]bool{false: false} + return OptionalMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]bool{} + return OptionalMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath:.*optional value was not specified", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestForbiddenValue(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "" + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "value" + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 0 + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 123 + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := false + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := true + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{} + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{"value"} + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := (*string)(nil) + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := ptr.To("") + return ForbiddenValue(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestForbiddenPointer(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*string)(nil) + return ForbiddenPointer(context.Background(), op, fp, pointer, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := "" + return ForbiddenPointer(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*int)(nil) + return ForbiddenPointer(context.Background(), op, fp, pointer, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := 0 + return ForbiddenPointer(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*bool)(nil) + return ForbiddenPointer(context.Background(), op, fp, pointer, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := false + return ForbiddenPointer(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (*struct{ S string })(nil) + return ForbiddenPointer(context.Background(), op, fp, pointer, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := struct{ S string }{} + return ForbiddenPointer(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + pointer := (**string)(nil) + return ForbiddenPointer(context.Background(), op, fp, pointer, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := (*string)(nil) + return ForbiddenPointer(context.Background(), op, fp, &value, nil) + }, + err: "fldpath: Forbidden", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestForbiddenSlice(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []string{} + return ForbiddenSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []string{""} + return ForbiddenSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []int{} + return ForbiddenSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []int{0} + return ForbiddenSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []bool{} + return ForbiddenSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []bool{false} + return ForbiddenSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []*string{} + return ForbiddenSlice(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := []*string{nil} + return ForbiddenSlice(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Forbidden", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} + +func TestForbiddenMap(t *testing.T) { + cases := []struct { + fn func(op operation.Operation, fp *field.Path) field.ErrorList + err string // regex + }{{ + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]string{} + return ForbiddenMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]string{"": ""} + return ForbiddenMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[int]int{} + return ForbiddenMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[int]int{0: 0} + return ForbiddenMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Forbidden", + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[string]bool{} + return ForbiddenMap(context.Background(), op, fp, value, nil) + }, + }, { + fn: func(op operation.Operation, fp *field.Path) field.ErrorList { + value := map[bool]bool{false: false} + return ForbiddenMap(context.Background(), op, fp, value, nil) + }, + err: "fldpath: Forbidden", + }} + + for i, tc := range cases { + result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) + if len(result) > 0 && tc.err == "" { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err != "" { + t.Errorf("case %d: unexpected success: expected %q", i, tc.err) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/strfmt.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/strfmt.go new file mode 100644 index 0000000000..893f55d777 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/strfmt.go @@ -0,0 +1,329 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/api/validate/content" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +const ( + uuidErrorMessage = "must be a lowercase UUID in 8-4-4-4-12 format" + defaultResourceRequestsPrefix = "requests." + // Default namespace prefix. + resourceDefaultNamespacePrefix = "kubernetes.io/" + resourceDeviceMaxLength = 32 +) + +// ShortName verifies that the specified value is a valid "short name" +// (sometimes known as a "DNS label"). +// - must not be empty +// - must be less than 64 characters long +// - must start and end with lower-case alphanumeric characters +// - must contain only lower-case alphanumeric characters or dashes +// +// All errors returned by this function will be "invalid" type errors. If the +// caller wants better errors, it must take responsibility for checking things +// like required/optional and max-length. +func ShortName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + for _, msg := range content.IsDNS1123Label((string)(*value)) { + allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-short-name")) + } + return allErrs +} + +// LongName verifies that the specified value is a valid "long name" +// (sometimes known as a "DNS subdomain"). +// - must not be empty +// - must be less than 254 characters long +// - each element must start and end with lower-case alphanumeric characters +// - each element must contain only lower-case alphanumeric characters or dashes +// +// All errors returned by this function will be "invalid" type errors. If the +// caller wants better errors, it must take responsibility for checking things +// like required/optional and max-length. +func LongName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + for _, msg := range content.IsDNS1123Subdomain((string)(*value)) { + allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-long-name")) + } + return allErrs +} + +// LabelKey verifies that the specified value is a valid label key. +// A label key is composed of an optional prefix and a name, separated by a '/'. +// The name part is required and must: +// - be 63 characters or less +// - begin and end with an alphanumeric character ([a-z0-9A-Z]) +// - contain only alphanumeric characters, dashes (-), underscores (_), or dots (.) +// +// The prefix is optional and must: +// - be a DNS subdomain +// - be no more than 253 characters +func LabelKey[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + for _, msg := range content.IsLabelKey((string)(*value)) { + allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-label-key")) + } + return allErrs +} + +// PrefixedLabelKey verifies that the specified value is a valid label key with +// a domain prefix. +// A prefixed label key is composed of a prefix and a name, separated by a '/'. +// The name part is required and must: +// - be 63 characters or less +// - begin and end with an alphanumeric character ([a-z0-9A-Z]) +// - contain only alphanumeric characters, dashes (-), underscores (_), or dots (.) +// +// The prefix must: +// - be a DNS subdomain +// - be no more than 253 characters +func PrefixedLabelKey[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + for _, msg := range content.IsPrefixedLabelKey((string)(*value)) { + allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-prefixed-label-key")) + } + return allErrs +} + +// LongNameCaseless verifies that the specified value is a valid "long name" +// (sometimes known as a "DNS subdomain"), but is case-insensitive. +// - must not be empty +// - must be less than 254 characters long +// - each element must start and end with alphanumeric characters +// - each element must contain only alphanumeric characters or dashes +// +// Deprecated: Case-insensitive names are not recommended as they can lead to ambiguity +// (e.g., 'Foo', 'FOO', and 'foo' would be allowed names for foo). Use LongName for strict, lowercase validation. +func LongNameCaseless[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + for _, msg := range content.IsDNS1123SubdomainCaseless((string)(*value)) { + allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-long-name-caseless")) + } + return allErrs +} + +// LabelValue verifies that the specified value is a valid label value. +// - can be empty +// - must be no more than 63 characters +// - must start and end with alphanumeric characters +// - must contain only alphanumeric characters, dashes, underscores, or dots +func LabelValue[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + for _, msg := range content.IsLabelValue((string)(*value)) { + allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-label-value")) + } + return allErrs +} + +// PathSegmentName verifies that the specified value is a valid path segment name. +// A path segment name can be safely encoded as a path segment in URLs and file paths. +// - must not be exactly "." or ".." +// - must not contain "/" (forward slash) +// - must not contain "%" (percent sign) +// - can contain any other characters including mixed case, numbers, dots, hyphens, underscores, and non-ASCII characters +func PathSegmentName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + for _, msg := range content.IsPathSegmentName((string)(*value)) { + allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-path-segment-name")) + } + return allErrs +} + +// UUID verifies that the specified value is a valid UUID (RFC 4122). +// - must be 36 characters long +// - must be in the normalized form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +// - must use only lowercase hexadecimal characters +func UUID[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + val := (string)(*value) + if len(val) != 36 { + return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")} + } + for idx := 0; idx < len(val); idx++ { + character := val[idx] + switch idx { + case 8, 13, 18, 23: + if character != '-' { + return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")} + } + default: + // should be lower case hexadecimal. + if (character < '0' || character > '9') && (character < 'a' || character > 'f') { + return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")} + } + } + } + return nil +} + +// ResourcePoolName verifies that the specified value is one or more valid "long name" +// parts separated by a '/' and no longer than 253 characters. +func ResourcePoolName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + val := (string)(*value) + var allErrs field.ErrorList + if len(val) > 253 { + allErrs = append(allErrs, field.TooLong(fldPath, val, 253)) + } + parts := strings.Split(val, "/") + for i, part := range parts { + if len(part) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("segment %d: must not be empty", i))) + continue + } + // Note that we are overwriting the origin from the underlying LongName validation. + allErrs = append(allErrs, LongName(ctx, op, fldPath, &part, nil).PrefixDetail(fmt.Sprintf("segment %d: ", i))...) + } + return allErrs.WithOrigin("format=k8s-resource-pool-name") +} + +// ExtendedResourceName verifies that the specified value is a valid extended resource name. +// An extended resource name is a domain-prefixed name that does not use the "kubernetes.io" +// or "requests." prefixes. Must be a valid label key when appended to "requests.", as in quota. +// +// - must have slash domain and name. +// - must not have the "kubernetes.io" domain +// - must not have the "requests." prefix +// - name must be 63 characters or less +// - must be a valid label key when appended to "requests.", as in quota +// -- must contain only alphanumeric characters, dashes, underscores, or dots +// -- must end with an alphanumeric character +func ExtendedResourceName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + val := string(*value) + allErrs := field.ErrorList{} + if !strings.Contains(val, "/") { + allErrs = append(allErrs, field.Invalid(fldPath, val, "a name must be a domain-prefixed path, such as 'example.com/my-prop'")) + } else if strings.Contains(val, resourceDefaultNamespacePrefix) { + allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("must not have %q domain", resourceDefaultNamespacePrefix))) + } + // Ensure extended resource is not type of quota. + if strings.HasPrefix(val, defaultResourceRequestsPrefix) { + allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("must not have %q prefix", defaultResourceRequestsPrefix))) + } + + // Ensure it satisfies the rules in IsLabelKey() after converted into quota resource name + nameForQuota := fmt.Sprintf("%s%s", defaultResourceRequestsPrefix, val) + for _, msg := range content.IsLabelKey(nameForQuota) { + allErrs = append(allErrs, field.Invalid(fldPath, val, msg)) + } + return allErrs.WithOrigin("format=k8s-extended-resource-name") +} + +// resourcesQualifiedName verifies that the specified value is a valid Kubernetes resources +// qualified name. +// - must not be empty +// - must be composed of an optional prefix and a name, separated by a slash (e.g., "prefix/name") +// - the prefix, if specified, must be a DNS subdomain +// - the name part must be a C identifier +// - the name part must be no more than 32 characters +func resourcesQualifiedName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + s := string(*value) + parts := strings.Split(s, "/") + // TODO: This validation and the corresponding handwritten validation validateQualifiedName in + // pkg/apis/resource/validation/validation.go are not validating whether there are more than 1 + // slash. This should be fixed in both places. + switch len(parts) { + case 1: + allErrs = append(allErrs, validateCIdentifier(parts[0], resourceDeviceMaxLength, fldPath)...) + case 2: + if len(parts[0]) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "prefix must not be empty")) + } else { + if len(parts[0]) > 63 { + allErrs = append(allErrs, field.TooLong(fldPath, parts[0], 63)) + } + allErrs = append(allErrs, LongName(ctx, op, fldPath, &parts[0], nil).PrefixDetail("prefix: ")...) + } + if len(parts[1]) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "name must not be empty")) + } else { + allErrs = append(allErrs, validateCIdentifier(parts[1], resourceDeviceMaxLength, fldPath)...) + } + } + return allErrs +} + +// ResourceFullyQualifiedName verifies that the specified value is a valid Kubernetes +// fully qualified name. +// - must not be empty +// - must be composed of a prefix and a name, separated by a slash (e.g., "prefix/name") +// - the prefix must be a DNS subdomain +// - the name part must be a C identifier +// - the name part must be no more than 32 characters +func ResourceFullyQualifiedName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList { + if value == nil { + return nil + } + var allErrs field.ErrorList + s := string(*value) + allErrs = append(allErrs, resourcesQualifiedName(ctx, op, fldPath, &s, nil)...) + if !strings.Contains(s, "/") { + allErrs = append(allErrs, field.Invalid(fldPath, s, "a fully qualified name must be a domain and a name separated by a slash")) + } + return allErrs.WithOrigin("format=k8s-resource-fully-qualified-name") +} + +func validateCIdentifier(id string, length int, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if len(id) > length { + allErrs = append(allErrs, field.TooLong(fldPath, id, length)) + } + for _, msg := range content.IsCIdentifier(id) { + allErrs = append(allErrs, field.Invalid(fldPath, id, msg)) + } + return allErrs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/strfmt_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/strfmt_test.go new file mode 100644 index 0000000000..1310f414a3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/strfmt_test.go @@ -0,0 +1,950 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestShortName(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid", + input: "abc-123", + wantErrs: nil, + }, { + name: "invalid: empty", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character").WithOrigin("format=k8s-short-name"), + }, + }, { + name: "invalid: too long", + input: "01234567890123456789012345678901234567890123456789012345678901234", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "01234567890123456789012345678901234567890123456789012345678901234", "must be no more than 63 bytes").WithOrigin("format=k8s-short-name"), + }, + }, { + name: "invalid: starts with dash", + input: "-abc-123", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "-abc-123", "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character").WithOrigin("format=k8s-short-name"), + }, + }, { + name: "invalid: ends with dash", + input: "abc-123-", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "abc-123-", "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character").WithOrigin("format=k8s-short-name"), + }, + }, { + name: "invalid: upper-case", + input: "ABC-123", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "ABC-123", "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character").WithOrigin("format=k8s-short-name"), + }, + }, { + name: "invalid: other chars", + input: "abc_123", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "abc_123", "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character").WithOrigin("format=k8s-short-name"), + }, + }} + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := tc.input + gotErrs := ShortName(ctx, operation.Operation{}, fldPath, &value, nil) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestLongName(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid", + input: "a.b.c", + wantErrs: nil, + }, { + name: "invalid: empty", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name"), + }, + }, { + name: "invalid: too long", + input: strings.Repeat("a", 254), + wantErrs: field.ErrorList{ + field.Invalid(fldPath, strings.Repeat("a", 254), "must be no more than 253 bytes").WithOrigin("format=k8s-long-name"), + }, + }, { + name: "invalid: starts with dash", + input: "-a.b.c", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "-a.b.c", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name"), + }, + }, { + name: "invalid: ends with dash", + input: "a.b.c-", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "a.b.c-", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name"), + }, + }, { + name: "invalid: upper-case", + input: "A.b.c", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "A.b.c", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name"), + }, + }, { + name: "invalid: other chars", + input: "a_b.c", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "a_b.c", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name"), + }, + }} + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := tc.input + gotErrs := LongName(ctx, operation.Operation{}, fldPath, &value, nil) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestLabelKey(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid key", + input: "app", + wantErrs: nil, + }, { + name: "valid key with dash", + input: "app-name", + wantErrs: nil, + }, { + name: "valid key with dot", + input: "app.name", + wantErrs: nil, + }, { + name: "valid key with underscore", + input: "app_name", + wantErrs: nil, + }, { + name: "valid key with prefix", + input: "example.com/app", + wantErrs: nil, + }, { + name: "valid key with long prefix", + input: strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 55) + "/app", + wantErrs: nil, + }, { + name: "invalid: empty string", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-key"), + }, + }, { + name: "invalid: starts with dash", + input: "-app", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-key"), + }, + }, { + name: "invalid: ends with dash", + input: "app-", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-key"), + }, + }, { + name: "invalid: contains invalid characters", + input: "app^", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-key"), + }, + }, { + name: "invalid: name too long", + input: strings.Repeat("a", 64), + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-key"), + }, + }, { + name: "invalid: prefix too long", + input: strings.Repeat("a", 254) + "/app", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-key"), + }, + }, { + name: "invalid: prefix is not a DNS subdomain", + input: "example-.com/app", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-key"), + }, + }, { + name: "nil value", + input: "", // This will be handled by setting value to nil in the test runner + wantErrs: nil, + }} + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var value *string + if tc.name != "nil value" { + v := tc.input + value = &v + } + gotErrs := LabelKey(ctx, operation.Operation{}, fldPath, value, nil) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestPrefixedLabelKey(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid key", + input: "app", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "valid key with dash", + input: "app-name", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "valid key with dot", + input: "app.name", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "valid key with underscore", + input: "app_name", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "valid key with prefix", + input: "example.com/app", + wantErrs: nil, + }, { + name: "valid key with long prefix", + input: strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 55) + "/app", + wantErrs: nil, + }, { + name: "invalid: empty string", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "invalid: starts with dash", + input: "-app", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "invalid: ends with dash", + input: "app-", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "invalid: contains invalid characters", + input: "app^", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "invalid: name too long", + input: strings.Repeat("a", 64), + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "invalid: prefix too long", + input: strings.Repeat("a", 254) + "/app", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "invalid: prefix is not a DNS subdomain", + input: "example-.com/app", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }, + }, { + name: "nil value", + input: "", // This will be handled by setting value to nil in the test runner + wantErrs: nil, + }} + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var value *string + if tc.name != "nil value" { + v := tc.input + value = &v + } + gotErrs := PrefixedLabelKey(ctx, operation.Operation{}, fldPath, value, nil) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestK8sUUID(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid uuid with hyphens", + input: "123e4567-e89b-12d3-a456-426614174000", + wantErrs: nil, + }, { + name: "invalid uuid with hyphens uppercase", + input: "123E4567-E89B-12D3-A456-426614174000", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "123E4567-E89B-12D3-A456-426614174000", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + }, + }, { + name: "invalid uuid with urn prefix", + input: "urn:uuid:123e4567-e89b-12d3-a456-426614174000", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "urn:uuid:123e4567-e89b-12d3-a456-426614174000", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + }, + }, { + name: "invalid uuid without hyphens", + input: "123e4567e89b12d3a456426614174000", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "123e4567e89b12d3a456426614174000", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + }, + }, { + name: "invalid: wrong length", + input: "123e4567-e89b-12d3-a456-42661417400", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "123e4567-e89b-12d3-a456-42661417400", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + }, + }, { + name: "invalid: wrong characters", + input: "123e4567-e89b-12d3-a456-42661417400g", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "123e4567-e89b-12d3-a456-42661417400g", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + }, + }, { + name: "invalid: misplaced hyphens", + input: "123e4567-e89b-12d3-a4564-26614174000", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "123e4567-e89b-12d3-a4564-26614174000", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + }, + }, { + name: "empty string", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + }, + }, { + name: "not a uuid", + input: "not-a-uuid", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "not-a-uuid", "must be a lowercase UUID in 8-4-4-4-12 format").WithOrigin("format=k8s-uuid"), + }, + }} + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailExact() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := tc.input + gotErrs := UUID(ctx, operation.Operation{}, fldPath, &value, nil) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestLabelValue(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid value", + input: "valid-value", + wantErrs: nil, + }, { + name: "valid value with dots", + input: "valid.value", + wantErrs: nil, + }, { + name: "valid value with underscores", + input: "valid_value", + wantErrs: nil, + }, { + name: "valid single character value", + input: "a", + wantErrs: nil, + }, { + name: "valid value with numbers", + input: "123-abc", + wantErrs: nil, + }, { + name: "valid uppercase characters", + input: "Valid-Value", + wantErrs: nil, + }, { + name: "invalid: starts with dash", + input: "-invalid-value", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-value"), + }, + }, { + name: "invalid: ends with dash", + input: "invalid-value-", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-value"), + }, + }, { + name: "invalid: starts with dot", + input: ".invalid.value", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-value"), + }, + }, { + name: "invalid: ends with dot", + input: "invalid.value.", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-value"), + }, + }, { + name: "invalid: starts with underscore", + input: "_invalid_value", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-value"), + }, + }, { + name: "invalid: ends with underscore", + input: "invalid_value_", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-value"), + }, + }, { + name: "invalid: contains special characters", + input: "invalid@value", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-value"), + }, + }, { + name: "invalid: too long", + input: "a" + strings.Repeat("b", 62) + "c", // 64 characters + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-label-value"), + }, + }, { + name: "valid: max length", + input: "a" + strings.Repeat("b", 61) + "c", // 63 characters + wantErrs: nil, + }, { + name: "valid: empty string", + input: "", + wantErrs: nil, + }} + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := tc.input + gotErrs := LabelValue(ctx, operation.Operation{}, fldPath, &value, nil) + + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestPathSegmentName(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + // This format validator does not check for empty strings or max length, + // only checks path-segment-unsafe characters. Those constraints are + // handled by separate validators (Required, maxLength) in the schema. + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid simple name", + input: "valid-name", + wantErrs: nil, + }, { + name: "valid with dots", + input: "foo.bar.baz", + wantErrs: nil, + }, { + name: "valid with mixed case", + input: "MyResource", + wantErrs: nil, + }, { + name: "valid with numbers", + input: "resource123", + wantErrs: nil, + }, { + name: "valid with underscores", + input: "my_resource", + wantErrs: nil, + }, { + name: "valid complex identifier", + input: "sha256:ABCDEF012345@ABCDEF012345", + wantErrs: nil, + }, { + name: "valid with non-ASCII characters", + input: "Iñtërnâtiônàlizætiøn", + wantErrs: nil, + }, { + name: "valid with leading dot", + input: ".test", + wantErrs: nil, + }, { + name: "valid with leading double dot", + input: "..test", + wantErrs: nil, + }, { + name: "valid empty string", + input: "", + wantErrs: nil, + }, { + name: "invalid: exactly dot", + input: ".", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, ".", "may not be '.'").WithOrigin("format=k8s-path-segment-name"), + }, + }, { + name: "invalid: exactly double dot", + input: "..", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "..", "may not be '..'").WithOrigin("format=k8s-path-segment-name"), + }, + }, { + name: "invalid: contains slash", + input: "foo/bar", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "foo/bar", "may not contain '/'").WithOrigin("format=k8s-path-segment-name"), + }, + }, { + name: "invalid: contains percent", + input: "foo%bar", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "foo%bar", "may not contain '%'").WithOrigin("format=k8s-path-segment-name"), + }, + }, { + name: "invalid: contains both slash and percent", + input: "foo/bar%baz", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "may not contain '/'").WithOrigin("format=k8s-path-segment-name"), + field.Invalid(fldPath, nil, "may not contain '%'").WithOrigin("format=k8s-path-segment-name"), + }, + }} + + exactMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := tc.input + gotErrs := PathSegmentName(ctx, operation.Operation{}, fldPath, &value, nil) + + exactMatcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestLongNameCaseless(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid", + input: "A.b.C", + wantErrs: nil, + }, { + name: "invalid: empty", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "an RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name-caseless"), + }, + }, { + name: "invalid: too long", + input: strings.Repeat("a", 254), + wantErrs: field.ErrorList{ + field.Invalid(fldPath, strings.Repeat("a", 254), "must be no more than 253 bytes").WithOrigin("format=k8s-long-name-caseless"), + }, + }, { + name: "invalid: starts with dash", + input: "-A.b.C", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "-A.b.C", "an RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name-caseless"), + }, + }, + { + name: "invalid: ends with dash", + input: "A.b.C-", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "A.b.C-", "an RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name-caseless"), + }, + }, + { + name: "invalid: other chars", + input: "A_b.C", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "A_b.C", "an RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character").WithOrigin("format=k8s-long-name-caseless"), + }, + }, + } + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := tc.input + gotErrs := LongNameCaseless(ctx, operation.Operation{}, fldPath, &value, nil) + + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestResourcePoolName(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid: single segment", + input: "a.valid.long-name", + }, { + name: "valid: two segments", + input: "a.valid.long-name/another.one", + }, { + name: "valid: multiple segments", + input: "a/b/c.d.e", + }, { + name: "valid: segments with numbers", + input: "1.2.3/4.5.6", + }, { + name: "invalid: empty string", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "segment 0: must not be empty").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: leading slash", + input: "/a.b.c", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: trailing slash", + input: "a.b.c/", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: double slash", + input: "a.b.c//d.e.f", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: one segment has uppercase", + input: "a.valid.name/Not.Valid", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "segment 1: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: one segment starts with dash", + input: "a.valid.name/-not-valid", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "segment 1: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: one segment has special characters", + input: "a.valid.name/not_valid", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "segment 1: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: too long", + input: "a.valid.name/" + strings.Repeat("b", 253), + wantErrs: field.ErrorList{ + field.TooLong(fldPath, nil, 253).WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: segment too long", + input: strings.Repeat("b", 254), + wantErrs: field.ErrorList{ + field.TooLong(fldPath, nil, 253).WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(fldPath, nil, "segment 0: must be no more than 253 bytes").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: multiple invalid segments", + input: "Not/Valid/Either", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "segment 0: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(fldPath, nil, "segment 1: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(fldPath, nil, "segment 2: a lowercase RFC 1123").WithOrigin("format=k8s-resource-pool-name"), + }, + }, { + name: "invalid: just a slash", + input: "/", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, nil, "segment 0: must not be empty").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(fldPath, nil, "segment 1: must not be empty").WithOrigin("format=k8s-resource-pool-name"), + }, + }} + + exactMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := &tc.input + gotErrs := ResourcePoolName(ctx, operation.Operation{}, fldPath, value, nil) + exactMatcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestExtendedResourceName(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{ + { + name: "valid", + input: "example-kub.io/foo", + wantErrs: nil, + }, + { + name: "invalid: name too long", + input: strings.Repeat("a", 64), + wantErrs: field.ErrorList{ + field.Invalid(fldPath, strings.Repeat("a", 64), "a name must be a domain-prefixed path, such as 'example.com/my-prop").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(fldPath, strings.Repeat("a", 64), "name part must be no more than 63 bytes").WithOrigin("format=k8s-extended-resource-name"), + }, + }, + { + name: "invalid: empty", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "a name must be a domain-prefixed path, such as 'example.com/my-prop").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(fldPath, "", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')").WithOrigin("format=k8s-extended-resource-name"), + }, + }, + { + name: "invalid: no domain", + input: "foo", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "foo", "a name must be a domain-prefixed path, such as 'example.com/my-prop'").WithOrigin("format=k8s-extended-resource-name"), + }, + }, + { + name: "invalid: no domain and no name", + input: "/", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "/", "name part must be non-empty").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(fldPath, "/", "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(fldPath, "/", "prefix part a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')").WithOrigin("format=k8s-extended-resource-name"), + }, + }, + { + name: "invalid: something.kubernetes.io domain", + input: "something.kubernetes.io/foo", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "something.kubernetes.io/foo", "must not have \"kubernetes.io/\" domain").WithOrigin("format=k8s-extended-resource-name"), + }, + }, + { + name: "invalid: kubernetes.io domain", + input: "kubernetes.io/foo", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "kubernetes.io/foo", "must not have \"kubernetes.io/\" domain").WithOrigin("format=k8s-extended-resource-name"), + }, + }, + { + name: "invalid: requests prefix", + input: "requests.example.com/foo", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "requests.example.com/foo", "must not have \"requests.\" prefix").WithOrigin("format=k8s-extended-resource-name"), + }, + }, + { + name: "invalid: name too long", + input: "example.com/" + strings.Repeat("a", 64), + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "example.com/"+strings.Repeat("a", 64), "name part must be no more than 63 bytes").WithOrigin("format=k8s-extended-resource-name"), + }, + }, + } + + exactMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := &tc.input + gotErrs := ExtendedResourceName(ctx, operation.Operation{}, fldPath, value, nil) + exactMatcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} + +func TestResourceFullyQualifiedName(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("test") + + testCases := []struct { + name string + input string + wantErrs field.ErrorList + }{{ + name: "valid name with prefix", + input: "prefix.com/name", + wantErrs: nil, + }, { + name: "valid name with complex prefix", + input: "my-subdomain.example.com/name", + wantErrs: nil, + }, { + name: "invalid name with dots", + input: "prefix.com/name.with.dots", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "name.with.dots", "a valid C identifier must start with alphabetic character").WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }, { + name: "invalid name with dashes", + input: "prefix.com/name-with-dashes", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "name-with-dashes", "a valid C identifier must start with alphabetic character").WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }, { + name: "invalid: no prefix", + input: "name", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "name", "a fully qualified name must be a domain and a name separated by a slash").WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }, { + name: "invalid: empty", + input: "", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "a fully qualified name must be a domain and a name separated by a slash").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(fldPath, "", "a valid C identifier must start with alphabetic character").WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }, { + name: "invalid: prefix too long", + input: strings.Repeat("a", 254) + "/name", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, strings.Repeat("a", 254), "prefix: must be no more than 253 bytes").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.TooLong(fldPath, strings.Repeat("a", 254), 63).WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }, { + name: "invalid: name too long", + input: "prefix.com/" + strings.Repeat("a", 64), + wantErrs: field.ErrorList{ + field.TooLong(fldPath, strings.Repeat("a", 64), 32).WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }, { + name: "invalid: prefix is not a valid DNS subdomain", + input: "Prefix.com/name", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "Prefix.com", "prefix: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters").WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }, { + name: "invalid: name is not a valid RFC 1123 label", + input: "prefix.com/Name", + wantErrs: nil, // no errors, C-identifiers can have uppercase letters + }, { + name: "invalid: more than one slash", + input: "prefix.com/name/extra", + wantErrs: nil, // This is not validated, yet. + }, { + name: "invalid: empty name", + input: "prefix.com/", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "name must not be empty").WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }, { + name: "invalid: empty prefix", + input: "/name", + wantErrs: field.ErrorList{ + field.Invalid(fldPath, "", "prefix must not be empty").WithOrigin("format=k8s-resource-fully-qualified-name"), + }, + }} + + matcher := field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := tc.input + gotErrs := ResourceFullyQualifiedName(ctx, operation.Operation{}, fldPath, &value, nil) + matcher.Test(t, tc.wantErrs, gotErrs) + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/subfield.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/subfield.go new file mode 100644 index 0000000000..7aa48d3304 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/subfield.go @@ -0,0 +1,56 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// GetFieldFunc is a function that extracts a field from a type and returns a +// nilable value. +type GetFieldFunc[Tstruct any, Tfield any] func(*Tstruct) Tfield + +// Subfield validates a subfield of a struct against a validator function. If +// the value of the subfield is the same as the previous value, as per the +// equiv function, then no validation is performed. +// +// The equiv function can be called with nil arguments in the case of nilable +// fields. +// +// The fldPath passed to the validator includes the subfield name. +func Subfield[Tstruct any, Tfield any]( + ctx context.Context, op operation.Operation, fldPath *field.Path, + newStruct, oldStruct *Tstruct, + fldName string, getField GetFieldFunc[Tstruct, Tfield], + equiv MatchFunc[Tfield], + validator ValidateFunc[Tfield], +) field.ErrorList { + var errs field.ErrorList + newVal := getField(newStruct) + var oldVal Tfield + if oldStruct != nil { + oldVal = getField(oldStruct) + } + if op.Type == operation.Update && oldStruct != nil && equiv(newVal, oldVal) { + return nil + } + errs = append(errs, validator(ctx, op, fldPath.Child(fldName), newVal, oldVal)...) + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/testing.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/testing.go new file mode 100644 index 0000000000..461bb0cdd0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/testing.go @@ -0,0 +1,35 @@ +/* +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 validate + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// FixedResult asserts a fixed boolean result. This is mostly useful for +// testing. +func FixedResult[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ T, result bool, arg string) field.ErrorList { + if result { + return nil + } + return field.ErrorList{ + field.Invalid(fldPath, value, "forced failure: "+arg).WithOrigin("validateFalse"), + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/testing_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/testing_test.go new file mode 100644 index 0000000000..762e6df621 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/testing_test.go @@ -0,0 +1,146 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestFixedResult(t *testing.T) { + cases := []struct { + value any + pass bool + }{{ + value: "", + pass: false, + }, { + value: "", + pass: true, + }, { + value: "nonempty", + pass: false, + }, { + value: "nonempty", + pass: true, + }, { + value: 0, + pass: false, + }, { + value: 0, + pass: true, + }, { + value: 1, + pass: false, + }, { + value: 1, + pass: true, + }, { + value: false, + pass: false, + }, { + value: false, + pass: true, + }, { + value: true, + pass: false, + }, { + value: true, + pass: true, + }, { + value: nil, + pass: false, + }, { + value: nil, + pass: true, + }, { + value: ptr.To(""), + pass: false, + }, { + value: ptr.To(""), + pass: true, + }, { + value: ptr.To("nonempty"), + pass: false, + }, { + value: ptr.To("nonempty"), + pass: true, + }, { + value: []string(nil), + pass: false, + }, { + value: []string(nil), + pass: true, + }, { + value: []string{}, + pass: false, + }, { + value: []string{}, + pass: true, + }, { + value: []string{"s"}, + pass: false, + }, { + value: []string{"s"}, + pass: true, + }, { + value: map[string]string(nil), + pass: false, + }, { + value: map[string]string(nil), + pass: true, + }, { + value: map[string]string{}, + pass: false, + }, { + value: map[string]string{}, + pass: true, + }, { + value: map[string]string{"k": "v"}, + pass: false, + }, { + value: map[string]string{"k": "v"}, + pass: true, + }} + + matcher := field.ErrorMatcher{}.ByOrigin().ByDetailExact() + for i, tc := range cases { + result := FixedResult(context.Background(), operation.Operation{}, field.NewPath("fldpath"), tc.value, nil, tc.pass, "detail string") + if len(result) != 0 && tc.pass { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && !tc.pass { + t.Errorf("case %d: unexpected success", i) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + wantErrorList := field.ErrorList{ + field.Invalid(field.NewPath("fldpath"), tc.value, "forced failure: detail string").WithOrigin("validateFalse"), + } + matcher.Test(t, wantErrorList, result) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/union.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/union.go new file mode 100644 index 0000000000..753a4a9ee1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/union.go @@ -0,0 +1,238 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "fmt" + "reflect" + "strings" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ExtractorFn extracts a value from a parent object. Depending on the context, +// that could be the value of a field or just whether that field was set or +// not. +// Note: obj is not guaranteed to be non-nil, need to handle nil obj in the +// extractor. +type ExtractorFn[T, V any] func(obj T) V + +// UnionValidationOptions configures how union validation behaves +type UnionValidationOptions struct { + // ErrorForEmpty returns error when no fields are set (nil means no error) + ErrorForEmpty func(fldPath *field.Path, allFields []string) *field.Error + + // ErrorForMultiple returns error when multiple fields are set (nil means no error) + ErrorForMultiple func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error +} + +// Union verifies that exactly one member of a union is specified. +// +// UnionMembership must define all the members of the union. +// +// For example: +// +// var UnionMembershipForABC := validate.NewUnionMembership( +// validate.NewUnionMember("a"), +// validate.NewUnionMember("b"), +// validate.NewUnionMember("c"), +// ) +// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) { +// errs = append(errs, Union(ctx, op, fldPath, in, oldIn, UnionMembershipForABC, +// func(in *ABC) bool { return in.A != nil }, +// func(in *ABC) bool { return in.B != "" }, +// func(in *ABC) bool { return in.C != 0 }, +// )...) +// return errs +// } +// +// Note that T is "any", rather than "comparable", because union-members can be +// slices, meaning T might be a struct with a slice, meaning it is not +// comparable. +func Union[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList { + options := UnionValidationOptions{ + ErrorForEmpty: func(fldPath *field.Path, allFields []string) *field.Error { + return field.Invalid(fldPath, "", + fmt.Sprintf("must specify one of: %s", strings.Join(allFields, ", "))) + }, + ErrorForMultiple: func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error { + return field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(specifiedFields, ", ")), + fmt.Sprintf("must specify exactly one of: %s", strings.Join(allFields, ", "))) + }, + } + + return unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...).WithOrigin("union") +} + +// DiscriminatedUnion verifies specified union member matches the discriminator. +// +// UnionMembership must define all the members of the union and the discriminator. +// +// For example: +// +// var UnionMembershipForABC = validate.NewDiscriminatedUnionMembership("type", +// validate.NewDiscriminatedUnionMember("a", "A"), +// validate.NewDiscriminatedUnionMember("b", "B"), +// validate.NewDiscriminatedUnionMember("c", "C"), +// ) +// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) { +// errs = append(errs, DiscriminatedUnion(ctx, op, fldPath, in, oldIn, UnionMembershipForABC, +// func(in *ABC) string { return string(in.Type) }, +// func(in *ABC) bool { return in.A != nil }, +// func(in *ABC) bool { return in.B != "" }, +// func(in *ABC) bool { return in.C != 0 }, +// )...) +// return errs +// } +// +// It is not an error for the discriminatorValue to be unknown. That must be +// validated on its own. +// +// Note that T is "any", rather than "comparable", because union-members can be +// slices, meaning T might be a struct with a slice, meaning it is not +// comparable. +func DiscriminatedUnion[T any, D ~string](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, discriminatorExtractor ExtractorFn[T, D], isSetFns ...ExtractorFn[T, bool]) (errs field.ErrorList) { + if len(union.members) != len(isSetFns) { + return field.ErrorList{ + field.InternalError(fldPath, + fmt.Errorf("number of extractors (%d) does not match number of union members (%d)", + len(isSetFns), len(union.members))), + } + } + hasOldValue := !reflect.ValueOf(oldObj).IsZero() // because T is any, rather than comparable + var changed bool + discriminatorValue := discriminatorExtractor(obj) + if op.Type == operation.Update { + oldDiscriminatorValue := discriminatorExtractor(oldObj) + changed = discriminatorValue != oldDiscriminatorValue + } + + for i, fieldIsSet := range isSetFns { + member := union.members[i] + isDiscriminatedMember := string(discriminatorValue) == member.discriminatorValue + newIsSet := fieldIsSet(obj) + if op.Type == operation.Update && !changed { + oldIsSet := fieldIsSet(oldObj) + changed = changed || newIsSet != oldIsSet + } + if newIsSet && !isDiscriminatedMember { + errs = append(errs, field.Invalid(fldPath.Child(member.fieldName), "", + fmt.Sprintf("may only be specified when `%s` is %q", union.discriminatorName, member.discriminatorValue))) + } else if !newIsSet && isDiscriminatedMember { + errs = append(errs, field.Invalid(fldPath.Child(member.fieldName), "", + fmt.Sprintf("must be specified when `%s` is %q", union.discriminatorName, discriminatorValue))) + } + } + // If the union discriminator and membership is unchanged, we don't need to + // re-validate. + if op.Type == operation.Update && hasOldValue && !changed { + return nil + } + return errs.WithOrigin("union") +} + +// UnionMember represents a member of a union. +type UnionMember struct { + fieldName string + discriminatorValue string +} + +// NewUnionMember returns a new UnionMember for the given field name. +func NewUnionMember(fieldName string) UnionMember { + return UnionMember{fieldName: fieldName} +} + +// NewDiscriminatedUnionMember returns a new UnionMember for the given field +// name and discriminator value. +func NewDiscriminatedUnionMember(fieldName, discriminatorValue string) UnionMember { + return UnionMember{fieldName: fieldName, discriminatorValue: discriminatorValue} +} + +// UnionMembership represents an ordered list of field union memberships. +type UnionMembership struct { + discriminatorName string + members []UnionMember +} + +// NewUnionMembership returns a new UnionMembership for the given list of members. +// Member names must be unique. +func NewUnionMembership(member ...UnionMember) *UnionMembership { + return NewDiscriminatedUnionMembership("", member...) +} + +// NewDiscriminatedUnionMembership returns a new UnionMembership for the given discriminator field and list of members. +// members are provided in the same way as for NewUnionMembership. +func NewDiscriminatedUnionMembership(discriminatorFieldName string, members ...UnionMember) *UnionMembership { + return &UnionMembership{ + discriminatorName: discriminatorFieldName, + members: members, + } +} + +// allFields returns a string listing all the field names of the member of a union for use in error reporting. +func (u UnionMembership) allFields() []string { + memberNames := make([]string, 0, len(u.members)) + for _, f := range u.members { + memberNames = append(memberNames, fmt.Sprintf("`%s`", f.fieldName)) + } + return memberNames +} + +func unionValidate[T any](op operation.Operation, fldPath *field.Path, + obj, oldObj T, union *UnionMembership, options UnionValidationOptions, isSetFns ...ExtractorFn[T, bool], +) field.ErrorList { + if len(union.members) != len(isSetFns) { + return field.ErrorList{ + field.InternalError(fldPath, + fmt.Errorf("number of extractors (%d) does not match number of union members (%d)", + len(isSetFns), len(union.members))), + } + } + + hasOldValue := !reflect.ValueOf(oldObj).IsZero() // because T is any, rather than comparable + var specifiedFields []string + var changed bool + for i, fieldIsSet := range isSetFns { + newIsSet := fieldIsSet(obj) + if op.Type == operation.Update && !changed { + oldIsSet := fieldIsSet(oldObj) + changed = changed || newIsSet != oldIsSet + } + if newIsSet { + specifiedFields = append(specifiedFields, union.members[i].fieldName) + } + } + + // If the union membership is unchanged, we don't need to re-validate. + if op.Type == operation.Update && hasOldValue && !changed { + return nil + } + + var errs field.ErrorList + + if len(specifiedFields) > 1 && options.ErrorForMultiple != nil { + errs = append(errs, options.ErrorForMultiple(fldPath, specifiedFields, union.allFields())) + } + + if len(specifiedFields) == 0 && options.ErrorForEmpty != nil { + errs = append(errs, options.ErrorForEmpty(fldPath, union.allFields())) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/union_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/union_test.go new file mode 100644 index 0000000000..4afc3f759d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/union_test.go @@ -0,0 +1,485 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +type testMember struct{} + +func TestUnion(t *testing.T) { + testCases := []struct { + name string + fields []string + fieldValues []bool + expected field.ErrorList + }{ + { + name: "one member set", + fields: []string{"a", "b", "c", "d"}, + fieldValues: []bool{false, false, false, true}, + expected: nil, + }, + { + name: "two members set", + fields: []string{"a", "b", "c", "d"}, + fieldValues: []bool{false, true, false, true}, + expected: field.ErrorList{field.Invalid(nil, "{b, d}", "must specify exactly one of: `a`, `b`, `c`, `d`")}.WithOrigin("union"), + }, + { + name: "all members set", + fields: []string{"a", "b", "c", "d"}, + fieldValues: []bool{true, true, true, true}, + expected: field.ErrorList{field.Invalid(nil, "{a, b, c, d}", "must specify exactly one of: `a`, `b`, `c`, `d`")}.WithOrigin("union"), + }, + { + name: "no member set", + fields: []string{"a", "b", "c", "d"}, + fieldValues: []bool{false, false, false, false}, + expected: field.ErrorList{field.Invalid(nil, "", "must specify one of: `a`, `b`, `c`, `d`")}.WithOrigin("union"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + members := []UnionMember{} + for _, f := range tc.fields { + members = append(members, NewUnionMember(f)) + } + + t.Run("pointer", func(t *testing.T) { + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ *testMember) bool { return val } + } + + got := Union(context.Background(), operation.Operation{}, nil, &testMember{}, nil, + NewUnionMembership(members...), extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + t.Run("value", func(t *testing.T) { + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ testMember) bool { return val } + } + + got := Union(context.Background(), operation.Operation{}, nil, testMember{}, testMember{}, + NewUnionMembership(members...), extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + }) + } +} + +func TestDiscriminatedUnion(t *testing.T) { + testCases := []struct { + name string + discriminatorField string + fields [][2]string + discriminatorValue string + fieldValues []bool + expected field.ErrorList + }{ + { + name: "valid discriminated union A", + discriminatorField: "d", + fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}}, + discriminatorValue: "A", + fieldValues: []bool{true, false, false, false}, + }, + { + name: "valid discriminated union C", + discriminatorField: "d", + fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}}, + discriminatorValue: "C", + fieldValues: []bool{false, false, true, false}, + }, + { + name: "invalid, discriminator not set to member that is specified", + discriminatorField: "type", + fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}}, + discriminatorValue: "C", + fieldValues: []bool{false, true, false, false}, + expected: field.ErrorList{ + field.Invalid(field.NewPath("b"), "", "may only be specified when `type` is \"B\""), + field.Invalid(field.NewPath("c"), "", "must be specified when `type` is \"C\""), + }.WithOrigin("union"), + }, + { + name: "invalid, discriminator correct, multiple members set", + discriminatorField: "type", + fields: [][2]string{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}}, + discriminatorValue: "C", + fieldValues: []bool{false, true, true, true}, + expected: field.ErrorList{ + field.Invalid(field.NewPath("b"), "", "may only be specified when `type` is \"B\""), + field.Invalid(field.NewPath("d"), "", "may only be specified when `type` is \"D\""), + }.WithOrigin("union"), + }, + } + + for _, tc := range testCases { + members := []UnionMember{} + for _, f := range tc.fields { + members = append(members, NewDiscriminatedUnionMember(f[0], f[1])) + } + + t.Run(tc.name, func(t *testing.T) { + t.Run("pointer", func(t *testing.T) { + discriminatorExtractor := func(_ *testMember) string { return tc.discriminatorValue } + + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ *testMember) bool { return val } + } + + got := DiscriminatedUnion(context.Background(), operation.Operation{}, nil, &testMember{}, nil, + NewDiscriminatedUnionMembership(tc.discriminatorField, members...), discriminatorExtractor, extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got.ToAggregate(), tc.expected.ToAggregate()) + } + }) + t.Run("value", func(t *testing.T) { + discriminatorExtractor := func(_ testMember) string { return tc.discriminatorValue } + + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ testMember) bool { return val } + } + + got := DiscriminatedUnion(context.Background(), operation.Operation{}, nil, testMember{}, testMember{}, + NewDiscriminatedUnionMembership(tc.discriminatorField, members...), discriminatorExtractor, extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got.ToAggregate(), tc.expected.ToAggregate()) + } + }) + }) + } +} + +type testStruct struct { + M1 *m1 `json:"m1"` + M2 *m2 `json:"m2"` + M3 []string `json:"m3"` + M4 map[string]string `json:"m4"` +} + +type m1 struct{} +type m2 struct{} + +var extractors = []ExtractorFn[*testStruct, bool]{ + func(s *testStruct) bool { + if s == nil { + return false + } + return s.M1 != nil + }, + func(s *testStruct) bool { + if s == nil { + return false + } + return s.M2 != nil + }, + func(s *testStruct) bool { + if s == nil { + return false + } + return len(s.M3) != 0 + }, + func(s *testStruct) bool { + if s == nil { + return false + } + return len(s.M4) != 0 + }, +} + +func TestUnionRatcheting(t *testing.T) { + testCases := []struct { + name string + oldStruct *testStruct + newStruct *testStruct + expected field.ErrorList + }{ + { + name: "old nil - no ratcheting", + oldStruct: nil, + newStruct: &testStruct{}, + expected: field.ErrorList{ + field.Invalid(nil, "", "must specify one of: `m1`, `m2`, `m3`, `m4`"), + }.WithOrigin("union"), + }, + { + name: "both empty struct", + oldStruct: &testStruct{}, + newStruct: &testStruct{}, + }, + { + name: "both have more than one member", + oldStruct: &testStruct{ + M1: &m1{}, + M2: &m2{}, + }, + newStruct: &testStruct{ + M1: &m1{}, + M2: &m2{}, + }, + }, + { + name: "change to invalid", + oldStruct: &testStruct{ + M1: &m1{}, + }, + newStruct: &testStruct{ + M1: &m1{}, + M2: &m2{}, + }, + expected: field.ErrorList{ + field.Invalid(nil, "{m1, m2}", "must specify exactly one of: `m1`, `m2`, `m3`, `m4`"), + }.WithOrigin("union"), + }, + { + name: "slice member ratcheting: unchanged membership", + oldStruct: &testStruct{M3: []string{"a"}}, + newStruct: &testStruct{M3: []string{"b"}}, + }, + { + name: "map member ratcheting: unchanged membership", + oldStruct: &testStruct{M4: map[string]string{"k": "v1"}}, + newStruct: &testStruct{M4: map[string]string{"k": "v2"}}, + }, + { + name: "empty slice is not set", + oldStruct: nil, + newStruct: &testStruct{M3: []string{}}, + expected: field.ErrorList{ + field.Invalid(nil, "", "must specify one of: `m1`, `m2`, `m3`, `m4`"), + }.WithOrigin("union"), + }, + { + name: "empty map is not set", + oldStruct: nil, + newStruct: &testStruct{M4: map[string]string{}}, + expected: field.ErrorList{ + field.Invalid(nil, "", "must specify one of: `m1`, `m2`, `m3`, `m4`"), + }.WithOrigin("union"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + members := []UnionMember{NewUnionMember("m1"), NewUnionMember("m2"), NewUnionMember("m3"), NewUnionMember("m4")} + got := Union(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, + NewUnionMembership(members...), extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + } +} + +type testDiscriminatedStruct struct { + D string `json:"d"` + M1 *m1 `json:"m1"` + M2 *m2 `json:"m2"` + M3 []string `json:"m3"` + M4 map[string]string `json:"m4"` +} + +var testDiscriminatorExtractor = func(s *testDiscriminatedStruct) string { + if s != nil { + return s.D + } + return "" +} +var testDiscriminatedExtractors = []ExtractorFn[*testDiscriminatedStruct, bool]{ + func(s *testDiscriminatedStruct) bool { + if s == nil { + return false + } + return s.M1 != nil + }, + func(s *testDiscriminatedStruct) bool { + if s == nil { + return false + } + return s.M2 != nil + }, + func(s *testDiscriminatedStruct) bool { + if s == nil { + return false + } + return len(s.M3) != 0 + }, + func(s *testDiscriminatedStruct) bool { + if s == nil { + return false + } + return len(s.M4) != 0 + }, +} + +func TestDiscriminatedUnionRatcheting(t *testing.T) { + testCases := []struct { + name string + oldStruct *testDiscriminatedStruct + newStruct *testDiscriminatedStruct + expected field.ErrorList + }{ + { + name: "pass with both nil", + }, + { + name: "pass with both empty struct", + oldStruct: &testDiscriminatedStruct{}, + newStruct: &testDiscriminatedStruct{}, + }, + { + name: "pass with both not set to member that is specified", + oldStruct: &testDiscriminatedStruct{ + D: "m1", + M2: &m2{}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m1", + M2: &m2{}, + }, + }, + { + name: "pass with both set to more than one member", + oldStruct: &testDiscriminatedStruct{ + D: "m1", + M1: &m1{}, + M2: &m2{}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m1", + M1: &m1{}, + M2: &m2{}, + }, + }, + { + name: "fail on changing to invalid with both set", + oldStruct: &testDiscriminatedStruct{ + D: "m1", + M1: &m1{}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m1", + M1: &m1{}, + M2: &m2{}, + }, + expected: field.ErrorList{ + field.Invalid(field.NewPath("m2"), "", "may only be specified when `d` is \"m2\""), + }.WithOrigin("union"), + }, + { + name: "fail on changing the discriminator", + oldStruct: &testDiscriminatedStruct{ + D: "m1", + M1: &m1{}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m2", + M1: &m1{}, + }, + expected: field.ErrorList{ + field.Invalid(field.NewPath("m1"), "", "may only be specified when `d` is \"m1\""), + field.Invalid(field.NewPath("m2"), "", "must be specified when `d` is \"m2\""), + }.WithOrigin("union"), + }, + { + name: "slice member ratcheting: unchanged membership", + oldStruct: &testDiscriminatedStruct{ + D: "m3", + M3: []string{"a"}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m3", + M3: []string{"b"}, + }, + }, + { + name: "map member ratcheting: unchanged membership", + oldStruct: &testDiscriminatedStruct{ + D: "m4", + M4: map[string]string{"k": "v1"}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m4", + M4: map[string]string{"k": "v2"}, + }, + }, + { + name: "empty slice is not set", + oldStruct: &testDiscriminatedStruct{ + D: "m3", + M3: []string{"a"}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m3", + M3: []string{}, + }, + expected: field.ErrorList{ + field.Invalid(field.NewPath("m3"), "", "must be specified when `d` is \"m3\""), + }.WithOrigin("union"), + }, + { + name: "empty map is not set", + oldStruct: &testDiscriminatedStruct{ + D: "m4", + M4: map[string]string{"k": "v"}, + }, + newStruct: &testDiscriminatedStruct{ + D: "m4", + M4: map[string]string{}, + }, + expected: field.ErrorList{ + field.Invalid(field.NewPath("m4"), "", "must be specified when `d` is \"m4\""), + }.WithOrigin("union"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + members := []UnionMember{NewDiscriminatedUnionMember("m1", "m1"), NewDiscriminatedUnionMember("m2", "m2"), NewDiscriminatedUnionMember("m3", "m3"), NewDiscriminatedUnionMember("m4", "m4")} + got := DiscriminatedUnion(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, + NewDiscriminatedUnionMembership("d", members...), testDiscriminatorExtractor, testDiscriminatedExtractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/update.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/update.go new file mode 100644 index 0000000000..cf4db5b529 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/update.go @@ -0,0 +1,289 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "fmt" + "slices" + + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// UpdateConstraint represents a constraint on update operations +type UpdateConstraint int + +const ( + // NoSet prevents unset->set transitions + NoSet UpdateConstraint = iota + // NoUnset prevents set->unset transitions + NoUnset + // NoModify prevents value changes but allows set/unset transitions + NoModify + // NoAddItem prevents adding items to a slice or map + NoAddItem + // NoRemoveItem prevents removing items from a slice or map + NoRemoveItem +) + +// UpdateValue verifies update constraints for value types. +func UpdateValue[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, equiv MatchFunc[T], constraints ...UpdateConstraint) field.ErrorList { + // nil oldValue means no prior value to compare against (eg: a new item at +k8s:eachVal scope) -> no transition to check. + if op.Type != operation.Update || oldValue == nil { + return nil + } + + var errs field.ErrorList + var zero T + valueIsZero := equiv(*value, zero) + oldValueIsZero := equiv(*oldValue, zero) + + for _, constraint := range constraints { + switch constraint { + case NoSet: + if oldValueIsZero && !valueIsZero { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update")) + } + case NoUnset: + if !oldValueIsZero && valueIsZero { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update")) + } + case NoModify: + // Rely on validation ratcheting to detect that the value has changed. + // This check only verifies that the field was set in both the old and + // new objects, confirming it was a modification, not a set/unset. + if !oldValueIsZero && !valueIsZero { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update")) + } + } + } + + return errs +} + +// UpdateValueByCompare verifies update constraints for comparable value types. +// +// Deprecated: Use UpdateValue instead. +func UpdateValueByCompare[T comparable](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList { + return UpdateValue(ctx, op, fldPath, value, oldValue, func(a, b T) bool { return a == b }, constraints...) +} + +// UpdatePointer verifies update constraints for pointer types. +func UpdatePointer[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList { + if op.Type != operation.Update { + return nil + } + + var errs field.ErrorList + + for _, constraint := range constraints { + switch constraint { + case NoSet: + if oldValue == nil && value != nil { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update")) + } + case NoUnset: + if oldValue != nil && value == nil { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update")) + } + case NoModify: + // Rely on validation ratcheting to detect that the value has changed. + // This check only verifies that the field was non-nil in both the old + // and new objects, confirming it was a modification, not a set/unset. + if oldValue != nil && value != nil { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update")) + } + } + } + + return errs +} + +// UpdateValueByReflect verifies update constraints for non-comparable value types using reflection. +// +// Deprecated: Use UpdateValue instead. +func UpdateValueByReflect[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList { + return UpdateValue(ctx, op, fldPath, value, oldValue, func(a, b T) bool { return equality.Semantic.DeepEqual(a, b) }, constraints...) +} + +// UpdateStruct verifies update constraints for non-pointer struct types. +// Non-pointer structs are always considered "set" and never "unset". +func UpdateStruct[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList { + // nil oldValue means no prior value to compare against (eg: a new item at +k8s:eachVal scope) -> no transition to check. + if op.Type != operation.Update || oldValue == nil { + return nil + } + + var errs field.ErrorList + + for _, constraint := range constraints { + switch constraint { + case NoSet, NoUnset: + // These constraints don't apply to non-pointer structs + // as they can't be unset. This should be caught at generation time. + continue + case NoModify: + // Non-pointer structs are always considered "set". Therefore, any + // change detected by validation ratcheting is a modification. + // The deep equality check is redundant and has been removed. + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update")) + } + } + + return errs +} + +// ValSliceUpdate verifies update constraints for slices of values. +// NoAddItem and NoRemoveItem use the match function to find corresponding +// elements between value and oldValue. NoSet and NoUnset treat len == 0 as +// "unset". +// +// The match function will never be called with nil arguments. +func ValSliceUpdate[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue []T, match MatchFunc[*T], constraints ...UpdateConstraint) field.ErrorList { + if op.Type != operation.Update { + return nil + } + + if match == nil && (slices.Contains(constraints, NoAddItem) || slices.Contains(constraints, NoRemoveItem)) { + return field.ErrorList{field.InternalError(fldPath, fmt.Errorf("ValSliceUpdate: NoAddItem/NoRemoveItem require a non-nil match function"))} + } + + var errs field.ErrorList + + for _, constraint := range constraints { + switch constraint { + case NoSet: + if len(oldValue) == 0 && len(value) > 0 { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update")) + } + case NoUnset: + if len(oldValue) > 0 && len(value) == 0 { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update")) + } + case NoAddItem: + for i := range value { + newItem := &value[i] + if lookup(oldValue, newItem, match) == nil { + errs = append(errs, field.Forbidden(fldPath.Index(i), "item may not be added").WithOrigin("update")) + } + } + case NoRemoveItem: + for i := range oldValue { + oldItem := &oldValue[i] + if lookup(value, oldItem, match) == nil { + errs = append(errs, field.Forbidden(fldPath, "item may not be removed").WithOrigin("update")) + } + } + } + } + + return errs +} + +// PtrSliceUpdate verifies update constraints for slices of pointers. +// NoAddItem and NoRemoveItem use the match function to find corresponding +// elements between value and oldValue. NoSet and NoUnset treat len == 0 as +// "unset". +// +// The match function will never be called with nil arguments. +func PtrSliceUpdate[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue []*T, match MatchFunc[*T], constraints ...UpdateConstraint) field.ErrorList { + if op.Type != operation.Update { + return nil + } + + if match == nil && (slices.Contains(constraints, NoAddItem) || slices.Contains(constraints, NoRemoveItem)) { + return field.ErrorList{field.InternalError(fldPath, fmt.Errorf("PtrSliceUpdate: NoAddItem/NoRemoveItem require a non-nil match function"))} + } + + var errs field.ErrorList + + for _, constraint := range constraints { + switch constraint { + case NoSet: + if len(oldValue) == 0 && len(value) > 0 { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update")) + } + case NoUnset: + if len(oldValue) > 0 && len(value) == 0 { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update")) + } + case NoAddItem: + for i := range value { + newItem := value[i] + if newItem == nil { + // Ignore nil items; they are supposed to have been checked by PtrSliceNoNils. + continue + } + if lookupPointer(oldValue, newItem, match) == nil { + errs = append(errs, field.Forbidden(fldPath.Index(i), "item may not be added").WithOrigin("update")) + } + } + case NoRemoveItem: + for i := range oldValue { + oldItem := oldValue[i] + if oldItem == nil { + continue + } + if lookupPointer(value, oldItem, match) == nil { + errs = append(errs, field.Forbidden(fldPath, "item may not be removed").WithOrigin("update")) + } + } + } + } + + return errs +} + +// UpdateMap verifies update constraints for map types. +// NoAddItem and NoRemoveItem compare keys between value and oldValue. NoSet +// and NoUnset treat len == 0 as "unset". +func UpdateMap[K comparable, V any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue map[K]V, constraints ...UpdateConstraint) field.ErrorList { + if op.Type != operation.Update { + return nil + } + + var errs field.ErrorList + + for _, constraint := range constraints { + switch constraint { + case NoSet: + if len(oldValue) == 0 && len(value) > 0 { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update")) + } + case NoUnset: + if len(oldValue) > 0 && len(value) == 0 { + errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update")) + } + case NoAddItem: + for k := range value { + if _, ok := oldValue[k]; !ok { + errs = append(errs, field.Forbidden(fldPath.Key(fmt.Sprintf("%v", k)), "item may not be added").WithOrigin("update")) + } + } + case NoRemoveItem: + for k := range oldValue { + if _, ok := value[k]; !ok { + errs = append(errs, field.Forbidden(fldPath.Key(fmt.Sprintf("%v", k)), "item may not be removed").WithOrigin("update")) + } + } + } + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/update_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/update_test.go new file mode 100644 index 0000000000..1978ced8d7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/update_test.go @@ -0,0 +1,928 @@ +/* +Copyright 2025 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 validate + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestUpdateValue(t *testing.T) { + tests := []struct { + name string + op operation.Type + value string + oldValue string + constraints []UpdateConstraint + wantErrs int + wantMsgs []string + }{ + { + name: "create operation - no validation", + op: operation.Create, + value: "value", + oldValue: "", + constraints: []UpdateConstraint{NoSet, NoUnset, NoModify}, + wantErrs: 0, + }, + { + name: "NoSet - unset to set transition (forbidden)", + op: operation.Update, + value: "value", + oldValue: "", + constraints: []UpdateConstraint{NoSet}, + wantErrs: 1, + wantMsgs: []string{"field cannot be set once created"}, + }, + { + name: "NoSet - set to set transition (allowed)", + op: operation.Update, + value: "value2", + oldValue: "value1", + constraints: []UpdateConstraint{NoSet}, + wantErrs: 0, + }, + { + name: "NoUnset - set to unset transition (forbidden)", + op: operation.Update, + value: "", + oldValue: "value", + constraints: []UpdateConstraint{NoUnset}, + wantErrs: 1, + wantMsgs: []string{"field cannot be cleared once set"}, + }, + { + name: "NoUnset - unset to set transition (allowed)", + op: operation.Update, + value: "value", + oldValue: "", + constraints: []UpdateConstraint{NoUnset}, + wantErrs: 0, + }, + { + name: "NoModify - set to different value (forbidden)", + op: operation.Update, + value: "value2", + oldValue: "value1", + constraints: []UpdateConstraint{NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + { + name: "NoModify - unset to set transition (allowed)", + op: operation.Update, + value: "value", + oldValue: "", + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "NoModify - set to unset transition (allowed)", + op: operation.Update, + value: "", + oldValue: "value", + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "Multiple constraints - NoSet and NoUnset", + op: operation.Update, + value: "value", + oldValue: "", + constraints: []UpdateConstraint{NoSet, NoUnset}, + wantErrs: 1, + wantMsgs: []string{"field cannot be set once created"}, + }, + { + name: "Multiple constraints - NoUnset and NoModify", + op: operation.Update, + value: "", + oldValue: "value", + constraints: []UpdateConstraint{NoUnset, NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be cleared once set"}, + }, + { + name: "Multiple constraints - NoSet, NoUnset, NoModify - modify attempt", + op: operation.Update, + value: "value2", + oldValue: "value1", + constraints: []UpdateConstraint{NoSet, NoUnset, NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + { + name: "No constraints", + op: operation.Update, + value: "value2", + oldValue: "value1", + constraints: []UpdateConstraint{}, + wantErrs: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := UpdateValue(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, func(a, b string) bool { return a == b }, tt.constraints...) + if len(errs) != tt.wantErrs { + t.Errorf("UpdateValue() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs) + } + for i, msg := range tt.wantMsgs { + if i >= len(errs) { + t.Errorf("Expected error message %q not found", msg) + continue + } + if errs[i].Detail != msg { + t.Errorf("UpdateValue() error message = %q, want %q", errs[i].Detail, msg) + } + } + }) + } +} + +func TestUpdateValueByCompare(t *testing.T) { + tests := []struct { + name string + op operation.Type + value string + oldValue string + constraints []UpdateConstraint + wantErrs int + wantMsgs []string + }{ + { + name: "NoSet - unset to set transition (forbidden)", + op: operation.Update, + value: "value", + oldValue: "", + constraints: []UpdateConstraint{NoSet}, + wantErrs: 1, + wantMsgs: []string{"field cannot be set once created"}, + }, + { + name: "NoUnset - set to unset transition (forbidden)", + op: operation.Update, + value: "", + oldValue: "value", + constraints: []UpdateConstraint{NoUnset}, + wantErrs: 1, + wantMsgs: []string{"field cannot be cleared once set"}, + }, + { + name: "NoModify - set to different value (forbidden)", + op: operation.Update, + value: "value2", + oldValue: "value1", + constraints: []UpdateConstraint{NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := UpdateValueByCompare(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, tt.constraints...) + if len(errs) != tt.wantErrs { + t.Errorf("UpdateValueByCompare() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs) + } + for i, msg := range tt.wantMsgs { + if i >= len(errs) { + t.Errorf("Expected error message %q not found", msg) + continue + } + if errs[i].Detail != msg { + t.Errorf("UpdateValueByCompare() error message = %q, want %q", errs[i].Detail, msg) + } + } + }) + } +} + +func TestUpdateValue_CustomMatchFunc(t *testing.T) { + type NonComparableStruct struct { + Name string + Tags []string + } + + customEquiv := func(a, b NonComparableStruct) bool { + return a.Name == b.Name && (len(a.Tags) == 0 && len(b.Tags) == 0 || (len(a.Tags) == len(b.Tags) && a.Tags[0] == b.Tags[0])) + } + + tests := []struct { + name string + op operation.Type + value NonComparableStruct + oldValue NonComparableStruct + constraints []UpdateConstraint + wantErrs int + wantMsgs []string + }{ + { + name: "NoSet - unset to set (forbidden)", + op: operation.Update, + value: NonComparableStruct{Name: "foo", Tags: []string{"a"}}, + oldValue: NonComparableStruct{}, + constraints: []UpdateConstraint{NoSet}, + wantErrs: 1, + wantMsgs: []string{"field cannot be set once created"}, + }, + { + name: "NoUnset - set to unset (forbidden)", + op: operation.Update, + value: NonComparableStruct{}, + oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}}, + constraints: []UpdateConstraint{NoUnset}, + wantErrs: 1, + wantMsgs: []string{"field cannot be cleared once set"}, + }, + { + name: "NoModify - zero to non-zero transition (allowed)", + op: operation.Update, + value: NonComparableStruct{Name: "foo", Tags: []string{"a"}}, + oldValue: NonComparableStruct{}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "NoModify - non-zero to zero transition (allowed)", + op: operation.Update, + value: NonComparableStruct{}, + oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "NoModify - non-zero to non-zero transition (forbidden)", + op: operation.Update, + value: NonComparableStruct{Name: "foo", Tags: []string{"b"}}, + oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := UpdateValue(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, customEquiv, tt.constraints...) + if len(errs) != tt.wantErrs { + t.Errorf("UpdateValue() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs) + } + for i, msg := range tt.wantMsgs { + if i >= len(errs) { + t.Errorf("Expected error message %q not found", msg) + continue + } + if errs[i].Detail != msg { + t.Errorf("UpdateValue() error message = %q, want %q", errs[i].Detail, msg) + } + } + }) + } +} + +func TestUpdatePointer(t *testing.T) { + stringPtr := func(s string) *string { return &s } + + tests := []struct { + name string + op operation.Type + value *string + oldValue *string + constraints []UpdateConstraint + wantErrs int + wantMsgs []string + }{ + { + name: "create operation - no validation", + op: operation.Create, + value: stringPtr("value"), + oldValue: nil, + constraints: []UpdateConstraint{NoSet, NoUnset, NoModify}, + wantErrs: 0, + }, + { + name: "NoSet - nil to non-nil transition (forbidden)", + op: operation.Update, + value: stringPtr("value"), + oldValue: nil, + constraints: []UpdateConstraint{NoSet}, + wantErrs: 1, + wantMsgs: []string{"field cannot be set once created"}, + }, + { + name: "NoSet - non-nil to non-nil transition (allowed)", + op: operation.Update, + value: stringPtr("value2"), + oldValue: stringPtr("value1"), + constraints: []UpdateConstraint{NoSet}, + wantErrs: 0, + }, + { + name: "NoUnset - non-nil to nil transition (forbidden)", + op: operation.Update, + value: nil, + oldValue: stringPtr("value"), + constraints: []UpdateConstraint{NoUnset}, + wantErrs: 1, + wantMsgs: []string{"field cannot be cleared once set"}, + }, + { + name: "NoUnset - nil to non-nil transition (allowed)", + op: operation.Update, + value: stringPtr("value"), + oldValue: nil, + constraints: []UpdateConstraint{NoUnset}, + wantErrs: 0, + }, + { + name: "NoModify - different values (forbidden)", + op: operation.Update, + value: stringPtr("value2"), + oldValue: stringPtr("value1"), + constraints: []UpdateConstraint{NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + { + name: "NoModify - nil to non-nil transition (allowed)", + op: operation.Update, + value: stringPtr("value"), + oldValue: nil, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "NoModify - non-nil to nil transition (allowed)", + op: operation.Update, + value: nil, + oldValue: stringPtr("value"), + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "Multiple constraints - all three", + op: operation.Update, + value: stringPtr("value2"), + oldValue: stringPtr("value1"), + constraints: []UpdateConstraint{NoSet, NoUnset, NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := UpdatePointer(context.TODO(), op, field.NewPath("test"), tt.value, tt.oldValue, tt.constraints...) + if len(errs) != tt.wantErrs { + t.Errorf("UpdatePointer() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs) + } + for i, msg := range tt.wantMsgs { + if i >= len(errs) { + t.Errorf("Expected error message %q not found", msg) + continue + } + if errs[i].Detail != msg { + t.Errorf("UpdatePointer() error message = %q, want %q", errs[i].Detail, msg) + } + } + }) + } +} + +func TestUpdateValueByReflect(t *testing.T) { + type CustomStruct struct { + Field1 string + Field2 int + } + + tests := []struct { + name string + op operation.Type + value CustomStruct + oldValue CustomStruct + constraints []UpdateConstraint + wantErrs int + wantMsgs []string + }{ + { + name: "NoModify - zero to non-zero transition (allowed)", + op: operation.Update, + value: CustomStruct{Field1: "test", Field2: 42}, + oldValue: CustomStruct{}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "NoModify - non-zero to zero transition (allowed)", + op: operation.Update, + value: CustomStruct{}, + oldValue: CustomStruct{Field1: "test", Field2: 42}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "NoModify - different values (forbidden)", + op: operation.Update, + value: CustomStruct{Field1: "test2", Field2: 100}, + oldValue: CustomStruct{Field1: "test1", Field2: 42}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + { + name: "NoSet - zero to non-zero (forbidden)", + op: operation.Update, + value: CustomStruct{Field1: "test", Field2: 42}, + oldValue: CustomStruct{}, + constraints: []UpdateConstraint{NoSet}, + wantErrs: 1, + wantMsgs: []string{"field cannot be set once created"}, + }, + { + name: "NoUnset - non-zero to zero (forbidden)", + op: operation.Update, + value: CustomStruct{}, + oldValue: CustomStruct{Field1: "test", Field2: 42}, + constraints: []UpdateConstraint{NoUnset}, + wantErrs: 1, + wantMsgs: []string{"field cannot be cleared once set"}, + }, + { + name: "Multiple constraints", + op: operation.Update, + value: CustomStruct{Field1: "test", Field2: 42}, + oldValue: CustomStruct{}, + constraints: []UpdateConstraint{NoSet, NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be set once created"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := UpdateValueByReflect(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, tt.constraints...) + if len(errs) != tt.wantErrs { + t.Errorf("UpdateValueByReflect() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs) + } + for i, msg := range tt.wantMsgs { + if i >= len(errs) { + t.Errorf("Expected error message %q not found", msg) + continue + } + if errs[i].Detail != msg { + t.Errorf("UpdateValueByReflect() error message = %q, want %q", errs[i].Detail, msg) + } + } + }) + } +} + +func TestUpdateStruct(t *testing.T) { + type TestStruct struct { + Field1 string + Field2 int + } + + tests := []struct { + name string + op operation.Type + value TestStruct + oldValue TestStruct + constraints []UpdateConstraint + wantErrs int + wantMsgs []string + }{ + { + name: "create operation - no validation", + op: operation.Create, + value: TestStruct{Field1: "test", Field2: 42}, + oldValue: TestStruct{}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 0, + }, + { + name: "NoModify - different values (forbidden)", + op: operation.Update, + value: TestStruct{Field1: "test2", Field2: 100}, + oldValue: TestStruct{Field1: "test1", Field2: 42}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + { + name: "NoModify - zero value to non-zero (forbidden)", + op: operation.Update, + value: TestStruct{Field1: "test", Field2: 42}, + oldValue: TestStruct{}, + constraints: []UpdateConstraint{NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + { + name: "NoSet and NoUnset with modification - only NoModify triggers", + op: operation.Update, + value: TestStruct{Field1: "test2", Field2: 100}, + oldValue: TestStruct{Field1: "test1", Field2: 42}, + constraints: []UpdateConstraint{NoSet, NoUnset, NoModify}, + wantErrs: 1, + wantMsgs: []string{"field cannot be modified once set"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := UpdateStruct(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, tt.constraints...) + if len(errs) != tt.wantErrs { + t.Errorf("UpdateStruct() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs) + } + for i, msg := range tt.wantMsgs { + if i >= len(errs) { + t.Errorf("Expected error message %q not found", msg) + continue + } + if errs[i].Detail != msg { + t.Errorf("UpdateStruct() error message = %q, want %q", errs[i].Detail, msg) + } + } + }) + } +} + +func TestValSliceUpdate(t *testing.T) { + type keyed struct { + Name string + Value string + } + keyMatch := func(a, b *keyed) bool { return a.Name == b.Name } + + tests := []struct { + name string + op operation.Type + value []string + oldValue []string + match MatchFunc[*string] + constraints []UpdateConstraint + wantDetails []string + }{ + { + name: "create operation - no validation", + op: operation.Create, + value: []string{"a"}, + oldValue: nil, + constraints: []UpdateConstraint{NoSet, NoUnset, NoAddItem, NoRemoveItem}, + match: DirectEqual[string], + }, + { + name: "NoSet nil to non-empty (forbidden)", + op: operation.Update, + value: []string{"a"}, + oldValue: nil, + constraints: []UpdateConstraint{NoSet}, + wantDetails: []string{"field cannot be set once created"}, + }, + { + name: "NoSet empty to non-empty (forbidden)", + op: operation.Update, + value: []string{"a"}, + oldValue: []string{}, + constraints: []UpdateConstraint{NoSet}, + wantDetails: []string{"field cannot be set once created"}, + }, + { + name: "NoSet non-empty to non-empty (allowed)", + op: operation.Update, + value: []string{"a", "b"}, + oldValue: []string{"a"}, + constraints: []UpdateConstraint{NoSet}, + }, + { + name: "NoUnset non-empty to nil (forbidden)", + op: operation.Update, + value: nil, + oldValue: []string{"a"}, + constraints: []UpdateConstraint{NoUnset}, + wantDetails: []string{"field cannot be cleared once set"}, + }, + { + name: "NoUnset non-empty to empty (forbidden)", + op: operation.Update, + value: []string{}, + oldValue: []string{"a"}, + constraints: []UpdateConstraint{NoUnset}, + wantDetails: []string{"field cannot be cleared once set"}, + }, + { + name: "NoAddItem direct-equal item added", + op: operation.Update, + value: []string{"a", "b", "c"}, + oldValue: []string{"a", "c"}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoAddItem}, + wantDetails: []string{"item may not be added"}, + }, + { + name: "NoAddItem direct-equal reorder allowed", + op: operation.Update, + value: []string{"c", "a", "b"}, + oldValue: []string{"a", "b", "c"}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoAddItem}, + }, + { + name: "NoRemoveItem direct-equal item removed", + op: operation.Update, + value: []string{"a"}, + oldValue: []string{"a", "b"}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoRemoveItem}, + wantDetails: []string{"item may not be removed"}, + }, + { + name: "NoAddItem + NoRemoveItem frozen shape (allowed)", + op: operation.Update, + value: []string{"a", "b"}, + oldValue: []string{"b", "a"}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoAddItem, NoRemoveItem}, + }, + { + name: "NoAddItem + NoRemoveItem one added one removed", + op: operation.Update, + value: []string{"a", "c"}, + oldValue: []string{"a", "b"}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoAddItem, NoRemoveItem}, + wantDetails: []string{ + "item may not be added", + "item may not be removed", + }, + }, + { + name: "NoAddItem + NoRemoveItem combined with NoUnset", + op: operation.Update, + value: nil, + oldValue: []string{"a"}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoUnset, NoAddItem, NoRemoveItem}, + wantDetails: []string{ + "field cannot be cleared once set", + "item may not be removed", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := ValSliceUpdate(context.TODO(), op, field.NewPath("test"), tt.value, tt.oldValue, tt.match, tt.constraints...) + if len(errs) != len(tt.wantDetails) { + t.Fatalf("ValSliceUpdate() returned %d errors, want %d: %v", len(errs), len(tt.wantDetails), errs) + } + for i, want := range tt.wantDetails { + if errs[i].Detail != want { + t.Errorf("ValSliceUpdate() error[%d] = %q, want %q", i, errs[i].Detail, want) + } + if errs[i].Origin != "update" { + t.Errorf("ValSliceUpdate() error[%d] origin = %q, want %q", i, errs[i].Origin, "update") + } + } + }) + } + + // Keyed-match case: matching by the Name field only. Modifying the Value + // of a keyed item should not trigger either NoAddItem or NoRemoveItem + // (that check belongs to eachVal + NoModify). + t.Run("NoAddItem+NoRemoveItem keyed match allows item modification", func(t *testing.T) { + op := operation.Operation{Type: operation.Update} + newList := []keyed{{Name: "alpha", Value: "v2"}, {Name: "beta", Value: "v1"}} + oldList := []keyed{{Name: "alpha", Value: "v1"}, {Name: "beta", Value: "v1"}} + errs := ValSliceUpdate(context.TODO(), op, field.NewPath("test"), newList, oldList, keyMatch, NoAddItem, NoRemoveItem) + if len(errs) != 0 { + t.Errorf("expected no errors for keyed-match item modification, got %v", errs) + } + }) + + t.Run("NoAddItem+NoRemoveItem keyed match reports add and remove by key", func(t *testing.T) { + op := operation.Operation{Type: operation.Update} + newList := []keyed{{Name: "alpha"}, {Name: "gamma"}} + oldList := []keyed{{Name: "alpha"}, {Name: "beta"}} + errs := ValSliceUpdate(context.TODO(), op, field.NewPath("test"), newList, oldList, keyMatch, NoAddItem, NoRemoveItem) + if len(errs) != 2 { + t.Fatalf("expected 2 errors, got %d: %v", len(errs), errs) + } + }) + + t.Run("NoAddItem without match returns internal error", func(t *testing.T) { + op := operation.Operation{Type: operation.Update} + errs := ValSliceUpdate[string](context.TODO(), op, field.NewPath("test"), []string{"a"}, nil, nil, NoAddItem) + if len(errs) != 1 || errs[0].Type != field.ErrorTypeInternal { + t.Errorf("expected single InternalError, got %v", errs) + } + }) +} + +func TestUpdateMap(t *testing.T) { + tests := []struct { + name string + op operation.Type + value map[string]string + oldValue map[string]string + constraints []UpdateConstraint + wantDetails []string + }{ + { + name: "create operation - no validation", + op: operation.Create, + value: map[string]string{"a": "1"}, + oldValue: nil, + constraints: []UpdateConstraint{NoSet, NoUnset, NoAddItem, NoRemoveItem}, + }, + { + name: "NoSet nil to non-empty (forbidden)", + op: operation.Update, + value: map[string]string{"a": "1"}, + oldValue: nil, + constraints: []UpdateConstraint{NoSet}, + wantDetails: []string{"field cannot be set once created"}, + }, + { + name: "NoUnset non-empty to empty (forbidden)", + op: operation.Update, + value: map[string]string{}, + oldValue: map[string]string{"a": "1"}, + constraints: []UpdateConstraint{NoUnset}, + wantDetails: []string{"field cannot be cleared once set"}, + }, + { + name: "NoAddItem key added", + op: operation.Update, + value: map[string]string{"a": "1", "b": "2"}, + oldValue: map[string]string{"a": "1"}, + constraints: []UpdateConstraint{NoAddItem}, + wantDetails: []string{"item may not be added"}, + }, + { + name: "NoAddItem value-only change allowed", + op: operation.Update, + value: map[string]string{"a": "2"}, + oldValue: map[string]string{"a": "1"}, + constraints: []UpdateConstraint{NoAddItem}, + }, + { + name: "NoRemoveItem key removed", + op: operation.Update, + value: map[string]string{"a": "1"}, + oldValue: map[string]string{"a": "1", "b": "2"}, + constraints: []UpdateConstraint{NoRemoveItem}, + wantDetails: []string{"item may not be removed"}, + }, + { + name: "NoAddItem + NoRemoveItem stable keys allowed", + op: operation.Update, + value: map[string]string{"a": "1", "b": "22"}, + oldValue: map[string]string{"a": "11", "b": "2"}, + constraints: []UpdateConstraint{NoAddItem, NoRemoveItem}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := UpdateMap(context.TODO(), op, field.NewPath("test"), tt.value, tt.oldValue, tt.constraints...) + if len(errs) != len(tt.wantDetails) { + t.Fatalf("UpdateMap() returned %d errors, want %d: %v", len(errs), len(tt.wantDetails), errs) + } + for i, want := range tt.wantDetails { + if errs[i].Detail != want { + t.Errorf("UpdateMap() error[%d] = %q, want %q", i, errs[i].Detail, want) + } + if errs[i].Origin != "update" { + t.Errorf("UpdateMap() error[%d] origin = %q, want %q", i, errs[i].Origin, "update") + } + } + }) + } +} + +func TestPtrSliceUpdate(t *testing.T) { + type keyed struct { + Name string + Value string + } + keyMatch := func(a, b *keyed) bool { return a.Name == b.Name } + + tests := []struct { + name string + op operation.Type + value []*string + oldValue []*string + match MatchFunc[*string] + constraints []UpdateConstraint + wantDetails []string + }{ + { + name: "create operation - no validation", + op: operation.Create, + value: []*string{new("a")}, + oldValue: nil, + constraints: []UpdateConstraint{NoSet, NoUnset, NoAddItem, NoRemoveItem}, + match: DirectEqual[string], + }, + { + name: "NoSet nil to non-empty (forbidden)", + op: operation.Update, + value: []*string{new("a")}, + oldValue: nil, + constraints: []UpdateConstraint{NoSet}, + wantDetails: []string{"field cannot be set once created"}, + }, + { + name: "NoAddItem direct-equal item added", + op: operation.Update, + value: []*string{new("a"), new("b"), new("c")}, + oldValue: []*string{new("a"), new("c")}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoAddItem}, + wantDetails: []string{"item may not be added"}, + }, + { + name: "NoRemoveItem direct-equal item removed", + op: operation.Update, + value: []*string{new("a")}, + oldValue: []*string{new("a"), new("b")}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoRemoveItem}, + wantDetails: []string{"item may not be removed"}, + }, + { + name: "nil element in value (ignored)", + op: operation.Update, + value: []*string{new("a"), nil, new("c")}, + oldValue: []*string{new("a"), new("c")}, + match: DirectEqual[string], + constraints: []UpdateConstraint{NoAddItem}, + // Expect 0 errors because nil is ignored and non-nil elements match old values. + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + op := operation.Operation{Type: tt.op} + errs := PtrSliceUpdate(context.TODO(), op, field.NewPath("test"), tt.value, tt.oldValue, tt.match, tt.constraints...) + + if len(errs) != len(tt.wantDetails) { + t.Fatalf("PtrSliceUpdate() returned %d errors, want %d: %v", len(errs), len(tt.wantDetails), errs) + } + for i, want := range tt.wantDetails { + if errs[i].Detail != want { + t.Errorf("PtrSliceUpdate() error[%d] = %q, want %q", i, errs[i].Detail, want) + } + if errs[i].Origin != "update" { + t.Errorf("PtrSliceUpdate() error[%d] origin = %q, want %q", i, errs[i].Origin, "update") + } + } + }) + } + + t.Run("NoAddItem+NoRemoveItem keyed match allows item modification", func(t *testing.T) { + op := operation.Operation{Type: operation.Update} + newList := []*keyed{{Name: "alpha", Value: "v2"}, {Name: "beta", Value: "v1"}} + oldList := []*keyed{{Name: "alpha", Value: "v1"}, {Name: "beta", Value: "v1"}} + errs := PtrSliceUpdate(context.TODO(), op, field.NewPath("test"), newList, oldList, keyMatch, NoAddItem, NoRemoveItem) + if len(errs) != 0 { + t.Errorf("expected no errors for keyed-match item modification, got %v", errs) + } + }) + + t.Run("NoAddItem without match returns internal error", func(t *testing.T) { + op := operation.Operation{Type: operation.Update} + errs := PtrSliceUpdate[string](context.TODO(), op, field.NewPath("test"), []*string{new("a")}, nil, nil, NoAddItem) + if len(errs) != 1 || errs[0].Type != field.ErrorTypeInternal { + t.Errorf("expected single InternalError, got %v", errs) + } + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/util_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/util_test.go new file mode 100644 index 0000000000..28bd577c8d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/util_test.go @@ -0,0 +1,41 @@ +/* +Copyright 2024 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 validate + +import ( + "bytes" + "strconv" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// fmtErrs is a helper for nicer test output. It will use multiple lines if +// errs has more than 1 item. +func fmtErrs(errs field.ErrorList) string { + if len(errs) == 0 { + return "" + } + if len(errs) == 1 { + return strconv.Quote(errs[0].Error()) + } + buf := bytes.Buffer{} + for _, e := range errs { + buf.WriteString("\n") + buf.WriteString(strconv.Quote(e.Error())) + } + return buf.String() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/zeroorone.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/zeroorone.go new file mode 100644 index 0000000000..6a5df4ca34 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/zeroorone.go @@ -0,0 +1,59 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ZeroOrOneOfUnion verifies that at most one member of a union is specified. +// +// ZeroOrOneOfMembership must define all the members of the union. +// +// For example: +// +// var ZeroOrOneOfMembershipForABC = validate.NewUnionMembership( +// validate.NewUnionMember("a"), +// validate.NewUnionMember("b"), +// validate.NewUnionMember("c"), +// ) +// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) { +// errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, in, oldIn, +// ZeroOrOneOfMembershipForABC, +// func(in *ABC) bool { return in.A != nil }, +// func(in *ABC) bool { return in.B != ""}, +// func(in *ABC) bool { return in.C != 0 }, +// )...) +// return errs +// } +func ZeroOrOneOfUnion[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList { + options := UnionValidationOptions{ + ErrorForEmpty: nil, + ErrorForMultiple: func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error { + return field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(specifiedFields, ", ")), + fmt.Sprintf("must specify at most one of: %s", strings.Join(allFields, ", "))).WithOrigin("zeroOrOneOf") + }, + } + + errs := unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...) + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/zeroorone_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/zeroorone_test.go new file mode 100644 index 0000000000..686a1c11a6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validate/zeroorone_test.go @@ -0,0 +1,153 @@ +/* +Copyright 2024 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 validate + +import ( + "context" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestZeroOrOneOfUnion(t *testing.T) { + testCases := []struct { + name string + fields []string + fieldValues []bool + expected field.ErrorList + }{ + { + name: "one member set", + fields: []string{"a", "b", "c", "d"}, + fieldValues: []bool{false, false, false, true}, + expected: nil, + }, + { + name: "two members set", + fields: []string{"a", "b", "c", "d"}, + fieldValues: []bool{false, true, false, true}, + expected: field.ErrorList{field.Invalid(nil, "{b, d}", "must specify at most one of: `a`, `b`, `c`, `d`").WithOrigin("zeroOrOneOf")}, + }, + { + name: "all members set", + fields: []string{"a", "b", "c", "d"}, + fieldValues: []bool{true, true, true, true}, + expected: field.ErrorList{field.Invalid(nil, "{a, b, c, d}", "must specify at most one of: `a`, `b`, `c`, `d`").WithOrigin("zeroOrOneOf")}, + }, + { + name: "no member set - allowed for ZeroOrOneOf", + fields: []string{"a", "b", "c", "d"}, + fieldValues: []bool{false, false, false, false}, + expected: nil, // This is the key difference from Union + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + members := []UnionMember{} + for _, f := range tc.fields { + members = append(members, NewUnionMember(f)) + } + + // Create mock extractors that return predefined values instead of + // actually extracting from the object. + extractors := make([]ExtractorFn[*testMember, bool], len(tc.fieldValues)) + for i, val := range tc.fieldValues { + extractors[i] = func(_ *testMember) bool { return val } + } + + got := ZeroOrOneOfUnion(context.Background(), operation.Operation{}, nil, &testMember{}, nil, + NewUnionMembership(members...), extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + } +} + +func TestZeroOrOneOfUnionRatcheting(t *testing.T) { + testCases := []struct { + name string + oldStruct *testStruct + newStruct *testStruct + expected field.ErrorList + }{ + { + name: "both nil", + oldStruct: nil, + newStruct: nil, + }, + { + name: "both empty struct - allowed for ZeroOrOneOf", + oldStruct: &testStruct{}, + newStruct: &testStruct{}, + }, + { + name: "both have more than one member", + oldStruct: &testStruct{ + M1: &m1{}, + M2: &m2{}, + }, + newStruct: &testStruct{ + M1: &m1{}, + M2: &m2{}, + }, + }, + { + name: "change to invalid", + oldStruct: &testStruct{ + M1: &m1{}, + }, + newStruct: &testStruct{ + M1: &m1{}, + M2: &m2{}, + }, + expected: field.ErrorList{ + field.Invalid(nil, "{m1, m2}", "must specify at most one of: `m1`, `m2`, `m3`, `m4`").WithOrigin("zeroOrOneOf"), + }, + }, + { + name: "change from empty to one member - allowed", + oldStruct: &testStruct{}, + newStruct: &testStruct{ + M1: &m1{}, + }, + expected: nil, + }, + { + name: "change from one member to empty - allowed", + oldStruct: &testStruct{ + M1: &m1{}, + }, + newStruct: &testStruct{}, + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + members := []UnionMember{NewUnionMember("m1"), NewUnionMember("m2"), NewUnionMember("m3"), NewUnionMember("m4")} + got := ZeroOrOneOfUnion(context.Background(), operation.Operation{Type: operation.Update}, nil, tc.newStruct, tc.oldStruct, + NewUnionMembership(members...), extractors...) + if !reflect.DeepEqual(got, tc.expected) { + t.Errorf("got %v want %v", got, tc.expected) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/OWNERS new file mode 100644 index 0000000000..4023732476 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/OWNERS @@ -0,0 +1,11 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +# Disable inheritance as this is an api owners file +options: + no_parent_owners: true +approvers: + - api-approvers +reviewers: + - api-reviewers +labels: + - kind/api-change diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/doc.go new file mode 100644 index 0000000000..9e305b0b18 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2017 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 validation contains generic api type validation functions. +package validation diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/generic.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/generic.go new file mode 100644 index 0000000000..35ea723a0f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/generic.go @@ -0,0 +1,94 @@ +/* +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 validation + +import ( + "strings" + + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// IsNegativeErrorMsg is a error message for value must be greater than or equal to 0. +const IsNegativeErrorMsg string = `must be greater than or equal to 0` + +// ValidateNameFunc validates that the provided name is valid for a given resource type. +// Not all resources have the same validation rules for names. Prefix is true +// if the name will have a value appended to it. If the name is not valid, +// this returns a list of descriptions of individual characteristics of the +// value that were not valid. Otherwise this returns an empty list or nil. +type ValidateNameFunc func(name string, prefix bool) []string + +// ValidateNameFuncWithErrors validates that the provided name is valid for a +// given resource type. +// +// This is similar to ValidateNameFunc, except that it produces an ErrorList. +type ValidateNameFuncWithErrors func(fldPath *field.Path, name string) field.ErrorList + +// NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. +func NameIsDNSSubdomain(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1123Subdomain(name) +} + +// NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label. +func NameIsDNSLabel(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1123Label(name) +} + +// NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label. +func NameIsDNS1035Label(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1035Label(name) +} + +// ValidateNamespaceName can be used to check whether the given namespace name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateNamespaceName = NameIsDNSLabel + +// ValidateServiceAccountName can be used to check whether the given service account name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateServiceAccountName = NameIsDNSSubdomain + +// maskTrailingDash replaces the final character of a string with a subdomain safe +// value if it is a dash and if the length of this string is greater than 1. Note that +// this is used when a value could be appended to the string, see ValidateNameFunc +// for more info. +func maskTrailingDash(name string) string { + if len(name) > 1 && strings.HasSuffix(name, "-") { + return name[:len(name)-2] + "a" + } + return name +} + +// ValidateNonnegativeField validates that given value is not negative. +func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if value < 0 { + allErrs = append(allErrs, field.Invalid(fldPath, value, IsNegativeErrorMsg).WithOrigin("minimum")) + } + return allErrs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/generic_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/generic_test.go new file mode 100644 index 0000000000..a753cc2b22 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/generic_test.go @@ -0,0 +1,65 @@ +/* +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 validation + +import "testing" + +func TestMaskTrailingDash(t *testing.T) { + testCases := []struct { + beforeMasking string + expectedAfterMasking string + description string + }{ + { + beforeMasking: "", + expectedAfterMasking: "", + description: "empty string", + }, + { + beforeMasking: "-", + expectedAfterMasking: "-", + description: "only a single dash", + }, + { + beforeMasking: "-foo", + expectedAfterMasking: "-foo", + description: "has leading dash", + }, + { + beforeMasking: "-foo-", + expectedAfterMasking: "-foa", + description: "has both leading and trailing dashes", + }, + { + beforeMasking: "b-", + expectedAfterMasking: "a", + description: "has trailing dash", + }, + { + beforeMasking: "ab", + expectedAfterMasking: "ab", + description: "has neither leading nor trailing dashes", + }, + } + + for _, tc := range testCases { + afterMasking := maskTrailingDash(tc.beforeMasking) + if afterMasking != tc.expectedAfterMasking { + t.Errorf("error in test case: %s. expected: %s, actual: %s", tc.description, tc.expectedAfterMasking, afterMasking) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/objectmeta.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/objectmeta.go new file mode 100644 index 0000000000..ff86973fbd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/objectmeta.go @@ -0,0 +1,369 @@ +/* +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 validation + +import ( + "context" + "fmt" + "strings" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/api/validate" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// FieldImmutableErrorMsg is a error message for field is immutable. +const FieldImmutableErrorMsg string = `field is immutable` + +const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB + +// BannedOwners is a black list of object that are not allowed to be owners. +var BannedOwners = map[schema.GroupVersionKind]struct{}{ + {Group: "", Version: "v1", Kind: "Event"}: {}, +} + +// ValidateAnnotations validates that a set of annotations are correctly defined. +func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for k := range annotations { + // The rule is QualifiedName except that case doesn't matter, so convert to lowercase before checking. + for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) { + allErrs = append(allErrs, field.Invalid(fldPath, k, msg).WithOrigin("format=k8s-label-key")) + } + } + if err := ValidateAnnotationsSize(annotations); err != nil { + allErrs = append(allErrs, field.TooLong(fldPath, "" /*unused*/, TotalAnnotationSizeLimitB)) + } + return allErrs +} + +func ValidateAnnotationsSize(annotations map[string]string) error { + var totalSize int64 + for k, v := range annotations { + totalSize += (int64)(len(k)) + (int64)(len(v)) + } + if totalSize > (int64)(TotalAnnotationSizeLimitB) { + return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB) + } + return nil +} + +func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + gv, err := schema.ParseGroupVersion(ownerReference.APIVersion) + // gvk.Group is empty for the legacy group. + if len(ownerReference.APIVersion) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), "must not be empty").MarkCoveredByDeclarative()) + } else if err != nil || len(gv.Version) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "must be / or ")) + } + if len(ownerReference.Kind) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "must not be empty").MarkCoveredByDeclarative()) + } + if len(ownerReference.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must not be empty").MarkCoveredByDeclarative()) + } + if len(ownerReference.UID) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("uid"), "must not be empty").MarkCoveredByDeclarative()) + } + gvk := gv.WithKind(ownerReference.Kind) + if _, ok := BannedOwners[gvk]; ok { + allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk))) + } + return allErrs +} + +// ValidateOwnerReferences validates that a set of owner references are correctly defined. +func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + firstControllerName := "" + for idx, ref := range ownerReferences { + allErrs = append(allErrs, validateOwnerReference(ref, fldPath.Index(idx))...) + if ref.Controller != nil && *ref.Controller { + curControllerName := ref.Kind + "/" + ref.Name + if firstControllerName != "" { + allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences, + fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", firstControllerName, curControllerName))) + } else { + firstControllerName = curControllerName + } + } + } + return allErrs +} + +// ValidateFinalizerName validates finalizer names. +func ValidateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsQualifiedName(stringValue) { + allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg)) + } + + return allErrs +} + +// ValidateNoNewFinalizers validates the new finalizers has no new finalizers compare to old finalizers. +func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...)) + if len(extra) != 0 { + allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("no new finalizers can be added if the object is being deleted, found new finalizers %#v", extra.List()))) + } + return allErrs +} + +// ValidateImmutableField validates the new value and the old value are deeply equal. +func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if !apiequality.Semantic.DeepEqual(oldVal, newVal) { + allErrs = append(allErrs, field.Invalid(fldPath, newVal, FieldImmutableErrorMsg)) + } + return allErrs +} + +// ValidateObjectMetaDeclaratively validates an ObjectMeta instance declaratively and deduplicates handwritten errors. +// betaEnabled controls whether declarative validation rules at the Beta stability level are enforced. +// NOTE: This method should be used in the types for which declarative validation is not enabled yet or cannot be enabled. The types +// for which declarative validation is enabled and valdiation code is generated must use ValidateObjectMeta and ValidateObjectMetaUpdate. +func ValidateObjectMetaDeclaratively(ctx context.Context, op operation.Type, obj, oldObj *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path, betaEnabled bool) field.ErrorList { + var errs field.ErrorList + switch op { + case operation.Create: + errs = ValidateObjectMeta(obj, requiresNamespace, nameFn, fldPath) + case operation.Update: + errs = ValidateObjectMetaUpdate(obj, oldObj, fldPath) + } + dvErrs := v1validation.Validate_ObjectMeta(ctx, operation.Operation{Type: op}, fldPath, obj, oldObj) + enforcedDVErrs := validate.FilterEnforcedDeclarativeErrors(ctx, dvErrs, betaEnabled) + errs = errs.MarkFromImperative() + errs = validate.FilterCoveredHandwrittenErrors(ctx, errs, enforcedDVErrs, betaEnabled) + return append(errs, enforcedDVErrs...) +} + +// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already +// been performed. +func ValidateObjectMeta(objMeta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { + metadata, err := meta.Accessor(objMeta) + if err != nil { + var allErrs field.ErrorList + allErrs = append(allErrs, field.Invalid(fldPath, objMeta, err.Error())) + return allErrs + } + return ValidateObjectMetaAccessor(metadata, requiresNamespace, nameFn, fldPath) +} + +// objectMetaValidationOptions defines behavioral modifications for validating +// an ObjectMeta. +type objectMetaValidationOptions struct { + /* nothing here yet */ +} + +// ObjectMetaValidationOption specifies a behavioral modifier for +// ValidateObjectMetaWithOpts and ValidateObjectMetaAccessorWithOpts. +type ObjectMetaValidationOption func(opts *objectMetaValidationOptions) + +// ValidateObjectMetaWithOpts validates an object's metadata on creation. It +// expects that name generation has already been performed, so name validation +// is always executed. +// +// This is similar to ValidateObjectMeta, but uses options to buy future-safety +// and uses different signature for the name validation function. It also does +// not directly validate the generateName field, because name generation +// should have already been performed and it is the result of that generastion +// that must conform to the nameFn. +func ValidateObjectMetaWithOpts(objMeta *metav1.ObjectMeta, isNamespaced bool, nameFn ValidateNameFuncWithErrors, fldPath *field.Path, options ...ObjectMetaValidationOption) field.ErrorList { + metadata, err := meta.Accessor(objMeta) + if err != nil { + var allErrs field.ErrorList + allErrs = append(allErrs, field.InternalError(fldPath, err)) + return allErrs + } + return ValidateObjectMetaAccessorWithOpts(metadata, isNamespaced, nameFn, fldPath, options...) +} + +// ValidateObjectMetaAccessor validates an object's metadata on creation. It expects that name generation has already +// been performed. +func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + if len(meta.GetGenerateName()) != 0 { + for _, msg := range nameFn(meta.GetGenerateName(), true) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GetGenerateName(), msg)) + } + } + // If the generated name validates, but the calculated value does not, it's a problem with generation, and we + // report it here. This may confuse users, but indicates a programming bug and still must be validated. + // If there are multiple fields out of which one is required then add an or as a separator + if len(meta.GetName()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required")) + } else { + for _, msg := range nameFn(meta.GetName(), false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.GetName(), msg)) + } + } + + return append(allErrs, validateObjectMetaAccessorWithOptsCommon(meta, requiresNamespace, fldPath, nil)...) +} + +// ValidateObjectMetaAccessorWithOpts validates an object's metadata on +// creation. It expects that name generation has already been performed, so +// name validation is always executed. +// +// This is similar to ValidateObjectMetaAccessor, but uses options to buy +// future-safety and uses different signature for the name validation function. +// It also does not directly validate the generateName field, because name +// generation should have already been performed and it is the result of that +// generastion that must conform to the nameFn. +func ValidateObjectMetaAccessorWithOpts(meta metav1.Object, isNamespaced bool, nameFn ValidateNameFuncWithErrors, fldPath *field.Path, options ...ObjectMetaValidationOption) field.ErrorList { + opts := objectMetaValidationOptions{} + for _, opt := range options { + opt(&opts) + } + + var allErrs field.ErrorList + + // generateName is not directly validated here. Types can have + // different rules for name generation, and the nameFn is for validating + // the post-generation data, not the input. In the past we assumed that + // name generation was always "append 5 random characters", but that's not + // NECESSARILY true. Also, the nameFn should always be considering the max + // length of the name, and it doesn't know enough about the name generation + // to do that. Also, given a bad generateName, the user will get errors + // for both the generateName and name fields. We will focus validation on + // the name field, which should give a better UX overall. + // TODO(thockin): should we do a max-length check here? e.g. 1K or 4K? + + if len(meta.GetGenerateName()) != 0 && len(meta.GetName()) == 0 { + allErrs = append(allErrs, + field.InternalError(fldPath.Child("name"), fmt.Errorf("generateName was specified (%q), but no name was generated", meta.GetGenerateName()))) + } + if len(meta.GetName()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required")) + } else { + allErrs = append(allErrs, nameFn(fldPath.Child("name"), meta.GetName())...) + } + + return append(allErrs, validateObjectMetaAccessorWithOptsCommon(meta, isNamespaced, fldPath, &opts)...) +} + +// validateObjectMetaAccessorWithOptsCommon is a shared function for validating +// the parts of an ObjectMeta with are handled the same in both paths.. +func validateObjectMetaAccessorWithOptsCommon(meta metav1.Object, isNamespaced bool, fldPath *field.Path, _ *objectMetaValidationOptions) field.ErrorList { + var allErrs field.ErrorList + + if isNamespaced { + if len(meta.GetNamespace()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) + } else { + for _, msg := range ValidateNamespaceName(meta.GetNamespace(), false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.GetNamespace(), msg)) + } + } + } else { + if len(meta.GetNamespace()) != 0 { + // TODO(thockin): change to "may not be specified on this type" or something + allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type")) + } + } + + allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation")).MarkCoveredByDeclarative()...) + allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...) + allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) + allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...) + allErrs = append(allErrs, v1validation.ValidateManagedFields(meta.GetManagedFields(), fldPath.Child("managedFields"), v1validation.CoveredByDeclarative)...) + return allErrs +} + +// ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers. +func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + hasFinalizerOrphanDependents := false + hasFinalizerDeleteDependents := false + for _, finalizer := range finalizers { + allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath)...) + if finalizer == metav1.FinalizerOrphanDependents { + hasFinalizerOrphanDependents = true + } + if finalizer == metav1.FinalizerDeleteDependents { + hasFinalizerDeleteDependents = true + } + } + if hasFinalizerDeleteDependents && hasFinalizerOrphanDependents { + allErrs = append(allErrs, field.Invalid(fldPath, finalizers, fmt.Sprintf("finalizer %s and %s cannot be both set", metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents))) + } + return allErrs +} + +// ValidateObjectMetaUpdate validates an object's metadata when updated. +func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { + newMetadata, err := meta.Accessor(newMeta) + if err != nil { + allErrs := field.ErrorList{} + allErrs = append(allErrs, field.Invalid(fldPath, newMeta, err.Error())) + return allErrs + } + oldMetadata, err := meta.Accessor(oldMeta) + if err != nil { + allErrs := field.ErrorList{} + allErrs = append(allErrs, field.Invalid(fldPath, oldMeta, err.Error())) + return allErrs + } + return ValidateObjectMetaAccessorUpdate(newMetadata, oldMetadata, fldPath) +} + +// ValidateObjectMetaAccessorUpdate validates an object's metadata when updated. +func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + // Finalizers cannot be added if the object is already being deleted. + if oldMeta.GetDeletionTimestamp() != nil { + allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.GetFinalizers(), oldMeta.GetFinalizers(), fldPath.Child("finalizers"))...) + } + + // Reject updates that don't specify a resource version + if len(newMeta.GetResourceVersion()) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.GetResourceVersion(), "must be specified for an update")) + } + + // Generation shouldn't be decremented + allErrs = append(allErrs, ValidateNonnegativeField(newMeta.GetGeneration(), fldPath.Child("generation")).MarkCoveredByDeclarative()...) + if newMeta.GetGeneration() < oldMeta.GetGeneration() { + allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented")) + } + + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid")).WithOrigin("immutable").MarkCoveredByDeclarative()...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetCreationTimestamp(), oldMeta.GetCreationTimestamp(), fldPath.Child("creationTimestamp")).WithOrigin("immutable").MarkCoveredByDeclarative()...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionTimestamp(), oldMeta.GetDeletionTimestamp(), fldPath.Child("deletionTimestamp")).WithOrigin("immutable").MarkCoveredByDeclarative()...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionGracePeriodSeconds(), oldMeta.GetDeletionGracePeriodSeconds(), fldPath.Child("deletionGracePeriodSeconds")).WithOrigin("immutable").MarkCoveredByDeclarative()...) + + allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.GetLabels(), fldPath.Child("labels"))...) + allErrs = append(allErrs, ValidateAnnotations(newMeta.GetAnnotations(), fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidateOwnerReferences(newMeta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) + allErrs = append(allErrs, v1validation.ValidateManagedFields(newMeta.GetManagedFields(), fldPath.Child("managedFields"), v1validation.CoveredByDeclarative)...) + + return allErrs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/objectmeta_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/objectmeta_test.go new file mode 100644 index 0000000000..ec329d1984 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/objectmeta_test.go @@ -0,0 +1,999 @@ +/* +Copyright 2017 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 validation + +import ( + "context" + "fmt" + "math/rand" + "reflect" + "strings" + "testing" + "time" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/api/validate" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +const ( + maxLengthErrMsg = "must be no more than" + namePartErrMsg = "name part must consist of" + nameErrMsg = "a valid label key must consist of" +) + +// Ensure custom name functions are allowed +func TestValidateObjectMetaCustomName(t *testing.T) { + testCases := []struct { + name string + input metav1.ObjectMeta + nErrs int + errStr string + }{{ + name: "valid name, empty generateName", + input: metav1.ObjectMeta{Name: "test", GenerateName: ""}, + }, { + name: "valid name and generateName", + input: metav1.ObjectMeta{Name: "test", GenerateName: "test"}, + }, { + name: "invalid name, empty generateName", + input: metav1.ObjectMeta{Name: "invalid", GenerateName: ""}, + nErrs: 1, + errStr: "wrong value", + }, { + name: "invalid name, valid generateName", + input: metav1.ObjectMeta{Name: "invalid", GenerateName: "test"}, + nErrs: 1, + errStr: "wrong value", + }, { + name: "invalid name, invalid generateName", + input: metav1.ObjectMeta{Name: "invalid", GenerateName: "invalid"}, + nErrs: 2, + errStr: "wrong value", + }} + + fn := func(s string, prefix bool) []string { + // Note: this is called on both name and generateName + if s == "test" { + return nil + } + return []string{"wrong value"} + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + errs := ValidateObjectMeta(&tc.input, false, fn, field.NewPath("field")) + + if len(errs) == 0 { + if len(tc.errStr) != 0 { + t.Fatalf("expected 1 error, got none") + } + } else { + if len(tc.errStr) == 0 { + t.Fatalf("expected no errors, got: %v", errs) + } + if len(errs) != tc.nErrs { + t.Fatalf("expected %d errors, got %d: %q", tc.nErrs, len(errs), errs) + } + if !strings.Contains(errs[0].Error(), "wrong value") { + t.Errorf("unexpected error message: %v", errs[0].Error()) + } + } + }) + } +} + +// Ensure custom name functions work +func TestValidateObjectMetaWithOptsName(t *testing.T) { + testCases := []struct { + name string + input metav1.ObjectMeta + errStr string + }{{ + name: "valid name, empty generateName", + input: metav1.ObjectMeta{Name: "test", GenerateName: ""}, + }, { + name: "valid name and generateName", + input: metav1.ObjectMeta{Name: "test", GenerateName: "test"}, + }, { + name: "invalid name, empty generateName", + input: metav1.ObjectMeta{Name: "invalid", GenerateName: ""}, + errStr: "wrong value", + }, { + name: "invalid name, valid generateName", + input: metav1.ObjectMeta{Name: "invalid", GenerateName: "test"}, + errStr: "wrong value", + }, { + name: "invalid name, invalid generateName", + input: metav1.ObjectMeta{Name: "invalid", GenerateName: "invalid"}, + errStr: "wrong value", + }} + + fn := func(fldPath *field.Path, s string) field.ErrorList { + if s == "test" { + return nil + } + return field.ErrorList{field.Invalid(fldPath, s, "wrong value")} + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + errs := ValidateObjectMetaWithOpts(&tc.input, false, fn, field.NewPath("field")) + + if len(errs) == 0 { + if len(tc.errStr) != 0 { + t.Fatalf("expected 1 error, got none") + } + } else { + if len(tc.errStr) == 0 { + t.Fatalf("expected no errors, got: %v", errs) + } + if len(errs) != 1 { + t.Fatalf("expected 1 error, got %d: %q", len(errs), errs) + } + if !strings.Contains(errs[0].Error(), "wrong value") { + t.Errorf("unexpected error message: %v", errs[0].Error()) + } + } + }) + } +} + +// Ensure namespace names follow dns label format +func TestValidateObjectMetaNamespaces(t *testing.T) { + errs := validateObjectMetaAccessorWithOptsCommon( + &metav1.ObjectMeta{Name: "test", Namespace: "foo.bar"}, + true, field.NewPath("field"), nil) + if len(errs) != 1 { + t.Fatalf("unexpected errors: %v", errs) + } + if !strings.Contains(errs[0].Error(), `Invalid value: "foo.bar"`) { + t.Errorf("unexpected error message: %v", errs) + } + maxLength := 63 + letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + b := make([]rune, maxLength+1) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + errs = validateObjectMetaAccessorWithOptsCommon( + &metav1.ObjectMeta{Name: "test", Namespace: string(b)}, + true, field.NewPath("field"), nil) + if len(errs) != 2 { + t.Fatalf("unexpected errors: %v", errs) + } + if !strings.Contains(errs[0].Error(), "Invalid value") || !strings.Contains(errs[1].Error(), "Invalid value") { + t.Errorf("unexpected error message: %v", errs) + } +} + +func TestValidateObjectMetaOwnerReferences(t *testing.T) { + trueVar := true + falseVar := false + testCases := []struct { + description string + ownerReferences []metav1.OwnerReference + expectError bool + expectedErrorMessage string + }{ + { + description: "simple success - third party extension.", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind", + Name: "name", + UID: "1", + }, + }, + expectError: false, + expectedErrorMessage: "", + }, + { + description: "simple failures - event shouldn't be set as an owner", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Event", + Name: "name", + UID: "1", + }, + }, + expectError: true, + expectedErrorMessage: "is disallowed from being an owner", + }, + { + description: "simple failures - invalid apiVersion with too many slashes", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "a/b/c", + Kind: "Pod", + Name: "name", + UID: "1", + }, + }, + expectError: true, + expectedErrorMessage: "must be / or ", + }, + { + description: "simple failures - invalid apiVersion with empty version", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "foo/", + Kind: "Pod", + Name: "name", + UID: "1", + }, + }, + expectError: true, + expectedErrorMessage: "must be / or ", + }, + { + description: "simple success - apiVersion with no slashes (legacy core group)", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Pod", + Name: "name", + UID: "1", + }, + }, + expectError: false, + expectedErrorMessage: "", + }, + { + description: "simple controller ref success - one reference with Controller set", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind", + Name: "name", + UID: "1", + Controller: &falseVar, + }, + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind", + Name: "name", + UID: "2", + Controller: &trueVar, + }, + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind", + Name: "name", + UID: "3", + Controller: &falseVar, + }, + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind", + Name: "name", + UID: "4", + }, + }, + expectError: false, + expectedErrorMessage: "", + }, + { + description: "simple controller ref failure - two references with Controller set", + ownerReferences: []metav1.OwnerReference{ + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind1", + Name: "name", + UID: "1", + Controller: &falseVar, + }, + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind2", + Name: "name", + UID: "2", + Controller: &trueVar, + }, + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind3", + Name: "name", + UID: "3", + Controller: &trueVar, + }, + { + APIVersion: "customresourceVersion", + Kind: "customresourceKind4", + Name: "name", + UID: "4", + }, + }, + expectError: true, + expectedErrorMessage: "Only one reference can have Controller set to true. Found \"true\" in references for customresourceKind2/name and customresourceKind3/name", + }, + } + + for _, tc := range testCases { + errs := validateObjectMetaAccessorWithOptsCommon( + &metav1.ObjectMeta{Name: "test", Namespace: "test", OwnerReferences: tc.ownerReferences}, + true, field.NewPath("field"), nil) + if len(errs) != 0 && !tc.expectError { + t.Errorf("unexpected error: %v in test case %v", errs, tc.description) + } + if len(errs) == 0 && tc.expectError { + t.Errorf("expect error in test case %v", tc.description) + } + if len(errs) != 0 && !strings.Contains(errs[0].Error(), tc.expectedErrorMessage) { + t.Errorf("unexpected error message: %v in test case %v", errs, tc.description) + } + } +} + +func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) { + if errs := ValidateObjectMetaUpdate( + &metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + &metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, + field.NewPath("field"), + ); len(errs) != 1 { + t.Fatalf("unexpected errors: %v", errs) + } + if errs := ValidateObjectMetaUpdate( + &metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, + &metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + field.NewPath("field"), + ); len(errs) != 1 { + t.Fatalf("unexpected errors: %v", errs) + } + if errs := ValidateObjectMetaUpdate( + &metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(10, 0))}, + &metav1.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: metav1.NewTime(time.Unix(11, 0))}, + field.NewPath("field"), + ); len(errs) != 1 { + t.Fatalf("unexpected errors: %v", errs) + } +} + +func TestValidateFinalizersUpdate(t *testing.T) { + testcases := map[string]struct { + Old metav1.ObjectMeta + New metav1.ObjectMeta + ExpectedErr string + }{ + "invalid adding finalizers": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}}, + ExpectedErr: "y/b", + }, + "invalid changing finalizers": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/b"}}, + ExpectedErr: "x/b", + }, + "valid removing finalizers": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a", "y/b"}}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"x/a"}}, + ExpectedErr: "", + }, + "valid adding finalizers for objects not being deleted": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a"}}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{"x/a", "y/b"}}, + ExpectedErr: "", + }, + } + for name, tc := range testcases { + errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) + if len(errs) == 0 { + if len(tc.ExpectedErr) != 0 { + t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr) + } + } else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) { + t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a) + } + } +} + +func TestValidateFinalizersPreventConflictingFinalizers(t *testing.T) { + testcases := map[string]struct { + ObjectMeta metav1.ObjectMeta + ExpectedErr string + }{ + "conflicting finalizers": { + ObjectMeta: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Finalizers: []string{metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents}}, + ExpectedErr: "cannot be both set", + }, + } + for name, tc := range testcases { + errs := validateObjectMetaAccessorWithOptsCommon(&tc.ObjectMeta, false, field.NewPath("field"), nil) + if len(errs) == 0 { + if len(tc.ExpectedErr) != 0 { + t.Errorf("case: %q, expected error to contain %q", name, tc.ExpectedErr) + } + } else if e, a := tc.ExpectedErr, errs.ToAggregate().Error(); !strings.Contains(a, e) { + t.Errorf("case: %q, expected error to contain %q, got error %q", name, e, a) + } + } +} + +func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) { + now := metav1.NewTime(time.Unix(1000, 0).UTC()) + later := metav1.NewTime(time.Unix(2000, 0).UTC()) + gracePeriodShort := int64(30) + gracePeriodLong := int64(40) + + testcases := map[string]struct { + Old metav1.ObjectMeta + New metav1.ObjectMeta + ExpectedNew metav1.ObjectMeta + ExpectedErrs []string + }{ + "valid without deletion fields": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedErrs: []string{}, + }, + "valid with deletion fields": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedErrs: []string{}, + }, + + "invalid set deletionTimestamp": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:16:40Z\": field is immutable"}, + }, + "invalid clear deletionTimestamp": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: null: field is immutable"}, + }, + "invalid change deletionTimestamp": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later}, + ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later}, + ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:33:20Z\": field is immutable"}, + }, + + "invalid set deletionGracePeriodSeconds": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 30: field is immutable"}, + }, + "invalid clear deletionGracePeriodSeconds": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"}, + ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: null: field is immutable"}, + }, + "invalid change deletionGracePeriodSeconds": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, + ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, + ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 40: field is immutable"}, + }, + } + + for k, tc := range testcases { + errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) + if len(errs) != len(tc.ExpectedErrs) { + t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) + t.Logf("%s: Got: %#v", k, errs) + t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) + continue + } + for i := range errs { + if errs[i].Error() != tc.ExpectedErrs[i] { + t.Errorf("%s: error #%d:\n expected: %q\n got: %q", k, i, tc.ExpectedErrs[i], errs[i].Error()) + } + } + if !reflect.DeepEqual(tc.New, tc.ExpectedNew) { + t.Errorf("%s: Expected after validation:\n%#v\ngot\n%#v", k, tc.ExpectedNew, tc.New) + } + } +} + +func TestObjectMetaGenerationUpdate(t *testing.T) { + testcases := map[string]struct { + Old metav1.ObjectMeta + New metav1.ObjectMeta + ExpectedErrs []string + }{ + "invalid generation change - decremented": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 4}, + ExpectedErrs: []string{"field.generation: Invalid value: 4: must not be decremented"}, + }, + "valid generation change - incremented by one": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 1}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 2}, + ExpectedErrs: []string{}, + }, + "valid generation field - not updated": { + Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, + New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", Generation: 5}, + ExpectedErrs: []string{}, + }, + } + + for k, tc := range testcases { + errList := []string{} + errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) + if len(errs) != len(tc.ExpectedErrs) { + t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) + for _, err := range errs { + errList = append(errList, err.Error()) + } + t.Logf("%s: Got: %#v", k, errList) + t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) + continue + } + for i := range errList { + if errList[i] != tc.ExpectedErrs[i] { + t.Errorf("%s: error #%d:\n expected: %q\n got: %q", k, i, tc.ExpectedErrs[i], errs[i].Error()) + } + } + } +} + +// Ensure trailing dash is allowed in generate name +func TestValidateObjectMetaTrimsTrailingDash(t *testing.T) { + errs := ValidateObjectMeta( + &metav1.ObjectMeta{Name: "test", GenerateName: "foo-"}, + false, + NameIsDNSSubdomain, + field.NewPath("field")) + if len(errs) != 0 { + t.Fatalf("unexpected errors: %v", errs) + } +} + +func TestValidateAnnotations(t *testing.T) { + successCases := []map[string]string{ + {"simple": "bar"}, + {"now-with-dashes": "bar"}, + {"1-starts-with-num": "bar"}, + {"1234": "bar"}, + {"simple/simple": "bar"}, + {"now-with-dashes/simple": "bar"}, + {"now-with-dashes/now-with-dashes": "bar"}, + {"now.with.dots/simple": "bar"}, + {"now-with.dashes-and.dots/simple": "bar"}, + {"1-num.2-num/3-num": "bar"}, + {"1234/5678": "bar"}, + {"1.2.3.4/5678": "bar"}, + {"UpperCase123": "bar"}, + {"a": strings.Repeat("b", TotalAnnotationSizeLimitB-1)}, + { + "a": strings.Repeat("b", TotalAnnotationSizeLimitB/2-1), + "c": strings.Repeat("d", TotalAnnotationSizeLimitB/2-1), + }, + } + for i := range successCases { + errs := ValidateAnnotations(successCases[i], field.NewPath("field")) + if len(errs) != 0 { + t.Errorf("case[%d] expected success, got %#v", i, errs) + } + } + + nameErrorCases := []struct { + annotations map[string]string + expect string + }{ + {map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg}, + {map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg}, + {map[string]string{"only/one/slash": "bar"}, nameErrMsg}, + {map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg}, + } + for i := range nameErrorCases { + errs := ValidateAnnotations(nameErrorCases[i].annotations, field.NewPath("field")) + if len(errs) != 1 { + t.Errorf("case[%d]: expected failure", i) + } else { + if !strings.Contains(errs[0].Detail, nameErrorCases[i].expect) { + t.Errorf("case[%d]: error details do not include %q: %q", i, nameErrorCases[i].expect, errs[0].Detail) + } + } + } + totalSizeErrorCases := []map[string]string{ + {"a": strings.Repeat("b", TotalAnnotationSizeLimitB)}, + { + "a": strings.Repeat("b", TotalAnnotationSizeLimitB/2), + "c": strings.Repeat("d", TotalAnnotationSizeLimitB/2), + }, + } + for i := range totalSizeErrorCases { + errs := ValidateAnnotations(totalSizeErrorCases[i], field.NewPath("field")) + if len(errs) != 1 { + t.Errorf("case[%d] expected failure", i) + } + } +} + +func TestValidateObjectMetaDeclaratively(t *testing.T) { + ctx := context.Background() + fldPath := field.NewPath("metadata") + now := metav1.NewTime(time.Unix(1000, 0).UTC()) + later := metav1.NewTime(time.Unix(2000, 0).UTC()) + gracePeriod30 := int64(30) + gracePeriod40 := int64(40) + + createCases := []struct { + name string + obj *metav1.ObjectMeta + requiresNamespace bool + expectedErrs field.ErrorList + }{ + { + name: "valid metadata", + obj: mkMeta(), + requiresNamespace: true, + expectedErrs: nil, + }, + { + name: "invalid name format", + obj: mkMeta(tweakName("invalid_name")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("name"), "invalid_name", "").MarkFromImperative(), + }, + }, + { + name: "missing required namespace", + obj: mkMeta(tweakNamespace("")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("namespace"), "").MarkFromImperative(), + }, + }, + { + name: "negative generation", + obj: mkMeta(tweakGeneration(-1)), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("generation"), int64(-1), "").WithOrigin("minimum").MarkAlpha(), + }, + }, + { + name: "managedFields empty operation", + obj: mkMeta(tweakManagedFields(metav1.ManagedFieldsEntry{FieldsType: "FieldsV1"})), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("managedFields").Index(0).Child("operation"), "").MarkAlpha(), + }, + }, + { + name: "ownerReferences empty apiVersion", + obj: mkMeta(tweakOwnerRefs(mkOwnerRef(tweakRefAPIVersion("")))), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("ownerReferences").Index(0).Child("apiVersion"), "").MarkAlpha(), + }, + }, + { + name: "ownerReferences empty kind", + obj: mkMeta(tweakOwnerRefs(mkOwnerRef(tweakRefKind("")))), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("ownerReferences").Index(0).Child("kind"), "").MarkAlpha(), + }, + }, + { + name: "ownerReferences empty name", + obj: mkMeta(tweakOwnerRefs(mkOwnerRef(tweakRefName("")))), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("ownerReferences").Index(0).Child("name"), "").MarkAlpha(), + }, + }, + { + name: "ownerReferences empty uid", + obj: mkMeta(tweakOwnerRefs(mkOwnerRef(tweakRefUID("")))), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("ownerReferences").Index(0).Child("uid"), "").MarkAlpha(), + }, + }, + { + name: "ownerReferences event is disallowed", + obj: mkMeta(tweakOwnerRefs(mkOwnerRef(tweakRefKind("Event")))), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("ownerReferences").Index(0), metav1.OwnerReference{APIVersion: "v1", Kind: "Event", Name: "name", UID: "uid-1"}, "").MarkFromImperative(), + }, + }, + { + name: "invalid annotation key", + obj: mkMeta(tweakAnnotations(map[string]string{"-invalid": "val"})), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("annotations"), "-invalid", "").WithOrigin("format=k8s-label-key").MarkFromImperative(), + }, + }, + { + name: "annotations size limit exceeded", + obj: mkMeta(tweakAnnotations(map[string]string{"a": strings.Repeat("b", TotalAnnotationSizeLimitB)})), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.TooLong(fldPath.Child("annotations"), "", TotalAnnotationSizeLimitB).MarkFromImperative(), + }, + }, + } + + matcher := field.ErrorMatcher{}.ByField().ByType().BySource().ByOrigin() + + toExpectedErrs := func(allDeclarativeEnforced bool, betaEnabled bool, errs field.ErrorList) field.ErrorList { + expected := make(field.ErrorList, 0, len(errs)) + for _, err := range errs { + e := *err + if !allDeclarativeEnforced && (e.IsAlpha() || (!betaEnabled && e.IsBeta())) { + _ = e.MarkFromImperative() + e.ValidationStabilityLevel = 0 + } + expected = append(expected, &e) + } + return expected + } + + for _, tc := range createCases { + for _, betaEnabled := range []bool{true, false} { + for _, allDeclarativeEnforced := range []bool{true, false} { + t.Run(fmt.Sprintf("Create: %s (betaEnabled=%v, allDeclarativeEnforced=%v)", tc.name, betaEnabled, allDeclarativeEnforced), func(t *testing.T) { + testCtx := ctx + if allDeclarativeEnforced { + testCtx = validate.WithAllDeclarativeEnforcedForTest(ctx) + } + errs := ValidateObjectMetaDeclaratively(testCtx, operation.Create, tc.obj, nil, tc.requiresNamespace, NameIsDNSSubdomain, fldPath, betaEnabled) + matcher.Test(t, toExpectedErrs(allDeclarativeEnforced, betaEnabled, tc.expectedErrs), errs) + }) + } + } + } + + updateCases := []struct { + name string + obj *metav1.ObjectMeta + oldObj *metav1.ObjectMeta + requiresNamespace bool + expectedErrs field.ErrorList + }{ + { + name: "valid update", + obj: mkMeta(tweakResourceVersion("2")), + oldObj: mkMeta(tweakResourceVersion("1")), + requiresNamespace: true, + expectedErrs: nil, + }, + { + name: "valid generation zero on update", + obj: mkMeta(tweakResourceVersion("2"), tweakGeneration(0)), + oldObj: mkMeta(tweakResourceVersion("1"), tweakGeneration(0)), + requiresNamespace: true, + expectedErrs: nil, + }, + { + name: "decremented generation to zero on update", + obj: mkMeta(tweakResourceVersion("2"), tweakGeneration(0)), + oldObj: mkMeta(tweakResourceVersion("1"), tweakGeneration(1)), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("generation"), int64(0), "must not be decremented").MarkFromImperative(), + }, + }, + { + name: "negative generation on update", + obj: mkMeta(tweakResourceVersion("2"), tweakGeneration(-1)), + oldObj: mkMeta(tweakResourceVersion("1"), tweakGeneration(0)), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("generation"), int64(-1), "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(fldPath.Child("generation"), int64(-1), "must not be decremented").MarkFromImperative(), + }, + }, + { + name: "immutable namespace", + obj: mkMeta(tweakNamespace("new-ns"), tweakResourceVersion("2")), + oldObj: mkMeta(tweakNamespace("old-ns"), tweakResourceVersion("1")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("namespace"), "new-ns", "").MarkFromImperative(), + }, + }, + { + name: "ownerReferences empty apiVersion on update", + obj: mkMeta(tweakResourceVersion("2"), tweakOwnerRefs(mkOwnerRef(tweakRefAPIVersion("")))), + oldObj: mkMeta(tweakResourceVersion("1")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("ownerReferences").Index(0).Child("apiVersion"), "").MarkAlpha(), + }, + }, + { + name: "ownerReferences empty kind on update", + obj: mkMeta(tweakResourceVersion("2"), tweakOwnerRefs(mkOwnerRef(tweakRefKind("")))), + oldObj: mkMeta(tweakResourceVersion("1")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("ownerReferences").Index(0).Child("kind"), "").MarkAlpha(), + }, + }, + { + name: "ownerReferences empty name on update", + obj: mkMeta(tweakResourceVersion("2"), tweakOwnerRefs(mkOwnerRef(tweakRefName("")))), + oldObj: mkMeta(tweakResourceVersion("1")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("ownerReferences").Index(0).Child("name"), "").MarkAlpha(), + }, + }, + { + name: "ownerReferences empty uid on update", + obj: mkMeta(tweakResourceVersion("2"), tweakOwnerRefs(mkOwnerRef(tweakRefUID("")))), + oldObj: mkMeta(tweakResourceVersion("1")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Required(fldPath.Child("ownerReferences").Index(0).Child("uid"), "").MarkAlpha(), + }, + }, + { + name: "invalid annotation key on update", + obj: mkMeta(tweakResourceVersion("2"), tweakAnnotations(map[string]string{"-invalid": "val"})), + oldObj: mkMeta(tweakResourceVersion("1")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("annotations"), "-invalid", "").WithOrigin("format=k8s-label-key").MarkFromImperative(), + }, + }, + { + name: "immutable uid on update", + obj: mkMeta(tweakResourceVersion("2"), tweakUID("uid-new")), + oldObj: mkMeta(tweakResourceVersion("1"), tweakUID("uid-old")), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("uid"), types.UID("uid-new"), "").WithOrigin("immutable").MarkAlpha(), + }, + }, + { + name: "immutable creationTimestamp on update", + obj: mkMeta(tweakResourceVersion("2"), tweakCreationTimestamp(later)), + oldObj: mkMeta(tweakResourceVersion("1"), tweakCreationTimestamp(now)), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("creationTimestamp"), later, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + { + name: "immutable deletionTimestamp on update", + obj: mkMeta(tweakResourceVersion("2"), tweakDeletionTimestamp(&later)), + oldObj: mkMeta(tweakResourceVersion("1"), tweakDeletionTimestamp(&now)), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("deletionTimestamp"), &later, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + { + name: "immutable deletionGracePeriodSeconds on update", + obj: mkMeta(tweakResourceVersion("2"), tweakDeletionGracePeriodSeconds(&gracePeriod40)), + oldObj: mkMeta(tweakResourceVersion("1"), tweakDeletionGracePeriodSeconds(&gracePeriod30)), + requiresNamespace: true, + expectedErrs: field.ErrorList{ + field.Invalid(fldPath.Child("deletionGracePeriodSeconds"), &gracePeriod40, "").WithOrigin("immutable").MarkAlpha(), + }, + }, + } + + for _, tc := range updateCases { + for _, betaEnabled := range []bool{true, false} { + for _, allDeclarativeEnforced := range []bool{true, false} { + t.Run(fmt.Sprintf("Update: %s (betaEnabled=%v, allDeclarativeEnforced=%v)", tc.name, betaEnabled, allDeclarativeEnforced), func(t *testing.T) { + testCtx := ctx + if allDeclarativeEnforced { + testCtx = validate.WithAllDeclarativeEnforcedForTest(ctx) + } + errs := ValidateObjectMetaDeclaratively(testCtx, operation.Update, tc.obj, tc.oldObj, tc.requiresNamespace, NameIsDNSSubdomain, fldPath, betaEnabled) + matcher.Test(t, toExpectedErrs(allDeclarativeEnforced, betaEnabled, tc.expectedErrs), errs) + }) + } + } + } +} + +func mkMeta(tweaks ...func(*metav1.ObjectMeta)) *metav1.ObjectMeta { + obj := &metav1.ObjectMeta{ + Name: "valid-name", + Namespace: "valid-ns", + } + for _, tweak := range tweaks { + tweak(obj) + } + return obj +} + +func tweakName(n string) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.Name = n } +} + +func tweakNamespace(ns string) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.Namespace = ns } +} + +func tweakResourceVersion(rv string) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.ResourceVersion = rv } +} + +func tweakGeneration(g int64) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.Generation = g } +} + +func tweakAnnotations(ann map[string]string) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.Annotations = ann } +} + +func tweakManagedFields(entries ...metav1.ManagedFieldsEntry) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.ManagedFields = entries } +} + +func tweakUID(u string) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.UID = types.UID(u) } +} + +func tweakCreationTimestamp(t metav1.Time) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.CreationTimestamp = t } +} + +func tweakDeletionTimestamp(t *metav1.Time) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.DeletionTimestamp = t } +} + +func tweakDeletionGracePeriodSeconds(gps *int64) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.DeletionGracePeriodSeconds = gps } +} + +func tweakRefAPIVersion(v string) func(*metav1.OwnerReference) { + return func(r *metav1.OwnerReference) { r.APIVersion = v } +} + +func tweakRefKind(k string) func(*metav1.OwnerReference) { + return func(r *metav1.OwnerReference) { r.Kind = k } +} + +func tweakRefName(n string) func(*metav1.OwnerReference) { + return func(r *metav1.OwnerReference) { r.Name = n } +} + +func tweakRefUID(u string) func(*metav1.OwnerReference) { + return func(r *metav1.OwnerReference) { r.UID = types.UID(u) } +} + +func tweakOwnerRefs(refs ...metav1.OwnerReference) func(*metav1.ObjectMeta) { + return func(o *metav1.ObjectMeta) { o.OwnerReferences = refs } +} + +func mkOwnerRef(tweaks ...func(*metav1.OwnerReference)) metav1.OwnerReference { + r := &metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Pod", + Name: "name", + UID: "uid-1", + } + for _, tweak := range tweaks { + tweak(r) + } + return *r +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/path/name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/path/name.go new file mode 100644 index 0000000000..c0dee8108a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/api/validation/path/name.go @@ -0,0 +1,42 @@ +/* +Copyright 2015 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 path + +import ( + "k8s.io/apimachinery/pkg/api/validate/content" +) + +// IsValidPathSegmentName validates the name can be safely encoded as a path segment +// +// Deprecated: use content.IsPathSegmentName directly. +var IsValidPathSegmentName = content.IsPathSegmentName + +// IsValidPathSegmentPrefix validates the name can be used as a prefix for a name which will be encoded as a path segment +// It does not check for exact matches with disallowed names, since an arbitrary suffix might make the name valid +// +// Deprecated: use content.IsPathSegmentPrefix directly. +var IsValidPathSegmentPrefix = content.IsPathSegmentPrefix + +// ValidatePathSegmentName validates the name can be safely encoded as a path segment +// +// Deprecated: use a locally defined function. +func ValidatePathSegmentName(name string, prefix bool) []string { + if prefix { + return IsValidPathSegmentPrefix(name) + } + return IsValidPathSegmentName(name) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/OWNERS new file mode 100644 index 0000000000..4023732476 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/OWNERS @@ -0,0 +1,11 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +# Disable inheritance as this is an api owners file +options: + no_parent_owners: true +approvers: + - api-approvers +reviewers: + - api-reviewers +labels: + - kind/api-change diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/asn1/oid.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/asn1/oid.go new file mode 100644 index 0000000000..59c363f919 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/asn1/oid.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 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 asn1 + +import "encoding/asn1" + +// These constants store suffixes for use with the CNCF Private Enterprise Number allocated to Kubernetes: +// https://www.iana.org/assignments/enterprise-numbers.txt +// +// Root: 1.3.6.1.4.1.57683 +// +// Cloud Native Computing Foundation +const ( + // single-value, string value + x509UIDSuffix = 2 +) + +func makeOID(suffix int) asn1.ObjectIdentifier { + return asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57683, suffix} +} + +// X509UID returns an OID (1.3.6.1.4.1.57683.2) for an element of an x509 distinguished name representing a user UID. +// The UID is a unique value for a particular user that will change if the user is removed from the system +// and another user is added with the same username. +// +// This element must not appear more than once in a distinguished name, and the value must be a string +func X509UID() asn1.ObjectIdentifier { + return makeOID(x509UIDSuffix) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go new file mode 100644 index 0000000000..c263f1450d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/fuzzer/fuzzer.go @@ -0,0 +1,339 @@ +/* +Copyright 2017 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 fuzzer + +import ( + "fmt" + "math/rand" + "sort" + "strconv" + "strings" + + "sigs.k8s.io/randfill" + + apitesting "k8s.io/apimachinery/pkg/api/apitesting" + "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" +) + +func genericFuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(q *resource.Quantity, c randfill.Continue) { + *q = *resource.NewQuantity(c.Int63n(1000), resource.DecimalExponent) + }, + func(j *int, c randfill.Continue) { + *j = int(c.Int31()) + }, + func(j **int, c randfill.Continue) { + if c.Bool() { + i := int(c.Int31()) + *j = &i + } else { + *j = nil + } + }, + func(j *runtime.TypeMeta, c randfill.Continue) { + // We have to customize the randomization of TypeMetas because their + // APIVersion and Kind must remain blank in memory. + j.APIVersion = "" + j.Kind = "" + }, + func(j *runtime.Object, c randfill.Continue) { + // TODO: uncomment when round trip starts from a versioned object + if true { // c.Bool() { + *j = &runtime.Unknown{ + // We do not set TypeMeta here because it is not carried through a round trip + Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`), + ContentType: runtime.ContentTypeJSON, + } + } else { + types := []runtime.Object{&metav1.Status{}, &metav1.APIGroup{}} + t := types[c.Rand.Intn(len(types))] + c.Fill(t) + *j = t + } + }, + func(r *runtime.RawExtension, c randfill.Continue) { + // Pick an arbitrary type and fuzz it + types := []runtime.Object{&metav1.Status{}, &metav1.APIGroup{}} + obj := types[c.Rand.Intn(len(types))] + c.Fill(obj) + + // Find a codec for converting the object to raw bytes. This is necessary for the + // api version and kind to be correctly set be serialization. + var codec = apitesting.TestCodec(codecs, metav1.SchemeGroupVersion) + + // Convert the object to raw bytes + bytes, err := runtime.Encode(codec, obj) + if err != nil { + panic(fmt.Sprintf("Failed to encode object: %v", err)) + } + + // strip trailing newlines which do not survive roundtrips + for len(bytes) >= 1 && bytes[len(bytes)-1] == 10 { + bytes = bytes[:len(bytes)-1] + } + + // Set the bytes field on the RawExtension + r.Raw = bytes + }, + } +} + +// taken from randfill (nee gofuzz) internals for RandString +type charRange struct { + first, last rune +} + +func (c *charRange) choose(r *rand.Rand) rune { + count := int64(c.last - c.first + 1) + ch := c.first + rune(r.Int63n(count)) + + return ch +} + +// randomLabelPart produces a valid random label value or name-part +// of a label key. +func randomLabelPart(c randfill.Continue, canBeEmpty bool) string { + validStartEnd := []charRange{{'0', '9'}, {'a', 'z'}, {'A', 'Z'}} + validMiddle := []charRange{{'0', '9'}, {'a', 'z'}, {'A', 'Z'}, + {'.', '.'}, {'-', '-'}, {'_', '_'}} + + partLen := c.Rand.Intn(64) // len is [0, 63] + if !canBeEmpty { + partLen = c.Rand.Intn(63) + 1 // len is [1, 63] + } + + runes := make([]rune, partLen) + if partLen == 0 { + return string(runes) + } + + runes[0] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) + for i := range runes[1:] { + runes[i+1] = validMiddle[c.Rand.Intn(len(validMiddle))].choose(c.Rand) + } + runes[len(runes)-1] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) + + return string(runes) +} + +func randomDNSLabel(c randfill.Continue) string { + validStartEnd := []charRange{{'0', '9'}, {'a', 'z'}} + validMiddle := []charRange{{'0', '9'}, {'a', 'z'}, {'-', '-'}} + + partLen := c.Rand.Intn(63) + 1 // len is [1, 63] + runes := make([]rune, partLen) + + runes[0] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) + for i := range runes[1:] { + runes[i+1] = validMiddle[c.Rand.Intn(len(validMiddle))].choose(c.Rand) + } + runes[len(runes)-1] = validStartEnd[c.Rand.Intn(len(validStartEnd))].choose(c.Rand) + + return string(runes) +} + +func randomLabelKey(c randfill.Continue) string { + namePart := randomLabelPart(c, false) + prefixPart := "" + + usePrefix := c.Bool() + if usePrefix { + // we can fit, with dots, at most 3 labels in the 253 allotted characters + prefixPartsLen := c.Rand.Intn(2) + 1 + prefixParts := make([]string, prefixPartsLen) + for i := range prefixParts { + prefixParts[i] = randomDNSLabel(c) + } + prefixPart = strings.Join(prefixParts, ".") + "/" + } + + return prefixPart + namePart +} + +func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { + + return []interface{}{ + func(j *metav1.TypeMeta, c randfill.Continue) { + // We have to customize the randomization of TypeMetas because their + // APIVersion and Kind must remain blank in memory. + j.APIVersion = "" + j.Kind = "" + }, + func(j *metav1.ObjectMeta, c randfill.Continue) { + c.FillNoCustom(j) + + j.ResourceVersion = strconv.FormatUint(c.Uint64(), 10) + j.UID = types.UID(c.String(0)) + + // Fuzzing sec and nsec in a smaller range (uint32 instead of int64), + // so that the result Unix time is a valid date and can be parsed into RFC3339 format. + var sec, nsec uint32 + c.Fill(&sec) + c.Fill(&nsec) + j.CreationTimestamp = metav1.Unix(int64(sec), int64(nsec)).Rfc3339Copy() + + if j.DeletionTimestamp != nil { + c.Fill(&sec) + c.Fill(&nsec) + t := metav1.Unix(int64(sec), int64(nsec)).Rfc3339Copy() + j.DeletionTimestamp = &t + } + + if len(j.Labels) == 0 { + j.Labels = nil + } else { + delete(j.Labels, "") + } + if len(j.Annotations) == 0 { + j.Annotations = nil + } else { + delete(j.Annotations, "") + } + if len(j.OwnerReferences) == 0 { + j.OwnerReferences = nil + } + if len(j.Finalizers) == 0 { + j.Finalizers = nil + } + }, + func(j *metav1.ResourceVersionMatch, c randfill.Continue) { + matches := []metav1.ResourceVersionMatch{"", metav1.ResourceVersionMatchExact, metav1.ResourceVersionMatchNotOlderThan} + *j = matches[c.Rand.Intn(len(matches))] + }, + func(j *metav1.ListMeta, c randfill.Continue) { + j.ResourceVersion = strconv.FormatUint(c.Uint64(), 10) + j.SelfLink = c.String(0) //nolint:staticcheck // SA1019 backwards compatibility + }, + func(j *metav1.LabelSelector, c randfill.Continue) { + c.FillNoCustom(j) + // we can't have an entirely empty selector, so force + // use of MatchExpression if necessary + if len(j.MatchLabels) == 0 && len(j.MatchExpressions) == 0 { + j.MatchExpressions = make([]metav1.LabelSelectorRequirement, c.Rand.Intn(2)+1) + } + + if j.MatchLabels != nil { + fuzzedMatchLabels := make(map[string]string, len(j.MatchLabels)) + for i := 0; i < len(j.MatchLabels); i++ { + fuzzedMatchLabels[randomLabelKey(c)] = randomLabelPart(c, true) + } + j.MatchLabels = fuzzedMatchLabels + } + + validOperators := []metav1.LabelSelectorOperator{ + metav1.LabelSelectorOpIn, + metav1.LabelSelectorOpNotIn, + metav1.LabelSelectorOpExists, + metav1.LabelSelectorOpDoesNotExist, + } + + if j.MatchExpressions != nil { + // NB: the label selector parser code sorts match expressions by key, and + // sorts and deduplicates the values, so we need to make sure ours are + // sorted and deduplicated as well here to preserve round-trip comparison. + // In practice, not sorting doesn't hurt anything... + + for i := range j.MatchExpressions { + req := metav1.LabelSelectorRequirement{} + c.Fill(&req) + req.Key = randomLabelKey(c) + req.Operator = validOperators[c.Rand.Intn(len(validOperators))] + if req.Operator == metav1.LabelSelectorOpIn || req.Operator == metav1.LabelSelectorOpNotIn { + if len(req.Values) == 0 { + // we must have some values here, so randomly choose a short length + req.Values = make([]string, c.Rand.Intn(2)+1) + } + for i := range req.Values { + req.Values[i] = randomLabelPart(c, true) + } + req.Values = sets.List(sets.New(req.Values...)) + } else { + req.Values = nil + } + j.MatchExpressions[i] = req + } + + sort.Slice(j.MatchExpressions, func(a, b int) bool { return j.MatchExpressions[a].Key < j.MatchExpressions[b].Key }) + } + }, + func(j *metav1.ManagedFieldsEntry, c randfill.Continue) { + c.FillNoCustom(j) + j.FieldsV1 = nil + }, + } +} + +func v1beta1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(r *metav1beta1.TableOptions, c randfill.Continue) { + c.FillNoCustom(r) + // NoHeaders is not serialized to the wire but is allowed within the versioned + // type because we don't use meta internal types in the client and API server. + r.NoHeaders = false + }, + func(r *metav1beta1.TableRow, c randfill.Continue) { + c.Fill(&r.Object) + c.Fill(&r.Conditions) + if len(r.Conditions) == 0 { + r.Conditions = nil + } + n := c.Intn(10) + if n > 0 { + r.Cells = make([]interface{}, n) + } + for i := range r.Cells { + t := c.Intn(6) + switch t { + case 0: + r.Cells[i] = c.String(0) + case 1: + r.Cells[i] = c.Int63() + case 2: + r.Cells[i] = c.Bool() + case 3: + x := map[string]interface{}{} + for j := c.Intn(10) + 1; j >= 0; j-- { + x[c.String(0)] = c.String(0) + } + r.Cells[i] = x + case 4: + x := make([]interface{}, c.Intn(10)) + for i := range x { + x[i] = c.Int63() + } + r.Cells[i] = x + default: + r.Cells[i] = nil + } + } + }, + } +} + +var Funcs = fuzzer.MergeFuzzerFuncs( + genericFuzzerFuncs, + v1FuzzerFuncs, + v1beta1FuzzerFuncs, +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go new file mode 100644 index 0000000000..f431157ec4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go @@ -0,0 +1,30 @@ +/* +Copyright 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 internalversion + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" +) + +func Convert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOptions, out *ListOptions, s conversion.Scope) error { + return autoConvert_v1_ListOptions_To_internalversion_ListOptions(in, out, s) +} + +func Convert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out *v1.ListOptions, s conversion.Scope) error { + return autoConvert_internalversion_ListOptions_To_v1_ListOptions(in, out, s) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/defaults.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/defaults.go new file mode 100644 index 0000000000..29c6a48b6a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/defaults.go @@ -0,0 +1,38 @@ +/* +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 internalversion + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// SetListOptionsDefaults sets defaults on the provided ListOptions if applicable. +// +// TODO(#115478): once the watch-list fg is always on we register this function in the scheme (via AddTypeDefaultingFunc). +// TODO(#115478): when the function is registered in the scheme remove all callers of this method. +func SetListOptionsDefaults(obj *ListOptions, isWatchListFeatureEnabled bool) { + if !isWatchListFeatureEnabled { + return + } + if obj.SendInitialEvents != nil || len(obj.ResourceVersionMatch) != 0 { + return + } + legacy := obj.ResourceVersion == "" || obj.ResourceVersion == "0" + if obj.Watch && legacy { + turnOnInitialEvents := true + obj.SendInitialEvents = &turnOnInitialEvents + obj.ResourceVersionMatch = metav1.ResourceVersionMatchNotOlderThan + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/defaults_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/defaults_test.go new file mode 100644 index 0000000000..f1c5016066 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/defaults_test.go @@ -0,0 +1,107 @@ +/* +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 internalversion + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/utils/ptr" +) + +func TestSetListOptionsDefaults(t *testing.T) { + scenarios := []struct { + name string + watchListFeatureEnabled bool + targetObj ListOptions + expectedObj ListOptions + }{ + { + name: "no-op, RV doesn't match", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "1"}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "1"}, + }, + { + name: "no-op, SendInitialEvents set", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true)}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true)}, + }, + { + name: "no-op, ResourceVersionMatch set", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersionMatch: "m"}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersionMatch: "m"}, + }, + { + name: "no-op, Watch=false", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything()}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything()}, + }, + { + name: "defaults applied, match on empty RV", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan}, + }, + { + name: "defaults applied, match on RV=0", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "0"}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersion: "0", SendInitialEvents: ptr.To(true), ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan}, + }, + { + name: "no-op, match on empty RV but watch-list fg is off", + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true}, + }, + { + name: "no-op, match on empty RV but SendInitialEvents is on", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true)}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(true)}, + }, + { + name: "no-op, match on empty RV but SendInitialEvents is off", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(false)}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, SendInitialEvents: ptr.To(false)}, + }, + { + name: "no-op, match on empty RV but ResourceVersionMatch set", + watchListFeatureEnabled: true, + targetObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersionMatch: "m"}, + expectedObj: ListOptions{LabelSelector: labels.Everything(), FieldSelector: fields.Everything(), Watch: true, ResourceVersionMatch: "m"}, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + SetListOptionsDefaults(&scenario.targetObj, scenario.watchListFeatureEnabled) + if !apiequality.Semantic.DeepEqual(&scenario.expectedObj, &scenario.targetObj) { + t.Errorf("expected and defaulted objects are different:\n%s", cmp.Diff(&scenario.expectedObj, &scenario.targetObj)) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/doc.go new file mode 100644 index 0000000000..1e85c5c43d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/meta/v1 + +package internalversion diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/register.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/register.go new file mode 100644 index 0000000000..a59ac71268 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/register.go @@ -0,0 +1,88 @@ +/* +Copyright 2017 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 internalversion + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name for this API. +const GroupName = "meta.k8s.io" + +var ( + // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// addToGroupVersion registers common meta types into schemas. +func addToGroupVersion(scheme *runtime.Scheme) error { + if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil { + return err + } + // ListOptions is the only options struct which needs conversion (it exposes labels and fields + // as selectors for convenience). The other types have only a single representation today. + scheme.AddKnownTypes(SchemeGroupVersion, + &ListOptions{}, + &metav1.GetOptions{}, + &metav1.DeleteOptions{}, + &metav1.CreateOptions{}, + &metav1.UpdateOptions{}, + ) + scheme.AddKnownTypes(SchemeGroupVersion, + &metav1.Table{}, + &metav1.TableOptions{}, + &metav1beta1.PartialObjectMetadata{}, + &metav1beta1.PartialObjectMetadataList{}, + ) + if err := metav1beta1.AddMetaToScheme(scheme); err != nil { + return err + } + if err := metav1.AddMetaToScheme(scheme); err != nil { + return err + } + // Allow delete options to be decoded across all version in this scheme (we may want to be more clever than this) + scheme.AddUnversionedTypes(SchemeGroupVersion, + &metav1.DeleteOptions{}, + &metav1.CreateOptions{}, + &metav1.UpdateOptions{}) + + metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) + if err := metav1beta1.RegisterConversions(scheme); err != nil { + return err + } + return nil +} + +// Unlike other API groups, meta internal knows about all meta external versions, but keeps +// the logic for conversion private. +func init() { + localSchemeBuilder.Register(addToGroupVersion) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/doc.go new file mode 100644 index 0000000000..b5ce956ee5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2016 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 scheme diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/register.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/register.go new file mode 100644 index 0000000000..585d7f44bd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/register.go @@ -0,0 +1,39 @@ +/* +Copyright 2017 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 scheme + +import ( + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +// Scheme is the registry for any type that adheres to the meta API spec. +var Scheme = runtime.NewScheme() + +// Codecs provides access to encoding and decoding for the scheme. +var Codecs = serializer.NewCodecFactory(Scheme) + +// ParameterCodec handles versioning of objects that are converted to query parameters. +var ParameterCodec = runtime.NewParameterCodec(Scheme) + +// Unlike other API groups, meta internal knows about all meta external versions, but keeps +// the logic for conversion private. +func init() { + utilruntime.Must(internalversion.AddToScheme(Scheme)) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/register_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/register_test.go new file mode 100644 index 0000000000..e17f28a7a0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/register_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2017 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 scheme + +import ( + "net/url" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestListOptions(t *testing.T) { + // verify round trip conversion + ten := int64(10) + in := &metav1.ListOptions{ + LabelSelector: "a=1", + FieldSelector: "b=1", + ResourceVersion: "10", + TimeoutSeconds: &ten, + Watch: true, + } + out := &metainternalversion.ListOptions{} + if err := Scheme.Convert(in, out, nil); err != nil { + t.Fatal(err) + } + actual := &metav1.ListOptions{} + if err := Scheme.Convert(out, actual, nil); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(in, actual) { + t.Errorf("unexpected: %s", cmp.Diff(in, actual)) + } + + // verify failing conversion + for i, failingObject := range []*metav1.ListOptions{ + {LabelSelector: "a!!!"}, + {FieldSelector: "a!!!"}, + } { + out = &metainternalversion.ListOptions{} + if err := Scheme.Convert(failingObject, out, nil); err == nil { + t.Errorf("%d: unexpected conversion: %#v", i, out) + } + } + + // verify kind registration + if gvks, unversioned, err := Scheme.ObjectKinds(in); err != nil || unversioned || gvks[0] != metav1.SchemeGroupVersion.WithKind("ListOptions") { + t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err) + } + if gvks, unversioned, err := Scheme.ObjectKinds(out); err != nil || unversioned || gvks[0] != metainternalversion.SchemeGroupVersion.WithKind("ListOptions") { + t.Errorf("unexpected: %v %v %v", gvks[0], unversioned, err) + } + + actual = &metav1.ListOptions{} + if err := ParameterCodec.DecodeParameters(url.Values{"watch": []string{"1"}}, metav1.SchemeGroupVersion, actual); err != nil { + t.Fatal(err) + } + if !actual.Watch { + t.Errorf("unexpected watch decode: %#v", actual) + } + + // check ParameterCodec + query, err := ParameterCodec.EncodeParameters(in, metav1.SchemeGroupVersion) + if err != nil { + t.Fatal(err) + } + actual = &metav1.ListOptions{} + if err := ParameterCodec.DecodeParameters(query, metav1.SchemeGroupVersion, actual); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(in, actual) { + t.Errorf("unexpected: %s", cmp.Diff(in, actual)) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/roundtrip_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/roundtrip_test.go new file mode 100644 index 0000000000..028942c6a8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme/roundtrip_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2017 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 scheme + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" + "k8s.io/apimachinery/pkg/apis/meta/fuzzer" +) + +func TestRoundTrip(t *testing.T) { + roundtrip.RoundTripTestForScheme(t, Scheme, fuzzer.Funcs) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go new file mode 100644 index 0000000000..a34a11c3e3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go @@ -0,0 +1,108 @@ +/* +Copyright 2017 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 internalversion + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ListOptions is the query options to a standard REST list call. +type ListOptions struct { + metav1.TypeMeta + + // A selector based on labels + LabelSelector labels.Selector + // A selector based on fields + FieldSelector fields.Selector + // If true, watch for changes to this list + Watch bool + // allowWatchBookmarks requests watch events with type "BOOKMARK". + // Servers that do not implement bookmarks may ignore this flag and + // bookmarks are sent at the server's discretion. Clients should not + // assume bookmarks are returned at any specific interval, nor may they + // assume the server will send any BOOKMARK event during a session. + // If this is not a watch, this field is ignored. + AllowWatchBookmarks bool + // resourceVersion sets a constraint on what resource versions a request may be served from. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + ResourceVersion string + // resourceVersionMatch determines how resourceVersion is applied to list calls. + // It is highly recommended that resourceVersionMatch be set for list calls where + // resourceVersion is set. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + ResourceVersionMatch metav1.ResourceVersionMatch + + // Timeout for the list/watch call. + TimeoutSeconds *int64 + // Limit specifies the maximum number of results to return from the server. The server may + // not support this field on all resource types, but if it does and more results remain it + // will set the continue field on the returned list object. + Limit int64 + // Continue is a token returned by the server that lets a client retrieve chunks of results + // from the server by specifying limit. The server may reject requests for continuation tokens + // it does not recognize and will return a 410 error if the token can no longer be used because + // it has expired. + Continue string + + // `sendInitialEvents=true` may be set together with `watch=true`. + // In that case, the watch stream will begin with synthetic events to + // produce the current state of objects in the collection. Once all such + // events have been sent, a synthetic "Bookmark" event will be sent. + // The bookmark will report the ResourceVersion (RV) corresponding to the + // set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. + // Afterwards, the watch stream will proceed as usual, sending watch events + // corresponding to changes (subsequent to the RV) to objects watched. + // + // When `sendInitialEvents` option is set, we require `resourceVersionMatch` + // option to also be set. The semantic of the watch request is as following: + // - `resourceVersionMatch` = NotOlderThan + // is interpreted as "data at least as new as the provided `resourceVersion`" + // and the bookmark event is send when the state is synced + // to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + // If `resourceVersion` is unset, this is interpreted as "consistent read" and the + // bookmark event is send when the state is synced at least to the moment + // when request started being processed. + // - `resourceVersionMatch` set to any other value or unset + // Invalid error is returned. + // + // Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward + // compatibility reasons) and to false otherwise. + SendInitialEvents *bool + + // ShardSelector is the raw shard selector string from the request. + // Parsing is deferred to the apiserver storage layer where the CEL + // parser dependency is available. + ShardSelector string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// List holds a list of objects, which may not be known by the server. +type List struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []runtime.Object +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation.go new file mode 100644 index 0000000000..2734a8f3ba --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation.go @@ -0,0 +1,76 @@ +/* +Copyright 2020 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 validation + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateListOptions returns all validation errors found while validating the ListOptions. +func ValidateListOptions(options *internalversion.ListOptions, isWatchListFeatureEnabled bool) field.ErrorList { + if options.Watch { + return validateWatchOptions(options, isWatchListFeatureEnabled) + } + allErrs := field.ErrorList{} + if match := options.ResourceVersionMatch; len(match) > 0 { + if len(options.ResourceVersion) == 0 { + allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden unless resourceVersion is provided")) + } + if len(options.Continue) > 0 { + allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden when continue is provided")) + } + if match != metav1.ResourceVersionMatchExact && match != metav1.ResourceVersionMatchNotOlderThan { + allErrs = append(allErrs, field.NotSupported(field.NewPath("resourceVersionMatch"), match, []string{string(metav1.ResourceVersionMatchExact), string(metav1.ResourceVersionMatchNotOlderThan), ""})) + } + if match == metav1.ResourceVersionMatchExact && options.ResourceVersion == "0" { + allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\"")) + } + } + if options.SendInitialEvents != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("sendInitialEvents"), "sendInitialEvents is forbidden for list")) + } + return allErrs +} + +func validateWatchOptions(options *internalversion.ListOptions, isWatchListFeatureEnabled bool) field.ErrorList { + allErrs := field.ErrorList{} + match := options.ResourceVersionMatch + if options.SendInitialEvents != nil { + if match != metav1.ResourceVersionMatchNotOlderThan { + allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), fmt.Sprintf("sendInitialEvents requires setting resourceVersionMatch to %s", metav1.ResourceVersionMatchNotOlderThan))) + } + if !isWatchListFeatureEnabled { + allErrs = append(allErrs, field.Forbidden(field.NewPath("sendInitialEvents"), "sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled")) + } + } + if len(match) > 0 { + if options.SendInitialEvents == nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden for watch unless sendInitialEvents is provided")) + } + if match != metav1.ResourceVersionMatchNotOlderThan { + allErrs = append(allErrs, field.NotSupported(field.NewPath("resourceVersionMatch"), match, []string{string(metav1.ResourceVersionMatchNotOlderThan)})) + } + if len(options.Continue) > 0 { + allErrs = append(allErrs, field.Forbidden(field.NewPath("resourceVersionMatch"), "resourceVersionMatch is forbidden when continue is provided")) + } + } + return allErrs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation_test.go new file mode 100644 index 0000000000..450627d923 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation_test.go @@ -0,0 +1,193 @@ +/* +Copyright 2020 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 validation + +import ( + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +func TestValidateListOptions(t *testing.T) { + cases := []struct { + name string + opts internalversion.ListOptions + watchListFeatureEnabled bool + expectErrors []string + }{{ + name: "valid-default", + opts: internalversion.ListOptions{}, + }, { + name: "valid-resourceversionmatch-exact", + opts: internalversion.ListOptions{ + ResourceVersion: "1", + ResourceVersionMatch: metav1.ResourceVersionMatchExact, + }, + }, { + name: "invalid-resourceversionmatch-exact", + opts: internalversion.ListOptions{ + ResourceVersion: "0", + ResourceVersionMatch: metav1.ResourceVersionMatchExact, + }, + expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch \"exact\" is forbidden for resourceVersion \"0\""}, + }, { + name: "valid-resourceversionmatch-notolderthan", + opts: internalversion.ListOptions{ + ResourceVersion: "0", + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + }, + }, { + name: "invalid-resourceversionmatch", + opts: internalversion.ListOptions{ + ResourceVersion: "0", + ResourceVersionMatch: "foo", + }, + expectErrors: []string{"resourceVersionMatch: Unsupported value: \"foo\": supported values: \"Exact\", \"NotOlderThan\", \"\""}, + }, { + name: "list-sendInitialEvents-forbidden", + opts: internalversion.ListOptions{ + SendInitialEvents: ptr.To(true), + }, + expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for list"}, + }, { + name: "valid-watch-default", + opts: internalversion.ListOptions{ + Watch: true, + }, + }, { + name: "valid-watch-sendInitialEvents-on", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(true), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + AllowWatchBookmarks: true, + }, + watchListFeatureEnabled: true, + }, { + name: "valid-watch-sendInitialEvents-off", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(false), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + AllowWatchBookmarks: true, + }, + watchListFeatureEnabled: true, + }, { + name: "watch-resourceversionmatch-without-sendInitialEvents-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + }, + expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch is forbidden for watch unless sendInitialEvents is provided"}, + }, { + name: "watch-sendInitialEvents-without-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(true), + }, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + }, { + name: "watch-sendInitialEvents-with-exact-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(true), + ResourceVersionMatch: metav1.ResourceVersionMatchExact, + AllowWatchBookmarks: true, + }, + watchListFeatureEnabled: true, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "resourceVersionMatch: Unsupported value: \"Exact\": supported values: \"NotOlderThan\""}, + }, { + name: "watch-sendInitialEvents-on-with-empty-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(true), + ResourceVersionMatch: "", + }, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + }, { + name: "watch-sendInitialEvents-off-with-empty-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(false), + ResourceVersionMatch: "", + }, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + }, { + name: "watch-sendInitialEvents-with-incorrect-resourceversionmatch-forbidden", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(true), + ResourceVersionMatch: "incorrect", + AllowWatchBookmarks: true, + }, + watchListFeatureEnabled: true, + expectErrors: []string{"resourceVersionMatch: Forbidden: sendInitialEvents requires setting resourceVersionMatch to NotOlderThan", "resourceVersionMatch: Unsupported value: \"incorrect\": supported values: \"NotOlderThan\""}, + }, { + // note that validating allowWatchBookmarks would break backward compatibility + // because it was possible to request initial events via resourceVersion=0 before this change + name: "watch-sendInitialEvents-no-allowWatchBookmark", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(true), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + }, + watchListFeatureEnabled: true, + }, { + name: "watch-sendInitialEvents-no-watchlist-fg-disabled", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(true), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + AllowWatchBookmarks: true, + }, + expectErrors: []string{"sendInitialEvents: Forbidden: sendInitialEvents is forbidden for watch unless the WatchList feature gate is enabled"}, + }, { + name: "watch-sendInitialEvents-no-watchlist-fg-disabled", + opts: internalversion.ListOptions{ + Watch: true, + SendInitialEvents: ptr.To(true), + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, + AllowWatchBookmarks: true, + Continue: "123", + }, + watchListFeatureEnabled: true, + expectErrors: []string{"resourceVersionMatch: Forbidden: resourceVersionMatch is forbidden when continue is provided"}, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + errs := ValidateListOptions(&tc.opts, tc.watchListFeatureEnabled) + if len(tc.expectErrors) > 0 { + if len(errs) != len(tc.expectErrors) { + t.Errorf("expected %d errors but got %d errors", len(tc.expectErrors), len(errs)) + return + } + for i, expectedErr := range tc.expectErrors { + if expectedErr != errs[i].Error() { + t.Errorf("expected error '%s' but got '%s'", expectedErr, errs[i].Error()) + } + } + return + } + if len(errs) != 0 { + t.Errorf("expected no errors, but got: %v", errs) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go new file mode 100644 index 0000000000..f321ad2345 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go @@ -0,0 +1,140 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package internalversion + +import ( + unsafe "unsafe" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*List)(nil), (*v1.List)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_internalversion_List_To_v1_List(a.(*List), b.(*v1.List), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1.List)(nil), (*List)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_List_To_internalversion_List(a.(*v1.List), b.(*List), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ListOptions)(nil), (*v1.ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_internalversion_ListOptions_To_v1_ListOptions(a.(*ListOptions), b.(*v1.ListOptions), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*v1.ListOptions)(nil), (*ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ListOptions_To_internalversion_ListOptions(a.(*v1.ListOptions), b.(*ListOptions), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_internalversion_List_To_v1_List(in *List, out *v1.List, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.RawExtension, len(*in)) + for i := range *in { + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_internalversion_List_To_v1_List is an autogenerated conversion function. +func Convert_internalversion_List_To_v1_List(in *List, out *v1.List, s conversion.Scope) error { + return autoConvert_internalversion_List_To_v1_List(in, out, s) +} + +func autoConvert_v1_List_To_internalversion_List(in *v1.List, out *List, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.Object, len(*in)) + for i := range *in { + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_List_To_internalversion_List is an autogenerated conversion function. +func Convert_v1_List_To_internalversion_List(in *v1.List, out *List, s conversion.Scope) error { + return autoConvert_v1_List_To_internalversion_List(in, out, s) +} + +func autoConvert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out *v1.ListOptions, s conversion.Scope) error { + if err := v1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil { + return err + } + if err := v1.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil { + return err + } + out.Watch = in.Watch + out.AllowWatchBookmarks = in.AllowWatchBookmarks + out.ResourceVersion = in.ResourceVersion + out.ResourceVersionMatch = v1.ResourceVersionMatch(in.ResourceVersionMatch) + out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) + out.Limit = in.Limit + out.Continue = in.Continue + out.SendInitialEvents = (*bool)(unsafe.Pointer(in.SendInitialEvents)) + out.ShardSelector = in.ShardSelector + return nil +} + +func autoConvert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOptions, out *ListOptions, s conversion.Scope) error { + if err := v1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil { + return err + } + if err := v1.Convert_string_To_fields_Selector(&in.FieldSelector, &out.FieldSelector, s); err != nil { + return err + } + out.Watch = in.Watch + out.AllowWatchBookmarks = in.AllowWatchBookmarks + out.ResourceVersion = in.ResourceVersion + out.ResourceVersionMatch = v1.ResourceVersionMatch(in.ResourceVersionMatch) + out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds)) + out.Limit = in.Limit + out.Continue = in.Continue + out.SendInitialEvents = (*bool)(unsafe.Pointer(in.SendInitialEvents)) + out.ShardSelector = in.ShardSelector + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go new file mode 100644 index 0000000000..af66a2ac4c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go @@ -0,0 +1,102 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package internalversion + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *List) DeepCopyInto(out *List) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.Object, len(*in)) + for i := range *in { + if (*in)[i] != nil { + (*out)[i] = (*in)[i].DeepCopyObject() + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List. +func (in *List) DeepCopy() *List { + if in == nil { + return nil + } + out := new(List) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *List) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ListOptions) DeepCopyInto(out *ListOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.LabelSelector != nil { + out.LabelSelector = in.LabelSelector.DeepCopySelector() + } + if in.FieldSelector != nil { + out.FieldSelector = in.FieldSelector.DeepCopySelector() + } + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int64) + **out = **in + } + if in.SendInitialEvents != nil { + in, out := &in.SendInitialEvents, &out.SendInitialEvents + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListOptions. +func (in *ListOptions) DeepCopy() *ListOptions { + if in == nil { + return nil + } + out := new(ListOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ListOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/OWNERS new file mode 100644 index 0000000000..ec414a84b9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/OWNERS @@ -0,0 +1,17 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +reviewers: + - thockin + - smarterclayton + - wojtek-t + - deads2k + - caesarxuchao + - liggitt + - sttts + - luxas + - janetkuo + - justinsb + - soltysh + - dims +emeritus_reviewers: + - ncdc diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/controller_ref.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/controller_ref.go new file mode 100644 index 0000000000..5005beb12d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/controller_ref.go @@ -0,0 +1,68 @@ +/* +Copyright 2017 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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" +) + +// IsControlledBy checks if the object has a controllerRef set to the given owner +func IsControlledBy(obj Object, owner Object) bool { + ref := GetControllerOfNoCopy(obj) + if ref == nil { + return false + } + return ref.UID == owner.GetUID() +} + +// GetControllerOf returns a pointer to a copy of the controllerRef if controllee has a controller +func GetControllerOf(controllee Object) *OwnerReference { + ref := GetControllerOfNoCopy(controllee) + if ref == nil { + return nil + } + cp := *ref + cp.Controller = ptr.To(*ref.Controller) + if ref.BlockOwnerDeletion != nil { + cp.BlockOwnerDeletion = ptr.To(*ref.BlockOwnerDeletion) + } + return &cp +} + +// GetControllerOfNoCopy returns a pointer to the controllerRef if controllee has a controller +func GetControllerOfNoCopy(controllee Object) *OwnerReference { + refs := controllee.GetOwnerReferences() + for i := range refs { + if refs[i].Controller != nil && *refs[i].Controller { + return &refs[i] + } + } + return nil +} + +// NewControllerRef creates an OwnerReference pointing to the given owner. +func NewControllerRef(owner Object, gvk schema.GroupVersionKind) *OwnerReference { + return &OwnerReference{ + APIVersion: gvk.GroupVersion().String(), + Kind: gvk.Kind, + Name: owner.GetName(), + UID: owner.GetUID(), + BlockOwnerDeletion: ptr.To(true), + Controller: ptr.To(true), + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/controller_ref_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/controller_ref_test.go new file mode 100644 index 0000000000..3abfb9aa91 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/controller_ref_test.go @@ -0,0 +1,173 @@ +/* +Copyright 2017 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 v1 + +import ( + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type metaObj struct { + ObjectMeta + TypeMeta +} + +func TestNewControllerRef(t *testing.T) { + gvk := schema.GroupVersionKind{ + Group: "group", + Version: "v1", + Kind: "Kind", + } + obj1 := &metaObj{ + ObjectMeta: ObjectMeta{ + Name: "name", + UID: "uid1", + }, + } + controllerRef := NewControllerRef(obj1, gvk) + if controllerRef.UID != obj1.UID { + t.Errorf("Incorrect UID: %s", controllerRef.UID) + } + if controllerRef.Controller == nil || *controllerRef.Controller != true { + t.Error("Controller must be set to true") + } + if controllerRef.BlockOwnerDeletion == nil || *controllerRef.BlockOwnerDeletion != true { + t.Error("BlockOwnerDeletion must be set to true") + } + if controllerRef.APIVersion == "" || + controllerRef.Kind == "" || + controllerRef.Name == "" { + t.Errorf("All controllerRef fields must be set: %v", controllerRef) + } +} + +func TestGetControllerOf(t *testing.T) { + gvk := schema.GroupVersionKind{ + Group: "group", + Version: "v1", + Kind: "Kind", + } + obj1 := &metaObj{ + ObjectMeta: ObjectMeta{ + UID: "uid1", + Name: "name1", + }, + } + controllerRef := NewControllerRef(obj1, gvk) + controllerRef.BlockOwnerDeletion = nil + var falseRef = false + obj2 := &metaObj{ + ObjectMeta: ObjectMeta{ + UID: "uid2", + Name: "name1", + OwnerReferences: []OwnerReference{ + { + Name: "owner1", + Controller: &falseRef, + }, + *controllerRef, + { + Name: "owner2", + Controller: &falseRef, + }, + }, + }, + } + + if GetControllerOf(obj1) != nil { + t.Error("GetControllerOf must return null") + } + c := GetControllerOf(obj2) + if c.Name != controllerRef.Name || c.UID != controllerRef.UID { + t.Errorf("Incorrect result of GetControllerOf: %v", c) + } + + // test that all pointers are also deep copied + if (c.Controller == controllerRef.Controller) || + (c.BlockOwnerDeletion != nil && c.BlockOwnerDeletion == controllerRef.BlockOwnerDeletion) { + t.Errorf("GetControllerOf did not return deep copy: %v", c) + } +} + +func BenchmarkGetControllerOf(b *testing.B) { + gvk := schema.GroupVersionKind{ + Group: "group", + Version: "v1", + Kind: "Kind", + } + obj1 := &metaObj{ + ObjectMeta: ObjectMeta{ + UID: "9d0cdf8a-dedc-11e9-bf91-42010a800167", + Name: "my-object", + }, + } + controllerRef := NewControllerRef(obj1, gvk) + controllerRef2 := *controllerRef + controllerRef2.Controller = nil + obj2 := &metaObj{ + ObjectMeta: ObjectMeta{ + UID: "uid2", + Name: "name1", + OwnerReferences: []OwnerReference{controllerRef2, controllerRef2, *controllerRef}, + }, + } + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + c := GetControllerOf(obj2) + if c.Name != controllerRef.Name || c.UID != controllerRef.UID { + b.Errorf("Incorrect result of GetControllerOf: %v", c) + } + } +} + +func TestIsControlledBy(t *testing.T) { + gvk := schema.GroupVersionKind{ + Group: "group", + Version: "v1", + Kind: "Kind", + } + obj1 := &metaObj{ + ObjectMeta: ObjectMeta{ + UID: "uid1", + }, + } + obj2 := &metaObj{ + ObjectMeta: ObjectMeta{ + UID: "uid2", + OwnerReferences: []OwnerReference{ + *NewControllerRef(obj1, gvk), + }, + }, + } + obj3 := &metaObj{ + ObjectMeta: ObjectMeta{ + UID: "uid3", + OwnerReferences: []OwnerReference{ + *NewControllerRef(obj2, gvk), + }, + }, + } + if !IsControlledBy(obj2, obj1) || !IsControlledBy(obj3, obj2) { + t.Error("Incorrect IsControlledBy result: false") + } + if IsControlledBy(obj3, obj1) { + t.Error("Incorrect IsControlledBy result: true") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go new file mode 100644 index 0000000000..8eaebb80e9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/conversion.go @@ -0,0 +1,355 @@ +/* +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 v1 + +import ( + "fmt" + "net/url" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func Convert_Pointer_float64_To_float64(in **float64, out *float64, s conversion.Scope) error { + if *in == nil { + *out = 0 + return nil + } + *out = float64(**in) + return nil +} + +func Convert_float64_To_Pointer_float64(in *float64, out **float64, s conversion.Scope) error { + temp := float64(*in) + *out = &temp + return nil +} + +func Convert_Pointer_int32_To_int32(in **int32, out *int32, s conversion.Scope) error { + if *in == nil { + *out = 0 + return nil + } + *out = int32(**in) + return nil +} + +func Convert_int32_To_Pointer_int32(in *int32, out **int32, s conversion.Scope) error { + temp := int32(*in) + *out = &temp + return nil +} + +func Convert_Pointer_int64_To_int64(in **int64, out *int64, s conversion.Scope) error { + if *in == nil { + *out = 0 + return nil + } + *out = int64(**in) + return nil +} + +func Convert_int64_To_Pointer_int64(in *int64, out **int64, s conversion.Scope) error { + temp := int64(*in) + *out = &temp + return nil +} + +func Convert_Pointer_int64_To_int(in **int64, out *int, s conversion.Scope) error { + if *in == nil { + *out = 0 + return nil + } + *out = int(**in) + return nil +} + +func Convert_int_To_Pointer_int64(in *int, out **int64, s conversion.Scope) error { + temp := int64(*in) + *out = &temp + return nil +} + +func Convert_Pointer_string_To_string(in **string, out *string, s conversion.Scope) error { + if *in == nil { + *out = "" + return nil + } + *out = **in + return nil +} + +func Convert_string_To_Pointer_string(in *string, out **string, s conversion.Scope) error { + if in == nil { + stringVar := "" + *out = &stringVar + return nil + } + *out = in + return nil +} + +func Convert_Pointer_bool_To_bool(in **bool, out *bool, s conversion.Scope) error { + if *in == nil { + *out = false + return nil + } + *out = **in + return nil +} + +func Convert_bool_To_Pointer_bool(in *bool, out **bool, s conversion.Scope) error { + if in == nil { + boolVar := false + *out = &boolVar + return nil + } + *out = in + return nil +} + +// +k8s:conversion-fn=drop +func Convert_v1_TypeMeta_To_v1_TypeMeta(in, out *TypeMeta, s conversion.Scope) error { + // These values are explicitly not copied + //out.APIVersion = in.APIVersion + //out.Kind = in.Kind + return nil +} + +// +k8s:conversion-fn=copy-only +func Convert_v1_ListMeta_To_v1_ListMeta(in, out *ListMeta, s conversion.Scope) error { + *out = *in + return nil +} + +// +k8s:conversion-fn=copy-only +func Convert_v1_DeleteOptions_To_v1_DeleteOptions(in, out *DeleteOptions, s conversion.Scope) error { + *out = *in + return nil +} + +// +k8s:conversion-fn=copy-only +func Convert_intstr_IntOrString_To_intstr_IntOrString(in, out *intstr.IntOrString, s conversion.Scope) error { + *out = *in + return nil +} + +func Convert_Pointer_intstr_IntOrString_To_intstr_IntOrString(in **intstr.IntOrString, out *intstr.IntOrString, s conversion.Scope) error { + if *in == nil { + *out = intstr.IntOrString{} // zero value + return nil + } + *out = **in // copy + return nil +} + +func Convert_intstr_IntOrString_To_Pointer_intstr_IntOrString(in *intstr.IntOrString, out **intstr.IntOrString, s conversion.Scope) error { + temp := *in // copy + *out = &temp + return nil +} + +// +k8s:conversion-fn=copy-only +func Convert_v1_Time_To_v1_Time(in *Time, out *Time, s conversion.Scope) error { + // Cannot deep copy these, because time.Time has unexported fields. + *out = *in + return nil +} + +// +k8s:conversion-fn=copy-only +func Convert_v1_MicroTime_To_v1_MicroTime(in *MicroTime, out *MicroTime, s conversion.Scope) error { + // Cannot deep copy these, because time.Time has unexported fields. + *out = *in + return nil +} + +func Convert_Pointer_v1_Duration_To_v1_Duration(in **Duration, out *Duration, s conversion.Scope) error { + if *in == nil { + *out = Duration{} // zero duration + return nil + } + *out = **in // copy + return nil +} + +func Convert_v1_Duration_To_Pointer_v1_Duration(in *Duration, out **Duration, s conversion.Scope) error { + temp := *in //copy + *out = &temp + return nil +} + +// Convert_Slice_string_To_v1_Time allows converting a URL query parameter value +func Convert_Slice_string_To_v1_Time(in *[]string, out *Time, s conversion.Scope) error { + str := "" + if len(*in) > 0 { + str = (*in)[0] + } + return out.UnmarshalQueryParameter(str) +} + +func Convert_Slice_string_To_Pointer_v1_Time(in *[]string, out **Time, s conversion.Scope) error { + if in == nil { + return nil + } + str := "" + if len(*in) > 0 { + str = (*in)[0] + } + temp := Time{} + if err := temp.UnmarshalQueryParameter(str); err != nil { + return err + } + *out = &temp + return nil +} + +func Convert_string_To_labels_Selector(in *string, out *labels.Selector, s conversion.Scope) error { + selector, err := labels.Parse(*in) + if err != nil { + return err + } + *out = selector + return nil +} + +func Convert_string_To_fields_Selector(in *string, out *fields.Selector, s conversion.Scope) error { + selector, err := fields.ParseSelector(*in) + if err != nil { + return err + } + *out = selector + return nil +} + +func Convert_labels_Selector_To_string(in *labels.Selector, out *string, s conversion.Scope) error { + if *in == nil { + return nil + } + *out = (*in).String() + return nil +} + +func Convert_fields_Selector_To_string(in *fields.Selector, out *string, s conversion.Scope) error { + if *in == nil { + return nil + } + *out = (*in).String() + return nil +} + +// +k8s:conversion-fn=copy-only +func Convert_resource_Quantity_To_resource_Quantity(in *resource.Quantity, out *resource.Quantity, s conversion.Scope) error { + *out = *in + return nil +} + +func Convert_Map_string_To_string_To_v1_LabelSelector(in *map[string]string, out *LabelSelector, s conversion.Scope) error { + if in == nil { + return nil + } + for labelKey, labelValue := range *in { + AddLabelToSelector(out, labelKey, labelValue) + } + return nil +} + +func Convert_v1_LabelSelector_To_Map_string_To_string(in *LabelSelector, out *map[string]string, s conversion.Scope) error { + var err error + *out, err = LabelSelectorAsMap(in) + return err +} + +// Convert_Slice_string_To_Slice_int32 converts multiple query parameters or +// a single query parameter with a comma delimited value to multiple int32. +// This is used for port forwarding which needs the ports as int32. +func Convert_Slice_string_To_Slice_int32(in *[]string, out *[]int32, s conversion.Scope) error { + for _, s := range *in { + for _, v := range strings.Split(s, ",") { + x, err := strconv.ParseUint(v, 10, 16) + if err != nil { + return fmt.Errorf("cannot convert to []int32: %v", err) + } + *out = append(*out, int32(x)) + } + } + return nil +} + +// Convert_Slice_string_To_Pointer_v1_DeletionPropagation allows converting a URL query parameter propagationPolicy +func Convert_Slice_string_To_Pointer_v1_DeletionPropagation(in *[]string, out **DeletionPropagation, s conversion.Scope) error { + var str string + if len(*in) > 0 { + str = (*in)[0] + } else { + str = "" + } + temp := DeletionPropagation(str) + *out = &temp + return nil +} + +// Convert_Slice_string_To_v1_IncludeObjectPolicy allows converting a URL query parameter value +func Convert_Slice_string_To_v1_IncludeObjectPolicy(in *[]string, out *IncludeObjectPolicy, s conversion.Scope) error { + if len(*in) > 0 { + *out = IncludeObjectPolicy((*in)[0]) + } + return nil +} + +// Convert_url_Values_To_v1_DeleteOptions allows converting a URL to DeleteOptions. +func Convert_url_Values_To_v1_DeleteOptions(in *url.Values, out *DeleteOptions, s conversion.Scope) error { + if err := autoConvert_url_Values_To_v1_DeleteOptions(in, out, s); err != nil { + return err + } + + uid := types.UID("") + if values, ok := (*in)["uid"]; ok && len(values) > 0 { + uid = types.UID(values[0]) + } + + resourceVersion := "" + if values, ok := (*in)["resourceVersion"]; ok && len(values) > 0 { + resourceVersion = values[0] + } + + if len(uid) > 0 || len(resourceVersion) > 0 { + if out.Preconditions == nil { + out.Preconditions = &Preconditions{} + } + if len(uid) > 0 { + out.Preconditions.UID = &uid + } + if len(resourceVersion) > 0 { + out.Preconditions.ResourceVersion = &resourceVersion + } + } + return nil +} + +// Convert_Slice_string_To_v1_ResourceVersionMatch allows converting a URL query parameter to ResourceVersionMatch +func Convert_Slice_string_To_v1_ResourceVersionMatch(in *[]string, out *ResourceVersionMatch, s conversion.Scope) error { + if len(*in) > 0 { + *out = ResourceVersionMatch((*in)[0]) + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go new file mode 100644 index 0000000000..1063759972 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/conversion_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2017 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 v1_test + +import ( + "testing" + "time" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMapToLabelSelectorRoundTrip(t *testing.T) { + // We should be able to round-trip a map-only selector through LabelSelector. + inputs := []map[string]string{ + nil, + {}, + {"one": "foo"}, + {"one": "foo", "two": "bar"}, + } + for _, in := range inputs { + ls := &v1.LabelSelector{} + if err := v1.Convert_Map_string_To_string_To_v1_LabelSelector(&in, ls, nil); err != nil { + t.Errorf("Convert_Map_string_To_string_To_v1_LabelSelector(%#v): %v", in, err) + continue + } + out := map[string]string{} + if err := v1.Convert_v1_LabelSelector_To_Map_string_To_string(ls, &out, nil); err != nil { + t.Errorf("Convert_v1_LabelSelector_To_Map_string_To_string(%#v): %v", ls, err) + continue + } + if !apiequality.Semantic.DeepEqual(in, out) { + t.Errorf("map-selector conversion round-trip failed: got %v; want %v", out, in) + } + } +} + +func TestConvertSliceStringToDeletionPropagation(t *testing.T) { + tcs := []struct { + Input []string + Output v1.DeletionPropagation + }{ + { + Input: nil, + Output: "", + }, + { + Input: []string{}, + Output: "", + }, + { + Input: []string{"foo"}, + Output: "foo", + }, + { + Input: []string{"bar", "foo"}, + Output: "bar", + }, + } + + for _, tc := range tcs { + var dpPtr *v1.DeletionPropagation + if err := v1.Convert_Slice_string_To_Pointer_v1_DeletionPropagation(&tc.Input, &dpPtr, nil); err != nil { + t.Errorf("Convert_Slice_string_To_Pointer_v1_DeletionPropagation(%#v): %v", tc.Input, err) + continue + } + if !apiequality.Semantic.DeepEqual(dpPtr, &tc.Output) { + t.Errorf("slice string to DeletionPropagation conversion failed: got %v; want %v", *dpPtr, tc.Output) + } + } +} + +func TestConvertSliceStringToPointerTime(t *testing.T) { + t1 := v1.Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC) + t1String := t1.Format(time.RFC3339) + t2 := v1.Date(2000, time.June, 6, 6, 6, 6, 0, time.UTC) + t2String := t2.Format(time.RFC3339) + + tcs := []struct { + Input []string + Output *v1.Time + }{ + { + Input: []string{}, + Output: &v1.Time{}, + }, + { + Input: []string{""}, + Output: &v1.Time{}, + }, + { + Input: []string{t1String}, + Output: &t1, + }, + { + Input: []string{t1String, t2String}, + Output: &t1, + }, + } + + for _, tc := range tcs { + var timePtr *v1.Time + if err := v1.Convert_Slice_string_To_Pointer_v1_Time(&tc.Input, &timePtr, nil); err != nil { + t.Errorf("Convert_Slice_string_To_Pointer_v1_Time(%#v): %v", tc.Input, err) + continue + } + if !apiequality.Semantic.DeepEqual(timePtr, tc.Output) { + t.Errorf("slice string to *v1.Time conversion failed: got %#v; want %#v", timePtr, tc.Output) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/deepcopy.go new file mode 100644 index 0000000000..8751d0524f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/deepcopy.go @@ -0,0 +1,46 @@ +/* +Copyright 2019 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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +func (in *TableRow) DeepCopy() *TableRow { + if in == nil { + return nil + } + + out := new(TableRow) + + if in.Cells != nil { + out.Cells = make([]interface{}, len(in.Cells)) + for i := range in.Cells { + out.Cells[i] = runtime.DeepCopyJSONValue(in.Cells[i]) + } + } + + if in.Conditions != nil { + out.Conditions = make([]TableRowCondition, len(in.Conditions)) + for i := range in.Conditions { + in.Conditions[i].DeepCopyInto(&out.Conditions[i]) + } + } + + in.Object.DeepCopyInto(&out.Object) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/doc.go new file mode 100644 index 0000000000..31c87361f6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/doc.go @@ -0,0 +1,25 @@ +/* +Copyright 2016 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. +*/ + +// +k8s:conversion-gen=false +// +k8s:deepcopy-gen=package +// +k8s:openapi-gen=true +// +k8s:defaulter-gen=TypeMeta +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.apis.meta.v1 + +// +groupName=meta.k8s.io + +package v1 diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/duration.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/duration.go new file mode 100644 index 0000000000..a22b07878f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/duration.go @@ -0,0 +1,65 @@ +/* +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 v1 + +import ( + "encoding/json" + "time" +) + +// Duration is a wrapper around time.Duration which supports correct +// marshaling to YAML and JSON. In particular, it marshals into strings, which +// can be used as map keys in json. +type Duration struct { + time.Duration `protobuf:"varint,1,opt,name=duration,casttype=time.Duration"` +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (d *Duration) UnmarshalJSON(b []byte) error { + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pd, err := time.ParseDuration(str) + if err != nil { + return err + } + d.Duration = pd + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Duration.String()) +} + +// ToUnstructured implements the value.UnstructuredConverter interface. +func (d Duration) ToUnstructured() interface{} { + return d.Duration.String() +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ Duration) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ Duration) OpenAPISchemaFormat() string { return "" } diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/duration_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/duration_test.go new file mode 100644 index 0000000000..34ca6b5ded --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/duration_test.go @@ -0,0 +1,153 @@ +/* +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 v1 + +import ( + "encoding/json" + "testing" + "time" + + "sigs.k8s.io/yaml" +) + +type DurationHolder struct { + D Duration `json:"d"` +} + +func TestDurationMarshalYAML(t *testing.T) { + cases := []struct { + input Duration + result string + }{ + {Duration{5 * time.Second}, "d: 5s\n"}, + {Duration{2 * time.Minute}, "d: 2m0s\n"}, + {Duration{time.Hour + 3*time.Millisecond}, "d: 1h0m0.003s\n"}, + } + + for _, c := range cases { + input := DurationHolder{c.input} + result, err := yaml.Marshal(&input) + if err != nil { + t.Errorf("Failed to marshal input: %q: %v", input, err) + } + if string(result) != c.result { + t.Errorf("Failed to marshal input: %q: expected %q, got %q", input, c.result, string(result)) + } + } +} + +func TestDurationUnmarshalYAML(t *testing.T) { + cases := []struct { + input string + result Duration + }{ + {"d: 0s\n", Duration{}}, + {"d: 5s\n", Duration{5 * time.Second}}, + {"d: 2m0s\n", Duration{2 * time.Minute}}, + {"d: 1h0m0.003s\n", Duration{time.Hour + 3*time.Millisecond}}, + + // Units with zero values can optionally be dropped + {"d: 2m\n", Duration{2 * time.Minute}}, + {"d: 1h0.003s\n", Duration{time.Hour + 3*time.Millisecond}}, + } + + for _, c := range cases { + var result DurationHolder + if err := yaml.Unmarshal([]byte(c.input), &result); err != nil { + t.Errorf("Failed to unmarshal input %q: %v", c.input, err) + } + if result.D != c.result { + t.Errorf("Failed to unmarshal input %q: expected %q, got %q", c.input, c.result, result) + } + } +} + +func TestDurationMarshalJSON(t *testing.T) { + cases := []struct { + input Duration + result string + }{ + {Duration{5 * time.Second}, `{"d":"5s"}`}, + {Duration{2 * time.Minute}, `{"d":"2m0s"}`}, + {Duration{time.Hour + 3*time.Millisecond}, `{"d":"1h0m0.003s"}`}, + } + + for _, c := range cases { + input := DurationHolder{c.input} + result, err := json.Marshal(&input) + if err != nil { + t.Errorf("Failed to marshal input: %q: %v", input, err) + } + if string(result) != c.result { + t.Errorf("Failed to marshal input: %q: expected %q, got %q", input, c.result, string(result)) + } + } +} + +func TestDurationUnmarshalJSON(t *testing.T) { + cases := []struct { + input string + result Duration + }{ + {`{"d":"0s"}`, Duration{}}, + {`{"d":"5s"}`, Duration{5 * time.Second}}, + {`{"d":"2m0s"}`, Duration{2 * time.Minute}}, + {`{"d":"1h0m0.003s"}`, Duration{time.Hour + 3*time.Millisecond}}, + + // Units with zero values can optionally be dropped + {`{"d":"2m"}`, Duration{2 * time.Minute}}, + {`{"d":"1h0.003s"}`, Duration{time.Hour + 3*time.Millisecond}}, + } + + for _, c := range cases { + var result DurationHolder + if err := json.Unmarshal([]byte(c.input), &result); err != nil { + t.Errorf("Failed to unmarshal input %q: %v", c.input, err) + } + if result.D != c.result { + t.Errorf("Failed to unmarshal input %q: expected %q, got %q", c.input, c.result, result) + } + } +} + +func TestDurationMarshalJSONUnmarshalYAML(t *testing.T) { + cases := []struct { + input Duration + }{ + {Duration{}}, + {Duration{5 * time.Second}}, + {Duration{2 * time.Minute}}, + {Duration{time.Hour + 3*time.Millisecond}}, + } + + for i, c := range cases { + input := DurationHolder{c.input} + jsonMarshalled, err := json.Marshal(&input) + if err != nil { + t.Errorf("%d-1: Failed to marshal input: '%v': %v", i, input, err) + } + + var result DurationHolder + if err := yaml.Unmarshal(jsonMarshalled, &result); err != nil { + t.Errorf("%d-2: Failed to unmarshal '%+v': %v", i, string(jsonMarshalled), err) + } + + if input.D != result.D { + t.Errorf("%d-4: Failed to marshal input '%#v': got %#v", i, input, result) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1.go new file mode 100644 index 0000000000..81aca59fac --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1.go @@ -0,0 +1,170 @@ +/* +Copyright 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 v1 + +import ( + "fmt" + "io" +) + +func (FieldsV1) SwaggerDoc() map[string]string { + return map[string]string{ + "": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + } +} + +type FieldsV1Reader interface { + io.Reader + io.ReaderAt + // Size returns the original byte length of the underlying data. Size is the number of bytes available for reading via ReadAt. + Size() int64 +} + +func (f *FieldsV1) DeepCopy() *FieldsV1 { + if f == nil { + return nil + } + out := new(FieldsV1) + f.DeepCopyInto(out) + return out +} + +func (f *FieldsV1) Marshal() (dAtA []byte, err error) { + size := f.Size() + dAtA = make([]byte, size) + n, err := f.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (f *FieldsV1) MarshalTo(dAtA []byte) (int, error) { + size := f.Size() + return f.MarshalToSizedBuffer(dAtA[:size]) +} + +func (f *FieldsV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + rawBytes := f.GetRawBytes() + if len(rawBytes) > 0 { + i -= len(rawBytes) + copy(dAtA[i:], rawBytes) + i = encodeVarintGenerated(dAtA, i, uint64(len(rawBytes))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (f *FieldsV1) Size() (n int) { + if f == nil { + return 0 + } + var l int + _ = l + if l := int(f.GetRawReader().Size()); l > 0 { + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (f *FieldsV1) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FieldsV1: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FieldsV1: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + + f.SetRawBytes(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_benchmark_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_benchmark_test.go new file mode 100644 index 0000000000..812c181627 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_benchmark_test.go @@ -0,0 +1,118 @@ +/* +Copyright 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 v1_test + +import ( + "encoding/json" + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var benchmarkPayloads = []string{ + // Small managed fields payload + `{"f:metadata":{"f:labels":{"f:app":{}},"f:annotations":{"f:revision":{}}},"f:spec":{"f:replicas":{},"f:template":{"f:metadata":{"f:labels":{"f:app":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"nginx\"}":{".":{},"f:image":{},"f:name":{}}}}}}}`, + // Larger, deeply nested valid JSON payload (representing complex managedFields) + `{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}},"f:labels":{".":{},"f:app.kubernetes.io/name":{}}},"f:spec":{"f:replicas":{},"f:selector":{},"f:template":{"f:metadata":{"f:labels":{".":{},"f:app.kubernetes.io/name":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"app\"}":{".":{},"f:image":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":8080,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{".":{},"f:limits":{".":{},"f:cpu":{},"f:memory":{}},"f:requests":{".":{},"f:cpu":{},"f:memory":{}}}}}}}}}`, +} + +// BenchmarkDecodeDuplicate measures the allocation and speed of unmarshaling +// exactly identical payloads repeatedly, which is the most common case for +// heavily replicated resources (DaemonSets, ReplicaSets) in the API server. +func BenchmarkDecodeDuplicate(b *testing.B) { + for _, payload := range benchmarkPayloads { + b.Run(fmt.Sprintf("Size%d", len(payload)), func(b *testing.B) { + rawJSON := []byte(payload) + b.ResetTimer() + b.ReportAllocs() + var retained []metav1.FieldsV1 + for j := 0; j < b.N; j++ { + var f metav1.FieldsV1 + if err := json.Unmarshal(rawJSON, &f); err != nil { + b.Fatal(err) + } + retained = append(retained, f) + } + _ = retained + }) + } +} + +// BenchmarkDecodeUnique measures the allocation and speed of unmarshaling +// completely unique payloads to measure the worst-case interning overhead. +func BenchmarkDecodeUnique(b *testing.B) { + for _, payload := range benchmarkPayloads { + b.Run(fmt.Sprintf("Size%d", len(payload)), func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + var retained []metav1.FieldsV1 + for j := 0; j < b.N; j++ { + // Inject the iteration counter to guarantee the string is unique + novelPayload := fmt.Appendf(nil, `{"f:iter":%d,%s`, j, payload[1:]) + var f metav1.FieldsV1 + if err := json.Unmarshal(novelPayload, &f); err != nil { + b.Fatal(err) + } + retained = append(retained, f) + } + _ = retained + }) + } +} + +// BenchmarkParallelDecode tests parallel deserialization of duplicate strings +// to verify lock contention behavior in highly concurrent environments. +func BenchmarkParallelDecode(b *testing.B) { + for _, payload := range benchmarkPayloads { + b.Run(fmt.Sprintf("Size%d", len(payload)), func(b *testing.B) { + rawJSON := []byte(payload) + b.ResetTimer() + b.ReportAllocs() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + var f metav1.FieldsV1 + if err := json.Unmarshal(rawJSON, &f); err != nil { + b.Fatal(err) + } + } + }) + }) + } +} + +// BenchmarkEqual_Same measures the fast-path equality check for identical structs. +func BenchmarkEqual_Same(b *testing.B) { + f1 := metav1.NewFieldsV1(benchmarkPayloads[1]) + f2 := metav1.NewFieldsV1(benchmarkPayloads[1]) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = f1.Equal(*f2) + } +} + +// BenchmarkEqual_Different measures the fallback-path equality check for differing structs. +func BenchmarkEqual_Different(b *testing.B) { + f1 := metav1.NewFieldsV1(benchmarkPayloads[1]) + f2 := metav1.NewFieldsV1(benchmarkPayloads[0]) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = f1.Equal(*f2) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_byte.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_byte.go new file mode 100644 index 0000000000..66f28e1d9f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_byte.go @@ -0,0 +1,105 @@ +//go:build !fieldsv1string + +/* +Copyright 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 v1 + +import ( + "bytes" +) + +// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. +// +// Each key is either a '.' representing the field itself, and will always map to an empty set, +// or a string representing a sub-field or item. The string will follow one of these four formats: +// 'f:', where is the name of a field in a struct, or key in a map +// 'v:', where is the exact json formatted value of a list item +// 'i:', where is position of a item in a list +// 'k:', where is a map of a list item's key fields to their unique values +// If a key maps to an empty Fields value, the field that key represents is part of the set. +// +// The exact format is defined in sigs.k8s.io/structured-merge-diff +// +k8s:deepcopy-gen=false +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +type FieldsV1 struct { + // Raw is the underlying serialization of this object. + // + // Deprecated: Direct access to this field is deprecated. Use GetRawBytes, GetRawString, SetRawBytes, SetRawString, GetRawReader, NewFieldsV1 instead. + Raw []byte `json:"-" protobuf:"bytes,1,opt,name=Raw"` +} + +func (f FieldsV1) String() string { + return string(f.Raw) +} + +func (f FieldsV1) Equal(f2 FieldsV1) bool { + return bytes.Equal(f.Raw, f2.Raw) +} + +func (f *FieldsV1) GetRawReader() FieldsV1Reader { + if f == nil || len(f.Raw) == 0 { + return bytes.NewReader(nil) + } + return bytes.NewReader(f.Raw) +} + +// GetRawBytes returns the raw bytes. +// These may or may not be a copy of the underlying bytes. +// If mutating the underlying bytes is desired, the returned bytes may be mutated and then passed to SetRawBytes(). +// If mutating the underlying bytes is not desired, make a copy of the returned bytes. +func (f *FieldsV1) GetRawBytes() []byte { + if f == nil { + return nil + } + return f.Raw +} + +// GetRawString returns the raw data as a string. +func (f *FieldsV1) GetRawString() string { + if f == nil { + return "" + } + return string(f.Raw) +} + +// SetRawBytes sets the raw bytes. It does not retain the passed-in byte slice. +func (f *FieldsV1) SetRawBytes(b []byte) { + if f != nil { + f.Raw = bytes.Clone(b) + } +} + +// SetRawString sets the raw data from a string. +func (f *FieldsV1) SetRawString(s string) { + if f != nil { + f.Raw = []byte(s) + } +} + +func NewFieldsV1(raw string) *FieldsV1 { + return &FieldsV1{Raw: []byte(raw)} +} + +func (f *FieldsV1) DeepCopyInto(out *FieldsV1) { + *out = *f + if f.Raw != nil { + in, out := &f.Raw, &out.Raw + *out = make([]byte, len(*in)) + copy(*out, *in) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string.go new file mode 100644 index 0000000000..679452ae17 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string.go @@ -0,0 +1,113 @@ +//go:build fieldsv1string + +/* +Copyright 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 v1 + +import ( + "strings" + "unique" +) + +// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. +// +// Each key is either a '.' representing the field itself, and will always map to an empty set, +// or a string representing a sub-field or item. The string will follow one of these four formats: +// 'f:', where is the name of a field in a struct, or key in a map +// 'v:', where is the exact json formatted value of a list item +// 'i:', where is position of a item in a list +// 'k:', where is a map of a list item's key fields to their unique values +// If a key maps to an empty Fields value, the field that key represents is part of the set. +// +// The exact format is defined in sigs.k8s.io/structured-merge-diff +// +k8s:deepcopy-gen=false +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +type FieldsV1 struct { + // The zero value of a unique.Handle[string] has an uninitialized underlying pointer. + // Calling .Value() on it panics. We must explicitly check for this uninitialized + // state (f.handle == unique.Handle[string]{}) across accessors to safely support + // uninitialized metav1.FieldsV1{} objects. + // See ongoing golang discussion related to this here: https://github.com/golang/go/issues/73344 + handle unique.Handle[string] +} + +func (f FieldsV1) String() string { + if f.handle == (unique.Handle[string]{}) { + return "" + } + return f.handle.Value() +} + +func (f FieldsV1) Equal(f2 FieldsV1) bool { + if f.handle == f2.handle { + return true + } + // An uninitialized FieldsV1 compared to an explicitly empty + // FieldsV1 (unique.Make("") will fail the handle check above. + // Evaluate string contents directly as well to maintain parity with legacy + // bytes.Equal(nil, []byte{}) == true behavior. + return f.GetRawString() == f2.GetRawString() +} + +func (f *FieldsV1) GetRawReader() FieldsV1Reader { + if f == nil || f.handle == (unique.Handle[string]{}) { + return strings.NewReader("") + } + return strings.NewReader(f.handle.Value()) +} + +// GetRawBytes returns the raw bytes. +// These may or may not be a copy of the underlying bytes. +// If mutating the underlying bytes is desired, the returned bytes may be mutated and then passed to SetRawBytes(). +// If mutating the underlying bytes is not desired, make a copy of the returned bytes. +func (f *FieldsV1) GetRawBytes() []byte { + if f == nil || f.handle == (unique.Handle[string]{}) { + return nil + } + return []byte(f.handle.Value()) +} + +// GetRawString returns the raw data as a string. +func (f *FieldsV1) GetRawString() string { + if f == nil || f.handle == (unique.Handle[string]{}) { + return "" + } + return f.handle.Value() +} + +// SetRawBytes sets the raw bytes. It does not retain the passed-in byte slice. +func (f *FieldsV1) SetRawBytes(b []byte) { + if f != nil { + f.handle = unique.Make(string(b)) + } +} + +// SetRawString sets the raw data from a string. +func (f *FieldsV1) SetRawString(s string) { + if f != nil { + f.handle = unique.Make(s) + } +} + +func NewFieldsV1(raw string) *FieldsV1 { + return &FieldsV1{handle: unique.Make(raw)} +} + +func (f *FieldsV1) DeepCopyInto(out *FieldsV1) { + *out = *f +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string_test.go new file mode 100644 index 0000000000..384951673a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/fieldsv1_string_test.go @@ -0,0 +1,362 @@ +//go:build fieldsv1string + +/* +Copyright 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 v1_test + +import ( + "bytes" + "encoding/json" + "io" + "testing" + "unsafe" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// requireInterned fails the test if the two strings do not point to the exact same memory address. +func requireInterned(t *testing.T, a, b string) { + t.Helper() + ptrA := unsafe.StringData(a) + ptrB := unsafe.StringData(b) + if ptrA != ptrB { + t.Fatalf("Expected strings to be interned (same memory address) but pointers differ: %p != %p", ptrA, ptrB) + } +} + +func TestFieldsV1_String(t *testing.T) { + for _, tc := range []struct { + name string + f metav1.FieldsV1 + expected string + }{ + { + name: "zero value handle", + f: metav1.FieldsV1{}, + expected: "", + }, + { + name: "initialized empty handle", + f: *metav1.NewFieldsV1(""), + expected: "", + }, + { + name: "valid payload", + f: *metav1.NewFieldsV1(`{"f:app":{}}`), + expected: `{"f:app":{}}`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if got := tc.f.String(); got != tc.expected { + t.Errorf("expected %q, got %q", tc.expected, got) + } + }) + } +} + +func TestFieldsV1_Equal(t *testing.T) { + valid := *metav1.NewFieldsV1(`{"f:app":{}}`) + validClone := *metav1.NewFieldsV1(`{"f:app":{}}`) + different := *metav1.NewFieldsV1(`{"f:other":{}}`) + + for _, tc := range []struct { + name string + a metav1.FieldsV1 + b metav1.FieldsV1 + expected bool + }{ + {"both zero value", metav1.FieldsV1{}, metav1.FieldsV1{}, true}, + {"zero value and initialized empty handle", metav1.FieldsV1{}, *metav1.NewFieldsV1(""), true}, + {"identical valid payloads", valid, validClone, true}, + {"different payloads", valid, different, false}, + {"zero value and valid payload", metav1.FieldsV1{}, valid, false}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := tc.a.Equal(tc.b); got != tc.expected { + t.Errorf("expected Equal() to be %v, got %v", tc.expected, got) + } + // Commutative property + if got := tc.b.Equal(tc.a); got != tc.expected { + t.Errorf("expected Equal() commutative to be %v, got %v", tc.expected, got) + } + }) + } +} + +func TestFieldsV1_GetRawReader(t *testing.T) { + for _, tc := range []struct { + name string + f *metav1.FieldsV1 + expected []byte + }{ + {"nil receiver", nil, []byte("")}, + {"zero value handle", &metav1.FieldsV1{}, []byte("")}, + {"initialized empty handle", metav1.NewFieldsV1(""), []byte("")}, + {"valid payload", metav1.NewFieldsV1(`{"f:app":{}}`), []byte(`{"f:app":{}}`)}, + } { + t.Run(tc.name, func(t *testing.T) { + reader := tc.f.GetRawReader() + got, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("unexpected error reading from RawReader: %v", err) + } + if !bytes.Equal(got, tc.expected) { + t.Errorf("expected %q, got %q", tc.expected, got) + } + }) + } +} + +func TestFieldsV1_GetRawBytes(t *testing.T) { + for _, tc := range []struct { + name string + f *metav1.FieldsV1 + expected []byte + }{ + {"nil receiver", nil, nil}, + {"zero value handle", &metav1.FieldsV1{}, nil}, + {"initialized empty handle", metav1.NewFieldsV1(""), []byte{}}, + {"valid payload", metav1.NewFieldsV1(`{"f:app":{}}`), []byte(`{"f:app":{}}`)}, + } { + t.Run(tc.name, func(t *testing.T) { + got := tc.f.GetRawBytes() + if !bytes.Equal(got, tc.expected) { + t.Errorf("expected %v, got %v", tc.expected, got) + } + // Explicitly check for nil + if tc.expected == nil && got != nil { + t.Errorf("expected strict nil, got %v", got) + } + }) + } + + t.Run("mutation safety", func(t *testing.T) { + f := metav1.NewFieldsV1(`{"f:app":{}}`) + b := f.GetRawBytes() + + // Mutate the returned bytes + b[2] = 'X' + + // The original interned string must remain unchanged + if got := f.GetRawString(); got != `{"f:app":{}}` { + t.Errorf("GetRawBytes returned an unisolated slice! Mutating it corrupted the handle. Got: %s", got) + } + }) +} + +func TestFieldsV1_GetRawString(t *testing.T) { + for _, tc := range []struct { + name string + f *metav1.FieldsV1 + expected string + }{ + {"nil receiver", nil, ""}, + {"zero value handle", &metav1.FieldsV1{}, ""}, + {"initialized empty handle", metav1.NewFieldsV1(""), ""}, + {"valid payload", metav1.NewFieldsV1(`{"f:app":{}}`), `{"f:app":{}}`}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := tc.f.GetRawString(); got != tc.expected { + t.Errorf("expected %q, got %q", tc.expected, got) + } + }) + } +} + +func TestFieldsV1_SetRawBytes(t *testing.T) { + t.Run("nil receiver", func(t *testing.T) { + var f *metav1.FieldsV1 // nil + f.SetRawBytes([]byte("test")) // Should not panic + }) + + t.Run("empty slice input", func(t *testing.T) { + f := &metav1.FieldsV1{} + f.SetRawBytes([]byte{}) + if f.GetRawString() != "" { + t.Errorf("Expected empty string, got %q", f.GetRawString()) + } + }) + + t.Run("nil slice input", func(t *testing.T) { + f := &metav1.FieldsV1{} + f.SetRawBytes(nil) + if f.GetRawString() != "" { + t.Errorf("Expected empty string, got %q", f.GetRawString()) + } + }) + + t.Run("interning behavior", func(t *testing.T) { + payload := []byte(`{"f:app":{}}`) + + // Two distinct slices with identical content + b1 := append([]byte(nil), payload...) + b2 := append([]byte(nil), payload...) + + f1 := &metav1.FieldsV1{} + f1.SetRawBytes(b1) + + f2 := &metav1.FieldsV1{} + f2.SetRawBytes(b2) + + if f1.GetRawString() != string(payload) { + t.Errorf("Expected GetRawString to match payload, got %q", f1.GetRawString()) + } + + requireInterned(t, f1.GetRawString(), f2.GetRawString()) + }) +} + +func TestFieldsV1_SetRawString(t *testing.T) { + t.Run("nil receiver", func(t *testing.T) { + var f *metav1.FieldsV1 // nil + f.SetRawString("test") // Should not panic + }) + + t.Run("interning behavior", func(t *testing.T) { + payload := `{"f:app":{}}` + + // Force string copy to avoid compiler static string optimization if possible + s1 := string(append([]byte(nil), payload...)) + s2 := string(append([]byte(nil), payload...)) + + f1 := &metav1.FieldsV1{} + f1.SetRawString(s1) + + f2 := &metav1.FieldsV1{} + f2.SetRawString(s2) + + requireInterned(t, f1.GetRawString(), f2.GetRawString()) + }) +} + +func TestFieldsV1_NewFieldsV1(t *testing.T) { + t.Run("interning behavior", func(t *testing.T) { + payload := `{"f:app":{}}` + s1 := string(append([]byte(nil), payload...)) + s2 := string(append([]byte(nil), payload...)) + + f1 := metav1.NewFieldsV1(s1) + f2 := metav1.NewFieldsV1(s2) + + requireInterned(t, f1.GetRawString(), f2.GetRawString()) + }) +} + +func TestFieldsV1_DeepCopyInto(t *testing.T) { + t.Run("preserves interning", func(t *testing.T) { + orig := metav1.NewFieldsV1(`{"f:app":{}}`) + var clone metav1.FieldsV1 + orig.DeepCopyInto(&clone) + + if orig.GetRawString() != clone.GetRawString() { + t.Fatalf("Expected strings to match after DeepCopyInto, got %q and %q", orig.GetRawString(), clone.GetRawString()) + } + + requireInterned(t, orig.GetRawString(), clone.GetRawString()) + }) + + t.Run("zero value deep copy", func(t *testing.T) { + var orig metav1.FieldsV1 + var clone metav1.FieldsV1 + orig.DeepCopyInto(&clone) + + if clone.GetRawString() != "" { + t.Errorf("Expected empty string from cloned zero-value, got %q", clone.GetRawString()) + } + if !orig.Equal(clone) { + t.Errorf("Expected original zero-value and its clone to be equal") + } + }) + + t.Run("independence after deep copy", func(t *testing.T) { + orig := metav1.NewFieldsV1(`{"f:app":{}}`) + var clone metav1.FieldsV1 + orig.DeepCopyInto(&clone) + + // Modify the clone + clone.SetRawString(`{"f:new":{}}`) + + // The original should be completely untouched + if got := orig.GetRawString(); got != `{"f:app":{}}` { + t.Errorf("DeepCopyInto failed to isolate clone! Modifying clone corrupted original. Got: %s", got) + } + }) +} +func TestFieldsV1_UnmarshalInterning(t *testing.T) { + jsonPayload := []byte(`{"f:metadata":{"f:labels":{"f:app":{}}}}`) + orig := metav1.NewFieldsV1(string(jsonPayload)) + + protoPayload, err := orig.Marshal() + if err != nil { + t.Fatalf("Failed to marshal protobuf payload during setup: %v", err) + } + + cborPayload, err := orig.MarshalCBOR() + if err != nil { + t.Fatalf("Failed to marshal CBOR payload during setup: %v", err) + } + + tests := []struct { + name string + payload []byte + unmarshal func(t *testing.T, f *metav1.FieldsV1, payload []byte) + }{ + { + name: "json unmarshal", + payload: jsonPayload, + unmarshal: func(t *testing.T, f *metav1.FieldsV1, payload []byte) { + if err := json.Unmarshal(payload, f); err != nil { + t.Fatalf("JSON Unmarshal failed: %v", err) + } + }, + }, + { + name: "protobuf unmarshal", + payload: protoPayload, + unmarshal: func(t *testing.T, f *metav1.FieldsV1, payload []byte) { + if err := f.Unmarshal(payload); err != nil { + t.Fatalf("Protobuf Unmarshal failed: %v", err) + } + }, + }, + { + name: "cbor unmarshal", + payload: cborPayload, + unmarshal: func(t *testing.T, f *metav1.FieldsV1, payload []byte) { + if err := f.UnmarshalCBOR(payload); err != nil { + t.Fatalf("CBOR Unmarshal failed: %v", err) + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Ensure independent slice memory + b1 := append([]byte(nil), tc.payload...) + b2 := append([]byte(nil), tc.payload...) + + var f1 metav1.FieldsV1 + var f2 metav1.FieldsV1 + + tc.unmarshal(t, &f1, b1) + tc.unmarshal(t, &f2, b2) + + requireInterned(t, f1.GetRawString(), f2.GetRawString()) + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go new file mode 100644 index 0000000000..293b0223a7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/generated.pb.go @@ -0,0 +1,10536 @@ +/* +Copyright 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto + +package v1 + +import ( + fmt "fmt" + + io "io" + "sort" + + runtime "k8s.io/apimachinery/pkg/runtime" + + math_bits "math/bits" + reflect "reflect" + strings "strings" + time "time" + + k8s_io_apimachinery_pkg_types "k8s.io/apimachinery/pkg/types" +) + +func (m *APIGroup) Reset() { *m = APIGroup{} } + +func (m *APIGroupList) Reset() { *m = APIGroupList{} } + +func (m *APIResource) Reset() { *m = APIResource{} } + +func (m *APIResourceList) Reset() { *m = APIResourceList{} } + +func (m *APIVersions) Reset() { *m = APIVersions{} } + +func (m *ApplyOptions) Reset() { *m = ApplyOptions{} } + +func (m *Condition) Reset() { *m = Condition{} } + +func (m *CreateOptions) Reset() { *m = CreateOptions{} } + +func (m *DeleteOptions) Reset() { *m = DeleteOptions{} } + +func (m *Duration) Reset() { *m = Duration{} } + +func (m *FieldSelectorRequirement) Reset() { *m = FieldSelectorRequirement{} } + +func (m *FieldsV1) Reset() { *m = FieldsV1{} } + +func (m *GetOptions) Reset() { *m = GetOptions{} } + +func (m *GroupKind) Reset() { *m = GroupKind{} } + +func (m *GroupResource) Reset() { *m = GroupResource{} } + +func (m *GroupVersion) Reset() { *m = GroupVersion{} } + +func (m *GroupVersionForDiscovery) Reset() { *m = GroupVersionForDiscovery{} } + +func (m *GroupVersionKind) Reset() { *m = GroupVersionKind{} } + +func (m *GroupVersionResource) Reset() { *m = GroupVersionResource{} } + +func (m *LabelSelector) Reset() { *m = LabelSelector{} } + +func (m *LabelSelectorRequirement) Reset() { *m = LabelSelectorRequirement{} } + +func (m *List) Reset() { *m = List{} } + +func (m *ListMeta) Reset() { *m = ListMeta{} } + +func (m *ListOptions) Reset() { *m = ListOptions{} } + +func (m *ManagedFieldsEntry) Reset() { *m = ManagedFieldsEntry{} } + +func (m *MicroTime) Reset() { *m = MicroTime{} } + +func (m *ObjectMeta) Reset() { *m = ObjectMeta{} } + +func (m *OwnerReference) Reset() { *m = OwnerReference{} } + +func (m *PartialObjectMetadata) Reset() { *m = PartialObjectMetadata{} } + +func (m *PartialObjectMetadataList) Reset() { *m = PartialObjectMetadataList{} } + +func (m *Patch) Reset() { *m = Patch{} } + +func (m *PatchOptions) Reset() { *m = PatchOptions{} } + +func (m *Preconditions) Reset() { *m = Preconditions{} } + +func (m *RootPaths) Reset() { *m = RootPaths{} } + +func (m *ServerAddressByClientCIDR) Reset() { *m = ServerAddressByClientCIDR{} } + +func (m *ShardInfo) Reset() { *m = ShardInfo{} } + +func (m *Status) Reset() { *m = Status{} } + +func (m *StatusCause) Reset() { *m = StatusCause{} } + +func (m *StatusDetails) Reset() { *m = StatusDetails{} } + +func (m *TableOptions) Reset() { *m = TableOptions{} } + +func (m *Time) Reset() { *m = Time{} } + +func (m *Timestamp) Reset() { *m = Timestamp{} } + +func (m *TypeMeta) Reset() { *m = TypeMeta{} } + +func (m *UpdateOptions) Reset() { *m = UpdateOptions{} } + +func (m *Verbs) Reset() { *m = Verbs{} } + +func (m *WatchEvent) Reset() { *m = WatchEvent{} } + +func (m *APIGroup) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *APIGroup) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *APIGroup) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ServerAddressByClientCIDRs) > 0 { + for iNdEx := len(m.ServerAddressByClientCIDRs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ServerAddressByClientCIDRs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + { + size, err := m.PreferredVersion.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Versions) > 0 { + for iNdEx := len(m.Versions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Versions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *APIGroupList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *APIGroupList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *APIGroupList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Groups) > 0 { + for iNdEx := len(m.Groups) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Groups[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *APIResource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *APIResource) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *APIResource) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.StorageVersionHash) + copy(dAtA[i:], m.StorageVersionHash) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StorageVersionHash))) + i-- + dAtA[i] = 0x52 + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x4a + i -= len(m.Group) + copy(dAtA[i:], m.Group) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) + i-- + dAtA[i] = 0x42 + if len(m.Categories) > 0 { + for iNdEx := len(m.Categories) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Categories[iNdEx]) + copy(dAtA[i:], m.Categories[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Categories[iNdEx]))) + i-- + dAtA[i] = 0x3a + } + } + i -= len(m.SingularName) + copy(dAtA[i:], m.SingularName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SingularName))) + i-- + dAtA[i] = 0x32 + if len(m.ShortNames) > 0 { + for iNdEx := len(m.ShortNames) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ShortNames[iNdEx]) + copy(dAtA[i:], m.ShortNames[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ShortNames[iNdEx]))) + i-- + dAtA[i] = 0x2a + } + } + if m.Verbs != nil { + { + size, err := m.Verbs.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0x1a + i-- + if m.Namespaced { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *APIResourceList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *APIResourceList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *APIResourceList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.APIResources) > 0 { + for iNdEx := len(m.APIResources) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.APIResources[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + i -= len(m.GroupVersion) + copy(dAtA[i:], m.GroupVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.GroupVersion))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *APIVersions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *APIVersions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *APIVersions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ServerAddressByClientCIDRs) > 0 { + for iNdEx := len(m.ServerAddressByClientCIDRs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ServerAddressByClientCIDRs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Versions) > 0 { + for iNdEx := len(m.Versions) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Versions[iNdEx]) + copy(dAtA[i:], m.Versions[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Versions[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ApplyOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ApplyOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ApplyOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.FieldManager) + copy(dAtA[i:], m.FieldManager) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldManager))) + i-- + dAtA[i] = 0x1a + i-- + if m.Force { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + if len(m.DryRun) > 0 { + for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.DryRun[iNdEx]) + copy(dAtA[i:], m.DryRun[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *Condition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Condition) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Condition) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x32 + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x2a + { + size, err := m.LastTransitionTime.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) + i-- + dAtA[i] = 0x18 + i -= len(m.Status) + copy(dAtA[i:], m.Status) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i-- + dAtA[i] = 0x12 + i -= len(m.Type) + copy(dAtA[i:], m.Type) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *CreateOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CreateOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CreateOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.FieldValidation) + copy(dAtA[i:], m.FieldValidation) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldValidation))) + i-- + dAtA[i] = 0x22 + i -= len(m.FieldManager) + copy(dAtA[i:], m.FieldManager) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldManager))) + i-- + dAtA[i] = 0x1a + if len(m.DryRun) > 0 { + for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.DryRun[iNdEx]) + copy(dAtA[i:], m.DryRun[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *DeleteOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeleteOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeleteOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.IgnoreStoreReadErrorWithClusterBreakingPotential != nil { + i-- + if *m.IgnoreStoreReadErrorWithClusterBreakingPotential { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + if len(m.DryRun) > 0 { + for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.DryRun[iNdEx]) + copy(dAtA[i:], m.DryRun[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) + i-- + dAtA[i] = 0x2a + } + } + if m.PropagationPolicy != nil { + i -= len(*m.PropagationPolicy) + copy(dAtA[i:], *m.PropagationPolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PropagationPolicy))) + i-- + dAtA[i] = 0x22 + } + if m.OrphanDependents != nil { + i-- + if *m.OrphanDependents { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.Preconditions != nil { + { + size, err := m.Preconditions.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.GracePeriodSeconds != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.GracePeriodSeconds)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Duration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Duration) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Duration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i = encodeVarintGenerated(dAtA, i, uint64(m.Duration)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} + +func (m *FieldSelectorRequirement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FieldSelectorRequirement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FieldSelectorRequirement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Values) > 0 { + for iNdEx := len(m.Values) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Values[iNdEx]) + copy(dAtA[i:], m.Values[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Values[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + i -= len(m.Operator) + copy(dAtA[i:], m.Operator) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operator))) + i-- + dAtA[i] = 0x12 + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GetOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GetOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.ResourceVersion) + copy(dAtA[i:], m.ResourceVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersion))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GroupKind) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GroupKind) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GroupKind) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0x12 + i -= len(m.Group) + copy(dAtA[i:], m.Group) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GroupResource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GroupResource) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GroupResource) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Resource) + copy(dAtA[i:], m.Resource) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resource))) + i-- + dAtA[i] = 0x12 + i -= len(m.Group) + copy(dAtA[i:], m.Group) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GroupVersion) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GroupVersion) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GroupVersion) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x12 + i -= len(m.Group) + copy(dAtA[i:], m.Group) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GroupVersionForDiscovery) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GroupVersionForDiscovery) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GroupVersionForDiscovery) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x12 + i -= len(m.GroupVersion) + copy(dAtA[i:], m.GroupVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.GroupVersion))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GroupVersionKind) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GroupVersionKind) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GroupVersionKind) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0x1a + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x12 + i -= len(m.Group) + copy(dAtA[i:], m.Group) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GroupVersionResource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GroupVersionResource) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GroupVersionResource) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Resource) + copy(dAtA[i:], m.Resource) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Resource))) + i-- + dAtA[i] = 0x1a + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x12 + i -= len(m.Group) + copy(dAtA[i:], m.Group) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *LabelSelector) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LabelSelector) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *LabelSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.MatchExpressions) > 0 { + for iNdEx := len(m.MatchExpressions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MatchExpressions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.MatchLabels) > 0 { + keysForMatchLabels := make([]string, 0, len(m.MatchLabels)) + for k := range m.MatchLabels { + keysForMatchLabels = append(keysForMatchLabels, string(k)) + } + sort.Strings(keysForMatchLabels) + for iNdEx := len(keysForMatchLabels) - 1; iNdEx >= 0; iNdEx-- { + v := m.MatchLabels[string(keysForMatchLabels[iNdEx])] + baseI := i + i -= len(v) + copy(dAtA[i:], v) + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i-- + dAtA[i] = 0x12 + i -= len(keysForMatchLabels[iNdEx]) + copy(dAtA[i:], keysForMatchLabels[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForMatchLabels[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *LabelSelectorRequirement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LabelSelectorRequirement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *LabelSelectorRequirement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Values) > 0 { + for iNdEx := len(m.Values) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Values[iNdEx]) + copy(dAtA[i:], m.Values[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Values[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + i -= len(m.Operator) + copy(dAtA[i:], m.Operator) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operator))) + i-- + dAtA[i] = 0x12 + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *List) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *List) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *List) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ListMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ListMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ListMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ShardInfo != nil { + { + size, err := m.ShardInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if m.RemainingItemCount != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.RemainingItemCount)) + i-- + dAtA[i] = 0x20 + } + i -= len(m.Continue) + copy(dAtA[i:], m.Continue) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Continue))) + i-- + dAtA[i] = 0x1a + i -= len(m.ResourceVersion) + copy(dAtA[i:], m.ResourceVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersion))) + i-- + dAtA[i] = 0x12 + i -= len(m.SelfLink) + copy(dAtA[i:], m.SelfLink) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SelfLink))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ListOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ListOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ListOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.ShardSelector) + copy(dAtA[i:], m.ShardSelector) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ShardSelector))) + i-- + dAtA[i] = 0x7a + if m.SendInitialEvents != nil { + i-- + if *m.SendInitialEvents { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x58 + } + i -= len(m.ResourceVersionMatch) + copy(dAtA[i:], m.ResourceVersionMatch) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersionMatch))) + i-- + dAtA[i] = 0x52 + i-- + if m.AllowWatchBookmarks { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x48 + i -= len(m.Continue) + copy(dAtA[i:], m.Continue) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Continue))) + i-- + dAtA[i] = 0x42 + i = encodeVarintGenerated(dAtA, i, uint64(m.Limit)) + i-- + dAtA[i] = 0x38 + if m.TimeoutSeconds != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TimeoutSeconds)) + i-- + dAtA[i] = 0x28 + } + i -= len(m.ResourceVersion) + copy(dAtA[i:], m.ResourceVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersion))) + i-- + dAtA[i] = 0x22 + i-- + if m.Watch { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + i -= len(m.FieldSelector) + copy(dAtA[i:], m.FieldSelector) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldSelector))) + i-- + dAtA[i] = 0x12 + i -= len(m.LabelSelector) + copy(dAtA[i:], m.LabelSelector) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.LabelSelector))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ManagedFieldsEntry) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ManagedFieldsEntry) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ManagedFieldsEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Subresource) + copy(dAtA[i:], m.Subresource) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Subresource))) + i-- + dAtA[i] = 0x42 + if m.FieldsV1 != nil { + { + size, err := m.FieldsV1.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + i -= len(m.FieldsType) + copy(dAtA[i:], m.FieldsType) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldsType))) + i-- + dAtA[i] = 0x32 + if m.Time != nil { + { + size, err := m.Time.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + i -= len(m.APIVersion) + copy(dAtA[i:], m.APIVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) + i-- + dAtA[i] = 0x1a + i -= len(m.Operation) + copy(dAtA[i:], m.Operation) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operation))) + i-- + dAtA[i] = 0x12 + i -= len(m.Manager) + copy(dAtA[i:], m.Manager) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Manager))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ObjectMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ObjectMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ObjectMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ManagedFields) > 0 { + for iNdEx := len(m.ManagedFields) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ManagedFields[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x8a + } + } + if len(m.Finalizers) > 0 { + for iNdEx := len(m.Finalizers) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Finalizers[iNdEx]) + copy(dAtA[i:], m.Finalizers[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Finalizers[iNdEx]))) + i-- + dAtA[i] = 0x72 + } + } + if len(m.OwnerReferences) > 0 { + for iNdEx := len(m.OwnerReferences) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.OwnerReferences[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6a + } + } + if len(m.Annotations) > 0 { + keysForAnnotations := make([]string, 0, len(m.Annotations)) + for k := range m.Annotations { + keysForAnnotations = append(keysForAnnotations, string(k)) + } + sort.Strings(keysForAnnotations) + for iNdEx := len(keysForAnnotations) - 1; iNdEx >= 0; iNdEx-- { + v := m.Annotations[string(keysForAnnotations[iNdEx])] + baseI := i + i -= len(v) + copy(dAtA[i:], v) + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i-- + dAtA[i] = 0x12 + i -= len(keysForAnnotations[iNdEx]) + copy(dAtA[i:], keysForAnnotations[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForAnnotations[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x62 + } + } + if len(m.Labels) > 0 { + keysForLabels := make([]string, 0, len(m.Labels)) + for k := range m.Labels { + keysForLabels = append(keysForLabels, string(k)) + } + sort.Strings(keysForLabels) + for iNdEx := len(keysForLabels) - 1; iNdEx >= 0; iNdEx-- { + v := m.Labels[string(keysForLabels[iNdEx])] + baseI := i + i -= len(v) + copy(dAtA[i:], v) + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i-- + dAtA[i] = 0x12 + i -= len(keysForLabels[iNdEx]) + copy(dAtA[i:], keysForLabels[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForLabels[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x5a + } + } + if m.DeletionGracePeriodSeconds != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.DeletionGracePeriodSeconds)) + i-- + dAtA[i] = 0x50 + } + if m.DeletionTimestamp != nil { + { + size, err := m.DeletionTimestamp.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + { + size, err := m.CreationTimestamp.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + i = encodeVarintGenerated(dAtA, i, uint64(m.Generation)) + i-- + dAtA[i] = 0x38 + i -= len(m.ResourceVersion) + copy(dAtA[i:], m.ResourceVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ResourceVersion))) + i-- + dAtA[i] = 0x32 + i -= len(m.UID) + copy(dAtA[i:], m.UID) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) + i-- + dAtA[i] = 0x2a + i -= len(m.SelfLink) + copy(dAtA[i:], m.SelfLink) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SelfLink))) + i-- + dAtA[i] = 0x22 + i -= len(m.Namespace) + copy(dAtA[i:], m.Namespace) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Namespace))) + i-- + dAtA[i] = 0x1a + i -= len(m.GenerateName) + copy(dAtA[i:], m.GenerateName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.GenerateName))) + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *OwnerReference) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *OwnerReference) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *OwnerReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.BlockOwnerDeletion != nil { + i-- + if *m.BlockOwnerDeletion { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } + if m.Controller != nil { + i-- + if *m.Controller { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + i -= len(m.APIVersion) + copy(dAtA[i:], m.APIVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) + i-- + dAtA[i] = 0x2a + i -= len(m.UID) + copy(dAtA[i:], m.UID) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) + i-- + dAtA[i] = 0x22 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x1a + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *PartialObjectMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PartialObjectMetadata) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PartialObjectMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *PartialObjectMetadataList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PartialObjectMetadataList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PartialObjectMetadataList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Patch) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Patch) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Patch) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *PatchOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PatchOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PatchOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.FieldValidation) + copy(dAtA[i:], m.FieldValidation) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldValidation))) + i-- + dAtA[i] = 0x22 + i -= len(m.FieldManager) + copy(dAtA[i:], m.FieldManager) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldManager))) + i-- + dAtA[i] = 0x1a + if m.Force != nil { + i-- + if *m.Force { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if len(m.DryRun) > 0 { + for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.DryRun[iNdEx]) + copy(dAtA[i:], m.DryRun[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *Preconditions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Preconditions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Preconditions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ResourceVersion != nil { + i -= len(*m.ResourceVersion) + copy(dAtA[i:], *m.ResourceVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ResourceVersion))) + i-- + dAtA[i] = 0x12 + } + if m.UID != nil { + i -= len(*m.UID) + copy(dAtA[i:], *m.UID) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.UID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *RootPaths) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RootPaths) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RootPaths) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Paths) > 0 { + for iNdEx := len(m.Paths) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Paths[iNdEx]) + copy(dAtA[i:], m.Paths[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Paths[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ServerAddressByClientCIDR) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ServerAddressByClientCIDR) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ServerAddressByClientCIDR) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.ServerAddress) + copy(dAtA[i:], m.ServerAddress) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ServerAddress))) + i-- + dAtA[i] = 0x12 + i -= len(m.ClientCIDR) + copy(dAtA[i:], m.ClientCIDR) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ClientCIDR))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ShardInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ShardInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ShardInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Selector) + copy(dAtA[i:], m.Selector) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Selector))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Status) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Status) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i = encodeVarintGenerated(dAtA, i, uint64(m.Code)) + i-- + dAtA[i] = 0x30 + if m.Details != nil { + { + size, err := m.Details.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x22 + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x1a + i -= len(m.Status) + copy(dAtA[i:], m.Status) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i-- + dAtA[i] = 0x12 + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *StatusCause) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatusCause) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StatusCause) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Field) + copy(dAtA[i:], m.Field) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Field))) + i-- + dAtA[i] = 0x1a + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x12 + i -= len(m.Type) + copy(dAtA[i:], m.Type) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *StatusDetails) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StatusDetails) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StatusDetails) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.UID) + copy(dAtA[i:], m.UID) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.UID))) + i-- + dAtA[i] = 0x32 + i = encodeVarintGenerated(dAtA, i, uint64(m.RetryAfterSeconds)) + i-- + dAtA[i] = 0x28 + if len(m.Causes) > 0 { + for iNdEx := len(m.Causes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Causes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0x1a + i -= len(m.Group) + copy(dAtA[i:], m.Group) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Group))) + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *TableOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TableOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TableOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.IncludeObject) + copy(dAtA[i:], m.IncludeObject) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.IncludeObject))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Timestamp) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Timestamp) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Timestamp) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i = encodeVarintGenerated(dAtA, i, uint64(m.Nanos)) + i-- + dAtA[i] = 0x10 + i = encodeVarintGenerated(dAtA, i, uint64(m.Seconds)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} + +func (m *TypeMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TypeMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TypeMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.APIVersion) + copy(dAtA[i:], m.APIVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) + i-- + dAtA[i] = 0x12 + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *UpdateOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *UpdateOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *UpdateOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.FieldValidation) + copy(dAtA[i:], m.FieldValidation) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldValidation))) + i-- + dAtA[i] = 0x1a + i -= len(m.FieldManager) + copy(dAtA[i:], m.FieldManager) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FieldManager))) + i-- + dAtA[i] = 0x12 + if len(m.DryRun) > 0 { + for iNdEx := len(m.DryRun) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.DryRun[iNdEx]) + copy(dAtA[i:], m.DryRun[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DryRun[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m Verbs) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m Verbs) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m Verbs) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m) > 0 { + for iNdEx := len(m) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m[iNdEx]) + copy(dAtA[i:], m[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *WatchEvent) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WatchEvent) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *WatchEvent) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Object.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(m.Type) + copy(dAtA[i:], m.Type) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *APIGroup) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Versions) > 0 { + for _, e := range m.Versions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = m.PreferredVersion.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.ServerAddressByClientCIDRs) > 0 { + for _, e := range m.ServerAddressByClientCIDRs { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *APIGroupList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Groups) > 0 { + for _, e := range m.Groups { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *APIResource) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + l = len(m.Kind) + n += 1 + l + sovGenerated(uint64(l)) + if m.Verbs != nil { + l = m.Verbs.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.ShortNames) > 0 { + for _, s := range m.ShortNames { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.SingularName) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Categories) > 0 { + for _, s := range m.Categories { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.Group) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Version) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.StorageVersionHash) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *APIResourceList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.GroupVersion) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.APIResources) > 0 { + for _, e := range m.APIResources { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *APIVersions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Versions) > 0 { + for _, s := range m.Versions { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.ServerAddressByClientCIDRs) > 0 { + for _, e := range m.ServerAddressByClientCIDRs { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ApplyOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DryRun) > 0 { + for _, s := range m.DryRun { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + n += 2 + l = len(m.FieldManager) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *Condition) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Type) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Status) + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.ObservedGeneration)) + l = m.LastTransitionTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Reason) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *CreateOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DryRun) > 0 { + for _, s := range m.DryRun { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.FieldManager) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FieldValidation) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *DeleteOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.GracePeriodSeconds != nil { + n += 1 + sovGenerated(uint64(*m.GracePeriodSeconds)) + } + if m.Preconditions != nil { + l = m.Preconditions.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.OrphanDependents != nil { + n += 2 + } + if m.PropagationPolicy != nil { + l = len(*m.PropagationPolicy) + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.DryRun) > 0 { + for _, s := range m.DryRun { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.IgnoreStoreReadErrorWithClusterBreakingPotential != nil { + n += 2 + } + return n +} + +func (m *Duration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Duration)) + return n +} + +func (m *FieldSelectorRequirement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Operator) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Values) > 0 { + for _, s := range m.Values { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *GetOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ResourceVersion) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *GroupKind) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Group) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Kind) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *GroupResource) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Group) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Resource) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *GroupVersion) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Group) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Version) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *GroupVersionForDiscovery) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.GroupVersion) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Version) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *GroupVersionKind) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Group) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Version) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Kind) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *GroupVersionResource) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Group) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Version) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Resource) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *LabelSelector) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.MatchLabels) > 0 { + for k, v := range m.MatchLabels { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + if len(m.MatchExpressions) > 0 { + for _, e := range m.MatchExpressions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *LabelSelectorRequirement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Operator) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Values) > 0 { + for _, s := range m.Values { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *List) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ListMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SelfLink) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.ResourceVersion) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Continue) + n += 1 + l + sovGenerated(uint64(l)) + if m.RemainingItemCount != nil { + n += 1 + sovGenerated(uint64(*m.RemainingItemCount)) + } + if m.ShardInfo != nil { + l = m.ShardInfo.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *ListOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.LabelSelector) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FieldSelector) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + l = len(m.ResourceVersion) + n += 1 + l + sovGenerated(uint64(l)) + if m.TimeoutSeconds != nil { + n += 1 + sovGenerated(uint64(*m.TimeoutSeconds)) + } + n += 1 + sovGenerated(uint64(m.Limit)) + l = len(m.Continue) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + l = len(m.ResourceVersionMatch) + n += 1 + l + sovGenerated(uint64(l)) + if m.SendInitialEvents != nil { + n += 2 + } + l = len(m.ShardSelector) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ManagedFieldsEntry) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Manager) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Operation) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.APIVersion) + n += 1 + l + sovGenerated(uint64(l)) + if m.Time != nil { + l = m.Time.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.FieldsType) + n += 1 + l + sovGenerated(uint64(l)) + if m.FieldsV1 != nil { + l = m.FieldsV1.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.Subresource) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ObjectMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.GenerateName) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Namespace) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.SelfLink) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.UID) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.ResourceVersion) + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Generation)) + l = m.CreationTimestamp.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.DeletionTimestamp != nil { + l = m.DeletionTimestamp.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.DeletionGracePeriodSeconds != nil { + n += 1 + sovGenerated(uint64(*m.DeletionGracePeriodSeconds)) + } + if len(m.Labels) > 0 { + for k, v := range m.Labels { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + if len(m.Annotations) > 0 { + for k, v := range m.Annotations { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + if len(m.OwnerReferences) > 0 { + for _, e := range m.OwnerReferences { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.Finalizers) > 0 { + for _, s := range m.Finalizers { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.ManagedFields) > 0 { + for _, e := range m.ManagedFields { + l = e.Size() + n += 2 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *OwnerReference) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Kind) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.UID) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.APIVersion) + n += 1 + l + sovGenerated(uint64(l)) + if m.Controller != nil { + n += 2 + } + if m.BlockOwnerDeletion != nil { + n += 2 + } + return n +} + +func (m *PartialObjectMetadata) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *PartialObjectMetadataList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *Patch) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *PatchOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DryRun) > 0 { + for _, s := range m.DryRun { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.Force != nil { + n += 2 + } + l = len(m.FieldManager) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FieldValidation) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *Preconditions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.UID != nil { + l = len(*m.UID) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.ResourceVersion != nil { + l = len(*m.ResourceVersion) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *RootPaths) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Paths) > 0 { + for _, s := range m.Paths { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ServerAddressByClientCIDR) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientCIDR) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.ServerAddress) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ShardInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Selector) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *Status) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Status) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Reason) + n += 1 + l + sovGenerated(uint64(l)) + if m.Details != nil { + l = m.Details.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + n += 1 + sovGenerated(uint64(m.Code)) + return n +} + +func (m *StatusCause) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Type) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Field) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *StatusDetails) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Group) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Kind) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Causes) > 0 { + for _, e := range m.Causes { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + n += 1 + sovGenerated(uint64(m.RetryAfterSeconds)) + l = len(m.UID) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *TableOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.IncludeObject) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *Timestamp) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Seconds)) + n += 1 + sovGenerated(uint64(m.Nanos)) + return n +} + +func (m *TypeMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Kind) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.APIVersion) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *UpdateOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DryRun) > 0 { + for _, s := range m.DryRun { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.FieldManager) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FieldValidation) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m Verbs) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m) > 0 { + for _, s := range m { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *WatchEvent) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Type) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Object.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *APIGroup) String() string { + if this == nil { + return "nil" + } + repeatedStringForVersions := "[]GroupVersionForDiscovery{" + for _, f := range this.Versions { + repeatedStringForVersions += strings.Replace(strings.Replace(f.String(), "GroupVersionForDiscovery", "GroupVersionForDiscovery", 1), `&`, ``, 1) + "," + } + repeatedStringForVersions += "}" + repeatedStringForServerAddressByClientCIDRs := "[]ServerAddressByClientCIDR{" + for _, f := range this.ServerAddressByClientCIDRs { + repeatedStringForServerAddressByClientCIDRs += strings.Replace(strings.Replace(f.String(), "ServerAddressByClientCIDR", "ServerAddressByClientCIDR", 1), `&`, ``, 1) + "," + } + repeatedStringForServerAddressByClientCIDRs += "}" + s := strings.Join([]string{`&APIGroup{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Versions:` + repeatedStringForVersions + `,`, + `PreferredVersion:` + strings.Replace(strings.Replace(this.PreferredVersion.String(), "GroupVersionForDiscovery", "GroupVersionForDiscovery", 1), `&`, ``, 1) + `,`, + `ServerAddressByClientCIDRs:` + repeatedStringForServerAddressByClientCIDRs + `,`, + `}`, + }, "") + return s +} +func (this *APIGroupList) String() string { + if this == nil { + return "nil" + } + repeatedStringForGroups := "[]APIGroup{" + for _, f := range this.Groups { + repeatedStringForGroups += strings.Replace(strings.Replace(f.String(), "APIGroup", "APIGroup", 1), `&`, ``, 1) + "," + } + repeatedStringForGroups += "}" + s := strings.Join([]string{`&APIGroupList{`, + `Groups:` + repeatedStringForGroups + `,`, + `}`, + }, "") + return s +} +func (this *APIResource) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&APIResource{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Namespaced:` + fmt.Sprintf("%v", this.Namespaced) + `,`, + `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, + `Verbs:` + strings.Replace(fmt.Sprintf("%v", this.Verbs), "Verbs", "Verbs", 1) + `,`, + `ShortNames:` + fmt.Sprintf("%v", this.ShortNames) + `,`, + `SingularName:` + fmt.Sprintf("%v", this.SingularName) + `,`, + `Categories:` + fmt.Sprintf("%v", this.Categories) + `,`, + `Group:` + fmt.Sprintf("%v", this.Group) + `,`, + `Version:` + fmt.Sprintf("%v", this.Version) + `,`, + `StorageVersionHash:` + fmt.Sprintf("%v", this.StorageVersionHash) + `,`, + `}`, + }, "") + return s +} +func (this *APIResourceList) String() string { + if this == nil { + return "nil" + } + repeatedStringForAPIResources := "[]APIResource{" + for _, f := range this.APIResources { + repeatedStringForAPIResources += strings.Replace(strings.Replace(f.String(), "APIResource", "APIResource", 1), `&`, ``, 1) + "," + } + repeatedStringForAPIResources += "}" + s := strings.Join([]string{`&APIResourceList{`, + `GroupVersion:` + fmt.Sprintf("%v", this.GroupVersion) + `,`, + `APIResources:` + repeatedStringForAPIResources + `,`, + `}`, + }, "") + return s +} +func (this *ApplyOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ApplyOptions{`, + `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, + `Force:` + fmt.Sprintf("%v", this.Force) + `,`, + `FieldManager:` + fmt.Sprintf("%v", this.FieldManager) + `,`, + `}`, + }, "") + return s +} +func (this *Condition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Condition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastTransitionTime), "Time", "Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} +func (this *CreateOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CreateOptions{`, + `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, + `FieldManager:` + fmt.Sprintf("%v", this.FieldManager) + `,`, + `FieldValidation:` + fmt.Sprintf("%v", this.FieldValidation) + `,`, + `}`, + }, "") + return s +} +func (this *DeleteOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&DeleteOptions{`, + `GracePeriodSeconds:` + valueToStringGenerated(this.GracePeriodSeconds) + `,`, + `Preconditions:` + strings.Replace(this.Preconditions.String(), "Preconditions", "Preconditions", 1) + `,`, + `OrphanDependents:` + valueToStringGenerated(this.OrphanDependents) + `,`, + `PropagationPolicy:` + valueToStringGenerated(this.PropagationPolicy) + `,`, + `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, + `IgnoreStoreReadErrorWithClusterBreakingPotential:` + valueToStringGenerated(this.IgnoreStoreReadErrorWithClusterBreakingPotential) + `,`, + `}`, + }, "") + return s +} +func (this *Duration) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Duration{`, + `Duration:` + fmt.Sprintf("%v", this.Duration) + `,`, + `}`, + }, "") + return s +} +func (this *FieldSelectorRequirement) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&FieldSelectorRequirement{`, + `Key:` + fmt.Sprintf("%v", this.Key) + `,`, + `Operator:` + fmt.Sprintf("%v", this.Operator) + `,`, + `Values:` + fmt.Sprintf("%v", this.Values) + `,`, + `}`, + }, "") + return s +} +func (this *GetOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&GetOptions{`, + `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, + `}`, + }, "") + return s +} +func (this *GroupVersionForDiscovery) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&GroupVersionForDiscovery{`, + `GroupVersion:` + fmt.Sprintf("%v", this.GroupVersion) + `,`, + `Version:` + fmt.Sprintf("%v", this.Version) + `,`, + `}`, + }, "") + return s +} +func (this *LabelSelector) String() string { + if this == nil { + return "nil" + } + repeatedStringForMatchExpressions := "[]LabelSelectorRequirement{" + for _, f := range this.MatchExpressions { + repeatedStringForMatchExpressions += strings.Replace(strings.Replace(f.String(), "LabelSelectorRequirement", "LabelSelectorRequirement", 1), `&`, ``, 1) + "," + } + repeatedStringForMatchExpressions += "}" + keysForMatchLabels := make([]string, 0, len(this.MatchLabels)) + for k := range this.MatchLabels { + keysForMatchLabels = append(keysForMatchLabels, k) + } + sort.Strings(keysForMatchLabels) + mapStringForMatchLabels := "map[string]string{" + for _, k := range keysForMatchLabels { + mapStringForMatchLabels += fmt.Sprintf("%v: %v,", k, this.MatchLabels[k]) + } + mapStringForMatchLabels += "}" + s := strings.Join([]string{`&LabelSelector{`, + `MatchLabels:` + mapStringForMatchLabels + `,`, + `MatchExpressions:` + repeatedStringForMatchExpressions + `,`, + `}`, + }, "") + return s +} +func (this *LabelSelectorRequirement) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&LabelSelectorRequirement{`, + `Key:` + fmt.Sprintf("%v", this.Key) + `,`, + `Operator:` + fmt.Sprintf("%v", this.Operator) + `,`, + `Values:` + fmt.Sprintf("%v", this.Values) + `,`, + `}`, + }, "") + return s +} +func (this *List) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]RawExtension{" + for _, f := range this.Items { + repeatedStringForItems += fmt.Sprintf("%v", f) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&List{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *ListMeta) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ListMeta{`, + `SelfLink:` + fmt.Sprintf("%v", this.SelfLink) + `,`, + `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, + `Continue:` + fmt.Sprintf("%v", this.Continue) + `,`, + `RemainingItemCount:` + valueToStringGenerated(this.RemainingItemCount) + `,`, + `ShardInfo:` + strings.Replace(this.ShardInfo.String(), "ShardInfo", "ShardInfo", 1) + `,`, + `}`, + }, "") + return s +} +func (this *ListOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ListOptions{`, + `LabelSelector:` + fmt.Sprintf("%v", this.LabelSelector) + `,`, + `FieldSelector:` + fmt.Sprintf("%v", this.FieldSelector) + `,`, + `Watch:` + fmt.Sprintf("%v", this.Watch) + `,`, + `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, + `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, + `Limit:` + fmt.Sprintf("%v", this.Limit) + `,`, + `Continue:` + fmt.Sprintf("%v", this.Continue) + `,`, + `AllowWatchBookmarks:` + fmt.Sprintf("%v", this.AllowWatchBookmarks) + `,`, + `ResourceVersionMatch:` + fmt.Sprintf("%v", this.ResourceVersionMatch) + `,`, + `SendInitialEvents:` + valueToStringGenerated(this.SendInitialEvents) + `,`, + `ShardSelector:` + fmt.Sprintf("%v", this.ShardSelector) + `,`, + `}`, + }, "") + return s +} +func (this *ManagedFieldsEntry) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ManagedFieldsEntry{`, + `Manager:` + fmt.Sprintf("%v", this.Manager) + `,`, + `Operation:` + fmt.Sprintf("%v", this.Operation) + `,`, + `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, + `Time:` + strings.Replace(fmt.Sprintf("%v", this.Time), "Time", "Time", 1) + `,`, + `FieldsType:` + fmt.Sprintf("%v", this.FieldsType) + `,`, + `FieldsV1:` + strings.Replace(fmt.Sprintf("%v", this.FieldsV1), "FieldsV1", "FieldsV1", 1) + `,`, + `Subresource:` + fmt.Sprintf("%v", this.Subresource) + `,`, + `}`, + }, "") + return s +} +func (this *ObjectMeta) String() string { + if this == nil { + return "nil" + } + repeatedStringForOwnerReferences := "[]OwnerReference{" + for _, f := range this.OwnerReferences { + repeatedStringForOwnerReferences += strings.Replace(strings.Replace(f.String(), "OwnerReference", "OwnerReference", 1), `&`, ``, 1) + "," + } + repeatedStringForOwnerReferences += "}" + repeatedStringForManagedFields := "[]ManagedFieldsEntry{" + for _, f := range this.ManagedFields { + repeatedStringForManagedFields += strings.Replace(strings.Replace(f.String(), "ManagedFieldsEntry", "ManagedFieldsEntry", 1), `&`, ``, 1) + "," + } + repeatedStringForManagedFields += "}" + keysForLabels := make([]string, 0, len(this.Labels)) + for k := range this.Labels { + keysForLabels = append(keysForLabels, k) + } + sort.Strings(keysForLabels) + mapStringForLabels := "map[string]string{" + for _, k := range keysForLabels { + mapStringForLabels += fmt.Sprintf("%v: %v,", k, this.Labels[k]) + } + mapStringForLabels += "}" + keysForAnnotations := make([]string, 0, len(this.Annotations)) + for k := range this.Annotations { + keysForAnnotations = append(keysForAnnotations, k) + } + sort.Strings(keysForAnnotations) + mapStringForAnnotations := "map[string]string{" + for _, k := range keysForAnnotations { + mapStringForAnnotations += fmt.Sprintf("%v: %v,", k, this.Annotations[k]) + } + mapStringForAnnotations += "}" + s := strings.Join([]string{`&ObjectMeta{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `GenerateName:` + fmt.Sprintf("%v", this.GenerateName) + `,`, + `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, + `SelfLink:` + fmt.Sprintf("%v", this.SelfLink) + `,`, + `UID:` + fmt.Sprintf("%v", this.UID) + `,`, + `ResourceVersion:` + fmt.Sprintf("%v", this.ResourceVersion) + `,`, + `Generation:` + fmt.Sprintf("%v", this.Generation) + `,`, + `CreationTimestamp:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CreationTimestamp), "Time", "Time", 1), `&`, ``, 1) + `,`, + `DeletionTimestamp:` + strings.Replace(fmt.Sprintf("%v", this.DeletionTimestamp), "Time", "Time", 1) + `,`, + `DeletionGracePeriodSeconds:` + valueToStringGenerated(this.DeletionGracePeriodSeconds) + `,`, + `Labels:` + mapStringForLabels + `,`, + `Annotations:` + mapStringForAnnotations + `,`, + `OwnerReferences:` + repeatedStringForOwnerReferences + `,`, + `Finalizers:` + fmt.Sprintf("%v", this.Finalizers) + `,`, + `ManagedFields:` + repeatedStringForManagedFields + `,`, + `}`, + }, "") + return s +} +func (this *OwnerReference) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&OwnerReference{`, + `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `UID:` + fmt.Sprintf("%v", this.UID) + `,`, + `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, + `Controller:` + valueToStringGenerated(this.Controller) + `,`, + `BlockOwnerDeletion:` + valueToStringGenerated(this.BlockOwnerDeletion) + `,`, + `}`, + }, "") + return s +} +func (this *PartialObjectMetadata) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PartialObjectMetadata{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "ObjectMeta", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *PartialObjectMetadataList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]PartialObjectMetadata{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "PartialObjectMetadata", "PartialObjectMetadata", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&PartialObjectMetadataList{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *Patch) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Patch{`, + `}`, + }, "") + return s +} +func (this *PatchOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PatchOptions{`, + `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, + `Force:` + valueToStringGenerated(this.Force) + `,`, + `FieldManager:` + fmt.Sprintf("%v", this.FieldManager) + `,`, + `FieldValidation:` + fmt.Sprintf("%v", this.FieldValidation) + `,`, + `}`, + }, "") + return s +} +func (this *Preconditions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Preconditions{`, + `UID:` + valueToStringGenerated(this.UID) + `,`, + `ResourceVersion:` + valueToStringGenerated(this.ResourceVersion) + `,`, + `}`, + }, "") + return s +} +func (this *RootPaths) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RootPaths{`, + `Paths:` + fmt.Sprintf("%v", this.Paths) + `,`, + `}`, + }, "") + return s +} +func (this *ServerAddressByClientCIDR) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ServerAddressByClientCIDR{`, + `ClientCIDR:` + fmt.Sprintf("%v", this.ClientCIDR) + `,`, + `ServerAddress:` + fmt.Sprintf("%v", this.ServerAddress) + `,`, + `}`, + }, "") + return s +} +func (this *ShardInfo) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ShardInfo{`, + `Selector:` + fmt.Sprintf("%v", this.Selector) + `,`, + `}`, + }, "") + return s +} +func (this *Status) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Status{`, + `ListMeta:` + strings.Replace(strings.Replace(this.ListMeta.String(), "ListMeta", "ListMeta", 1), `&`, ``, 1) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Details:` + strings.Replace(this.Details.String(), "StatusDetails", "StatusDetails", 1) + `,`, + `Code:` + fmt.Sprintf("%v", this.Code) + `,`, + `}`, + }, "") + return s +} +func (this *StatusCause) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StatusCause{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `Field:` + fmt.Sprintf("%v", this.Field) + `,`, + `}`, + }, "") + return s +} +func (this *StatusDetails) String() string { + if this == nil { + return "nil" + } + repeatedStringForCauses := "[]StatusCause{" + for _, f := range this.Causes { + repeatedStringForCauses += strings.Replace(strings.Replace(f.String(), "StatusCause", "StatusCause", 1), `&`, ``, 1) + "," + } + repeatedStringForCauses += "}" + s := strings.Join([]string{`&StatusDetails{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Group:` + fmt.Sprintf("%v", this.Group) + `,`, + `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, + `Causes:` + repeatedStringForCauses + `,`, + `RetryAfterSeconds:` + fmt.Sprintf("%v", this.RetryAfterSeconds) + `,`, + `UID:` + fmt.Sprintf("%v", this.UID) + `,`, + `}`, + }, "") + return s +} +func (this *TableOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&TableOptions{`, + `IncludeObject:` + fmt.Sprintf("%v", this.IncludeObject) + `,`, + `}`, + }, "") + return s +} +func (this *Timestamp) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Timestamp{`, + `Seconds:` + fmt.Sprintf("%v", this.Seconds) + `,`, + `Nanos:` + fmt.Sprintf("%v", this.Nanos) + `,`, + `}`, + }, "") + return s +} +func (this *TypeMeta) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&TypeMeta{`, + `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, + `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, + `}`, + }, "") + return s +} +func (this *UpdateOptions) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&UpdateOptions{`, + `DryRun:` + fmt.Sprintf("%v", this.DryRun) + `,`, + `FieldManager:` + fmt.Sprintf("%v", this.FieldManager) + `,`, + `FieldValidation:` + fmt.Sprintf("%v", this.FieldValidation) + `,`, + `}`, + }, "") + return s +} +func (this *WatchEvent) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&WatchEvent{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Object:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Object), "RawExtension", "runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *APIGroup) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: APIGroup: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: APIGroup: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Versions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Versions = append(m.Versions, GroupVersionForDiscovery{}) + if err := m.Versions[len(m.Versions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PreferredVersion", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.PreferredVersion.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServerAddressByClientCIDRs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServerAddressByClientCIDRs = append(m.ServerAddressByClientCIDRs, ServerAddressByClientCIDR{}) + if err := m.ServerAddressByClientCIDRs[len(m.ServerAddressByClientCIDRs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *APIGroupList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: APIGroupList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: APIGroupList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Groups", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Groups = append(m.Groups, APIGroup{}) + if err := m.Groups[len(m.Groups)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *APIResource) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: APIResource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: APIResource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespaced", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Namespaced = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Verbs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Verbs == nil { + m.Verbs = Verbs{} + } + if err := m.Verbs.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ShortNames", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ShortNames = append(m.ShortNames, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SingularName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SingularName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Categories", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Categories = append(m.Categories, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Group = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StorageVersionHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StorageVersionHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *APIResourceList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: APIResourceList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: APIResourceList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GroupVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GroupVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIResources", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.APIResources = append(m.APIResources, APIResource{}) + if err := m.APIResources[len(m.APIResources)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *APIVersions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: APIVersions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: APIVersions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Versions", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Versions = append(m.Versions, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServerAddressByClientCIDRs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServerAddressByClientCIDRs = append(m.ServerAddressByClientCIDRs, ServerAddressByClientCIDR{}) + if err := m.ServerAddressByClientCIDRs[len(m.ServerAddressByClientCIDRs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ApplyOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplyOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplyOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Force", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Force = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldManager", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldManager = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Condition) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Condition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Condition: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Type = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Status = ConditionStatus(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ObservedGeneration", wireType) + } + m.ObservedGeneration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ObservedGeneration |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastTransitionTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LastTransitionTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CreateOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CreateOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CreateOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldManager", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldManager = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldValidation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldValidation = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeleteOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeleteOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeleteOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field GracePeriodSeconds", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.GracePeriodSeconds = &v + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Preconditions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Preconditions == nil { + m.Preconditions = &Preconditions{} + } + if err := m.Preconditions.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OrphanDependents", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.OrphanDependents = &b + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PropagationPolicy", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := DeletionPropagation(dAtA[iNdEx:postIndex]) + m.PropagationPolicy = &s + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IgnoreStoreReadErrorWithClusterBreakingPotential", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.IgnoreStoreReadErrorWithClusterBreakingPotential = &b + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Duration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Duration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Duration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Duration", wireType) + } + m.Duration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Duration |= time.Duration(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FieldSelectorRequirement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FieldSelectorRequirement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FieldSelectorRequirement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Operator = FieldSelectorOperator(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Values", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Values = append(m.Values, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GroupKind) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GroupKind: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GroupKind: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Group = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GroupResource) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GroupResource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GroupResource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Group = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Resource = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GroupVersion) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GroupVersion: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GroupVersion: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Group = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GroupVersionForDiscovery) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GroupVersionForDiscovery: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GroupVersionForDiscovery: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GroupVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GroupVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GroupVersionKind) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GroupVersionKind: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GroupVersionKind: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Group = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GroupVersionResource) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GroupVersionResource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GroupVersionResource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Group = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Resource", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Resource = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LabelSelector) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LabelSelector: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LabelSelector: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MatchLabels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.MatchLabels == nil { + m.MatchLabels = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.MatchLabels[mapkey] = mapvalue + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MatchExpressions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MatchExpressions = append(m.MatchExpressions, LabelSelectorRequirement{}) + if err := m.MatchExpressions[len(m.MatchExpressions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LabelSelectorRequirement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LabelSelectorRequirement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LabelSelectorRequirement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Operator = LabelSelectorOperator(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Values", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Values = append(m.Values, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *List) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: List: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: List: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, runtime.RawExtension{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ListMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ListMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ListMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SelfLink", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SelfLink = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Continue", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Continue = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RemainingItemCount", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.RemainingItemCount = &v + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ShardInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ShardInfo == nil { + m.ShardInfo = &ShardInfo{} + } + if err := m.ShardInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ListOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ListOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ListOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LabelSelector", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LabelSelector = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldSelector", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldSelector = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Watch", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Watch = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutSeconds", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TimeoutSeconds = &v + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Limit", wireType) + } + m.Limit = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Limit |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Continue", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Continue = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowWatchBookmarks", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowWatchBookmarks = bool(v != 0) + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersionMatch", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceVersionMatch = ResourceVersionMatch(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SendInitialEvents", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.SendInitialEvents = &b + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ShardSelector", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ShardSelector = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ManagedFieldsEntry) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ManagedFieldsEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ManagedFieldsEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Manager", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Manager = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Operation = ManagedFieldsOperationType(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.APIVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Time == nil { + m.Time = &Time{} + } + if err := m.Time.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldsType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldsType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldsV1", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.FieldsV1 == nil { + m.FieldsV1 = &FieldsV1{} + } + if err := m.FieldsV1.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Subresource", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Subresource = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ObjectMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ObjectMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ObjectMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GenerateName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GenerateName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Namespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SelfLink", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SelfLink = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ResourceVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Generation", wireType) + } + m.Generation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Generation |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CreationTimestamp", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CreationTimestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeletionTimestamp", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DeletionTimestamp == nil { + m.DeletionTimestamp = &Time{} + } + if err := m.DeletionTimestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DeletionGracePeriodSeconds", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.DeletionGracePeriodSeconds = &v + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Labels == nil { + m.Labels = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Labels[mapkey] = mapvalue + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Annotations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Annotations == nil { + m.Annotations = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Annotations[mapkey] = mapvalue + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OwnerReferences", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OwnerReferences = append(m.OwnerReferences, OwnerReference{}) + if err := m.OwnerReferences[len(m.OwnerReferences)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Finalizers", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Finalizers = append(m.Finalizers, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ManagedFields", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ManagedFields = append(m.ManagedFields, ManagedFieldsEntry{}) + if err := m.ManagedFields[len(m.ManagedFields)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *OwnerReference) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: OwnerReference: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: OwnerReference: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.APIVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Controller", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.Controller = &b + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockOwnerDeletion", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.BlockOwnerDeletion = &b + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PartialObjectMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PartialObjectMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PartialObjectMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PartialObjectMetadataList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PartialObjectMetadataList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PartialObjectMetadataList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, PartialObjectMetadata{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Patch) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Patch: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Patch: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PatchOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PatchOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PatchOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Force", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.Force = &b + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldManager", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldManager = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldValidation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldValidation = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Preconditions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Preconditions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Preconditions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) + m.UID = &s + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.ResourceVersion = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RootPaths) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RootPaths: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RootPaths: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Paths", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Paths = append(m.Paths, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ServerAddressByClientCIDR) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ServerAddressByClientCIDR: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ServerAddressByClientCIDR: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientCIDR", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientCIDR = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServerAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServerAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ShardInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ShardInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ShardInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Selector = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Status: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Status = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = StatusReason(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Details", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Details == nil { + m.Details = &StatusDetails{} + } + if err := m.Details.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) + } + m.Code = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Code |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusCause) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusCause: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusCause: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Type = CauseType(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Field", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Field = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusDetails) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusDetails: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusDetails: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Group", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Group = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Causes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Causes = append(m.Causes, StatusCause{}) + if err := m.Causes[len(m.Causes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RetryAfterSeconds", wireType) + } + m.RetryAfterSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RetryAfterSeconds |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UID = k8s_io_apimachinery_pkg_types.UID(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TableOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TableOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TableOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IncludeObject", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.IncludeObject = IncludeObjectPolicy(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Timestamp) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Timestamp: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Timestamp: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Seconds", wireType) + } + m.Seconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Seconds |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Nanos", wireType) + } + m.Nanos = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Nanos |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TypeMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TypeMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TypeMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.APIVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UpdateOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UpdateOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UpdateOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DryRun", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DryRun = append(m.DryRun, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldManager", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldManager = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FieldValidation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FieldValidation = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Verbs) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Verbs: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Verbs: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + *m = append(*m, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WatchEvent) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WatchEvent: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WatchEvent: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Type = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Object", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Object.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenerated + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenerated + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto new file mode 100644 index 0000000000..afc9b250fa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto @@ -0,0 +1,1331 @@ +/* +Copyright 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.apimachinery.pkg.apis.meta.v1; + +import "k8s.io/apimachinery/pkg/runtime/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/apimachinery/pkg/apis/meta/v1"; + +// APIGroup contains the name, the supported versions, and the preferred version +// of a group. +message APIGroup { + // name is the name of the group. + optional string name = 1; + + // versions are the versions supported in this group. + // +listType=atomic + repeated GroupVersionForDiscovery versions = 2; + + // preferredVersion is the version preferred by the API server, which + // probably is the storage version. + // +optional + optional GroupVersionForDiscovery preferredVersion = 3; + + // a map of client CIDR to server address that is serving this group. + // This is to help clients reach servers in the most network-efficient way possible. + // Clients can use the appropriate server address as per the CIDR that they match. + // In case of multiple matches, clients should use the longest matching CIDR. + // The server returns only those CIDRs that it thinks that the client can match. + // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. + // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. + // +optional + // +listType=atomic + repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 4; +} + +// APIGroupList is a list of APIGroup, to allow clients to discover the API at +// /apis. +message APIGroupList { + // groups is a list of APIGroup. + // +listType=atomic + repeated APIGroup groups = 1; +} + +// APIResource specifies the name of a resource and whether it is namespaced. +message APIResource { + // name is the plural name of the resource. + optional string name = 1; + + // singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. + // The singularName is more correct for reporting status on a single item and both singular and plural are allowed + // from the kubectl CLI interface. + optional string singularName = 6; + + // namespaced indicates if a resource is namespaced or not. + optional bool namespaced = 2; + + // group is the preferred group of the resource. Empty implies the group of the containing resource list. + // For subresources, this may have a different value, for example: Scale". + optional string group = 8; + + // version is the preferred version of the resource. Empty implies the version of the containing resource list + // For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". + optional string version = 9; + + // kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') + optional string kind = 3; + + // verbs is a list of supported kube verbs (this includes get, list, watch, create, + // update, patch, delete, deletecollection, and proxy) + optional Verbs verbs = 4; + + // shortNames is a list of suggested short names of the resource. + // +listType=atomic + repeated string shortNames = 5; + + // categories is a list of the grouped resources this resource belongs to (e.g. 'all') + // +listType=atomic + repeated string categories = 7; + + // The hash value of the storage version, the version this resource is + // converted to when written to the data store. Value must be treated + // as opaque by clients. Only equality comparison on the value is valid. + // This is an alpha feature and may change or be removed in the future. + // The field is populated by the apiserver only if the + // StorageVersionHash feature gate is enabled. + // This field will remain optional even if it graduates. + // +optional + optional string storageVersionHash = 10; +} + +// APIResourceList is a list of APIResource, it is used to expose the name of the +// resources supported in a specific group and version, and if the resource +// is namespaced. +message APIResourceList { + // groupVersion is the group and version this APIResourceList is for. + optional string groupVersion = 1; + + // resources contains the name of the resources and if they are namespaced. + // +listType=atomic + repeated APIResource resources = 2; +} + +// APIVersions lists the versions that are available, to allow clients to +// discover the API at /api, which is the root path of the legacy v1 API. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +message APIVersions { + // versions are the api versions that are available. + // +listType=atomic + repeated string versions = 1; + + // a map of client CIDR to server address that is serving this group. + // This is to help clients reach servers in the most network-efficient way possible. + // Clients can use the appropriate server address as per the CIDR that they match. + // In case of multiple matches, clients should use the longest matching CIDR. + // The server returns only those CIDRs that it thinks that the client can match. + // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. + // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. + // +listType=atomic + repeated ServerAddressByClientCIDR serverAddressByClientCIDRs = 2; +} + +// ApplyOptions may be provided when applying an API object. +// FieldManager is required for apply requests. +// ApplyOptions is equivalent to PatchOptions. It is provided as a convenience with documentation +// that speaks specifically to how the options fields relate to apply. +message ApplyOptions { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + // +listType=atomic + repeated string dryRun = 1; + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. + optional bool force = 2; + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. This + // field is required. + optional string fieldManager = 3; +} + +// Condition contains details for one aspect of the current state of this API Resource. +// --- +// This struct is intended for direct use as an array at the field path .status.conditions. For example, +// +// type FooStatus struct{ +// // Represents the observations of a foo's current state. +// // Known .status.conditions.type are: "Available", "Progressing", and "Degraded" +// // +patchMergeKey=type +// // +patchStrategy=merge +// // +listType=map +// // +listMapKey=type +// // +k8s:alpha(since: "1.37")=+k8s:optional +// // +k8s:alpha(since: "1.37")=+k8s:listType=map +// // +k8s:alpha(since: "1.37")=+k8s:listMapKey=type +// Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +// +// // other fields +// } +message Condition { + // type of condition in CamelCase or in foo.example.com/CamelCase. + // --- + // Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + // useful (see .node.status.conditions), the ability to deconflict is important. + // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` + // +kubebuilder:validation:MaxLength=316 + // +k8s:alpha(since: "1.37")=+k8s:required + optional string type = 1; + + // status of the condition, one of True, False, Unknown. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=True;False;Unknown + // +k8s:alpha(since: "1.37")=+k8s:required + optional string status = 2; + + // observedGeneration represents the .metadata.generation that the condition was set based upon. + // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + // with respect to the current state of the instance. + // +optional + // +kubebuilder:validation:Minimum=0 + // +k8s:alpha(since: "1.37")=+k8s:optional + // +k8s:alpha(since: "1.37")=+k8s:minimum=0 + optional int64 observedGeneration = 3; + + // lastTransitionTime is the last time the condition transitioned from one status to another. + // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=date-time + // +k8s:alpha(since: "1.37")=+k8s:customValidation + optional Time lastTransitionTime = 4; + + // reason contains a programmatic identifier indicating the reason for the condition's last transition. + // Producers of specific condition types may define expected values and meanings for this field, + // and whether the values are considered a guaranteed API. + // The value should be a CamelCase string. + // This field may not be empty. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=1024 + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` + // +k8s:alpha(since: "1.37")=+k8s:required + // +k8s:alpha(since: "1.37")=+k8s:maxBytes=1024 + optional string reason = 5; + + // message is a human readable message indicating details about the transition. + // This may be an empty string. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=32768 + optional string message = 6; +} + +// CreateOptions may be provided when creating an API object. +message CreateOptions { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + // +listType=atomic + repeated string dryRun = 1; + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. + // +optional + optional string fieldManager = 3; + + // fieldValidation instructs the server on how to handle + // objects in the request (POST/PUT/PATCH) containing unknown + // or duplicate fields. Valid values are: + // - Ignore: This will ignore any unknown fields that are silently + // dropped from the object, and will ignore all but the last duplicate + // field that the decoder encounters. This is the default behavior + // prior to v1.23. + // - Warn: This will send a warning via the standard warning response + // header for each unknown field that is dropped from the object, and + // for each duplicate field that is encountered. The request will + // still succeed if there are no other errors, and will only persist + // the last of any duplicate fields. This is the default in v1.23+ + // - Strict: This will fail the request with a BadRequest error if + // any unknown fields would be dropped from the object, or if any + // duplicate fields are present. The error returned from the server + // will contain all unknown and duplicate fields encountered. + // +optional + optional string fieldValidation = 4; +} + +// DeleteOptions may be provided when deleting an API object. +message DeleteOptions { + // The duration in seconds before the object should be deleted. Value must be non-negative integer. + // The value zero indicates delete immediately. If this value is nil, the default grace period for the + // specified type will be used. + // Defaults to a per object value if not specified. zero means delete immediately. + // +optional + optional int64 gracePeriodSeconds = 1; + + // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be + // returned. + // +k8s:conversion-gen=false + // +optional + optional Preconditions preconditions = 2; + + // Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. + // Should the dependent objects be orphaned. If true/false, the "orphan" + // finalizer will be added to/removed from the object's finalizers list. + // Either this field or PropagationPolicy may be set, but not both. + // +optional + optional bool orphanDependents = 3; + + // Whether and how garbage collection will be performed. + // Either this field or OrphanDependents may be set, but not both. + // The default policy is decided by the existing finalizer set in the + // metadata.finalizers and the resource-specific default policy. + // Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - + // allow the garbage collector to delete the dependents in the background; + // 'Foreground' - a cascading policy that deletes all dependents in the + // foreground. + // +optional + optional string propagationPolicy = 4; + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + // +listType=atomic + repeated string dryRun = 5; + + // if set to true, it will trigger an unsafe deletion of the resource in + // case the normal deletion flow fails with a corrupt object error. + // A resource is considered corrupt if it can not be retrieved from + // the underlying storage successfully because of a) its data can + // not be transformed e.g. decryption failure, or b) it fails + // to decode into an object. + // NOTE: unsafe deletion ignores finalizer constraints, skips + // precondition checks, and removes the object from the storage. + // WARNING: This may potentially break the cluster if the workload + // associated with the resource being unsafe-deleted relies on normal + // deletion flow. Use only if you REALLY know what you are doing. + // The default value is false, and the user must opt in to enable it + // +optional + optional bool ignoreStoreReadErrorWithClusterBreakingPotential = 6; +} + +// Duration is a wrapper around time.Duration which supports correct +// marshaling to YAML and JSON. In particular, it marshals into strings, which +// can be used as map keys in json. +message Duration { + optional int64 duration = 1; +} + +// FieldSelectorRequirement is a selector that contains values, a key, and an operator that +// relates the key and values. +message FieldSelectorRequirement { + // key is the field selector key that the requirement applies to. + optional string key = 1; + + // operator represents a key's relationship to a set of values. + // Valid operators are In, NotIn, Exists, DoesNotExist. + // The list of operators may grow in the future. + optional string operator = 2; + + // values is an array of string values. + // If the operator is In or NotIn, the values array must be non-empty. + // If the operator is Exists or DoesNotExist, the values array must be empty. + // +optional + // +listType=atomic + repeated string values = 3; +} + +// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. +// +// Each key is either a '.' representing the field itself, and will always map to an empty set, +// or a string representing a sub-field or item. The string will follow one of these four formats: +// 'f:', where is the name of a field in a struct, or key in a map +// 'v:', where is the exact json formatted value of a list item +// 'i:', where is position of a item in a list +// 'k:', where is a map of a list item's key fields to their unique values +// If a key maps to an empty Fields value, the field that key represents is part of the set. +// +// The exact format is defined in sigs.k8s.io/structured-merge-diff +// +k8s:deepcopy-gen=false +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +message FieldsV1 { + // Raw is the underlying serialization of this object. + // + // Deprecated: Direct access to this field is deprecated. Use GetRawBytes, GetRawString, SetRawBytes, SetRawString, GetRawReader, NewFieldsV1 instead. + optional bytes Raw = 1; +} + +// GetOptions is the standard query options to the standard REST get call. +message GetOptions { + // resourceVersion sets a constraint on what resource versions a request may be served from. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + optional string resourceVersion = 1; +} + +// GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying +// concepts during lookup stages without having partially valid types +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +message GroupKind { + optional string group = 1; + + optional string kind = 2; +} + +// GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying +// concepts during lookup stages without having partially valid types +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +message GroupResource { + optional string group = 1; + + optional string resource = 2; +} + +// GroupVersion contains the "group" and the "version", which uniquely identifies the API. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +message GroupVersion { + optional string group = 1; + + optional string version = 2; +} + +// GroupVersion contains the "group/version" and "version" string of a version. +// It is made a struct to keep extensibility. +message GroupVersionForDiscovery { + // groupVersion specifies the API group and version in the form "group/version" + optional string groupVersion = 1; + + // version specifies the version in the form of "version". This is to save + // the clients the trouble of splitting the GroupVersion. + optional string version = 2; +} + +// GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion +// to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +message GroupVersionKind { + optional string group = 1; + + optional string version = 2; + + optional string kind = 3; +} + +// GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion +// to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +message GroupVersionResource { + optional string group = 1; + + optional string version = 2; + + optional string resource = 3; +} + +// A label selector is a label query over a set of resources. The result of matchLabels and +// matchExpressions are ANDed. An empty label selector matches all objects. A null +// label selector matches no objects. +// +structType=atomic +message LabelSelector { + // matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + // map is equivalent to an element of matchExpressions, whose key field is "key", the + // operator is "In", and the values array contains only "value". The requirements are ANDed. + // +optional + map matchLabels = 1; + + // matchExpressions is a list of label selector requirements. The requirements are ANDed. + // +optional + // +listType=atomic + repeated LabelSelectorRequirement matchExpressions = 2; +} + +// A label selector requirement is a selector that contains values, a key, and an operator that +// relates the key and values. +message LabelSelectorRequirement { + // key is the label key that the selector applies to. + optional string key = 1; + + // operator represents a key's relationship to a set of values. + // Valid operators are In, NotIn, Exists and DoesNotExist. + optional string operator = 2; + + // values is an array of string values. If the operator is In or NotIn, + // the values array must be non-empty. If the operator is Exists or DoesNotExist, + // the values array must be empty. This array is replaced during a strategic + // merge patch. + // +optional + // +listType=atomic + repeated string values = 3; +} + +// List holds a list of objects, which may not be known by the server. +message List { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional ListMeta metadata = 1; + + // List of objects + repeated .k8s.io.apimachinery.pkg.runtime.RawExtension items = 2; +} + +// ListMeta describes metadata that synthetic resources must have, including lists and +// various status objects. A resource may have only one of {ObjectMeta, ListMeta}. +message ListMeta { + // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. + // +optional + optional string selfLink = 1; + + // String that identifies the server's internal version of this object that + // can be used by clients to determine when objects have changed. + // Value must be treated as opaque by clients and passed unmodified back to the server. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + // +optional + optional string resourceVersion = 2; + + // continue may be set if the user set a limit on the number of items returned, and indicates that + // the server has more data available. The value is opaque and may be used to issue another request + // to the endpoint that served this list to retrieve the next set of available objects. Continuing a + // consistent list may not be possible if the server configuration has changed or more than a few + // minutes have passed. The resourceVersion field returned when using this continue value will be + // identical to the value in the first response, unless you have received this token from an error + // message. + optional string continue = 3; + + // remainingItemCount is the number of subsequent items in the list which are not included in this + // list response. If the list request contained label or field selectors, then the number of + // remaining items is unknown and the field will be left unset and omitted during serialization. + // If the list is complete (either because it is not chunking or because this is the last chunk), + // then there are no more remaining items and this field will be left unset and omitted during + // serialization. + // Servers older than v1.15 do not set this field. + // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients + // should not rely on the remainingItemCount to be set or to be exact. + // +optional + optional int64 remainingItemCount = 4; + + // shardInfo is set when the list is a filtered subset of the full collection, + // as selected by a shard selector on the request. It echoes back the selector + // so clients can verify which shard they received and merge sharded responses. + // Clients should not cache sharded list responses as a full representation + // of the collection. + // + // This is an alpha field and requires enabling the ShardedListAndWatch feature gate. + // +featureGate=ShardedListAndWatch + // +optional + optional ShardInfo shardInfo = 5; +} + +// ListOptions is the query options to a standard REST list call. +message ListOptions { + // A selector to restrict the list of returned objects by their labels. + // Defaults to everything. + // +optional + optional string labelSelector = 1; + + // A selector to restrict the list of returned objects by their fields. + // Defaults to everything. + // +optional + optional string fieldSelector = 2; + + // Watch for changes to the described resources and return them as a stream of + // add, update, and remove notifications. Specify resourceVersion. + // +optional + optional bool watch = 3; + + // allowWatchBookmarks requests watch events with type "BOOKMARK". + // Servers that do not implement bookmarks may ignore this flag and + // bookmarks are sent at the server's discretion. Clients should not + // assume bookmarks are returned at any specific interval, nor may they + // assume the server will send any BOOKMARK event during a session. + // If this is not a watch, this field is ignored. + // +optional + optional bool allowWatchBookmarks = 9; + + // resourceVersion sets a constraint on what resource versions a request may be served from. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + optional string resourceVersion = 4; + + // resourceVersionMatch determines how resourceVersion is applied to list calls. + // It is highly recommended that resourceVersionMatch be set for list calls where + // resourceVersion is set + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + optional string resourceVersionMatch = 10; + + // Timeout for the list/watch call. + // This limits the duration of the call, regardless of any activity or inactivity. + // +optional + optional int64 timeoutSeconds = 5; + + // limit is a maximum number of responses to return for a list call. If more items exist, the + // server will set the `continue` field on the list metadata to a value that can be used with the + // same initial query to retrieve the next set of results. Setting a limit may return fewer than + // the requested amount of items (up to zero items) in the event all requested objects are + // filtered out and clients should only use the presence of the continue field to determine whether + // more results are available. Servers may choose not to support the limit argument and will return + // all of the available results. If limit is specified and the continue field is empty, clients may + // assume that no more results are available. This field is not supported if watch is true. + // + // The server guarantees that the objects returned when using continue will be identical to issuing + // a single list call without a limit - that is, no objects created, modified, or deleted after the + // first request is issued will be included in any subsequent continued requests. This is sometimes + // referred to as a consistent snapshot, and ensures that a client that is using limit to receive + // smaller chunks of a very large result can ensure they see all possible objects. If objects are + // updated during a chunked list the version of the object that was present at the time the first list + // result was calculated is returned. + optional int64 limit = 7; + + // The continue option should be set when retrieving more results from the server. Since this value is + // server defined, clients may only use the continue value from a previous query result with identical + // query parameters (except for the value of continue) and the server may reject a continue value it + // does not recognize. If the specified continue value is no longer valid whether due to expiration + // (generally five to fifteen minutes) or a configuration change on the server, the server will + // respond with a 410 ResourceExpired error together with a continue token. If the client needs a + // consistent list, it must restart their list without the continue field. Otherwise, the client may + // send another list request with the token received with the 410 error, the server will respond with + // a list starting from the next key, but from the latest snapshot, which is inconsistent from the + // previous list results - objects that are created, modified, or deleted after the first list request + // will be included in the response, as long as their keys are after the "next key". + // + // This field is not supported when watch is true. Clients may start a watch from the last + // resourceVersion value returned by the server and not miss any modifications. + optional string continue = 8; + + // `sendInitialEvents=true` may be set together with `watch=true`. + // In that case, the watch stream will begin with synthetic events to + // produce the current state of objects in the collection. Once all such + // events have been sent, a synthetic "Bookmark" event will be sent. + // The bookmark will report the ResourceVersion (RV) corresponding to the + // set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. + // Afterwards, the watch stream will proceed as usual, sending watch events + // corresponding to changes (subsequent to the RV) to objects watched. + // + // When `sendInitialEvents` option is set, we require `resourceVersionMatch` + // option to also be set. The semantic of the watch request is as following: + // - `resourceVersionMatch` = NotOlderThan + // is interpreted as "data at least as new as the provided `resourceVersion`" + // and the bookmark event is send when the state is synced + // to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + // If `resourceVersion` is unset, this is interpreted as "consistent read" and the + // bookmark event is send when the state is synced at least to the moment + // when request started being processed. + // - `resourceVersionMatch` set to any other value or unset + // Invalid error is returned. + // + // Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward + // compatibility reasons) and to false otherwise. + // +optional + optional bool sendInitialEvents = 11; + + // shardSelector restricts the list of returned objects using a CEL-based + // shard selector expression. The format uses the shardRange() function + // combined with || (logical OR) to specify one or more hash ranges: + // + // shardRange(object.metadata.uid, '0x0', '0x8000000000000000') + // shardRange(object.metadata.uid, '0x0', '0x8000000000000000') || shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000') + // + // Field paths use CEL-style object-rooted syntax (e.g. "object.metadata.uid"), + // NOT the fieldSelector format ("metadata.uid"). Currently supported paths: + // - object.metadata.uid + // - object.metadata.namespace + // + // hexStart and hexEnd are single-quoted CEL string literals with a '0x' prefix, + // defining the inclusive lower and exclusive upper bounds over the 64-bit FNV-1a + // hash space. The full range is [0x0, 0x10000000000000000), where the exclusive + // upper bound equals 2^64. + // + // Examples: + // 2-shard split: + // shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000') + // shard 1: shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000') + // 4-shard split: + // shard 0: shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000') + // shard 1: shardRange(object.metadata.uid, '0x4000000000000000', '0x8000000000000000') + // shard 2: shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000') + // shard 3: shardRange(object.metadata.uid, '0xc000000000000000', '0x10000000000000000') + // + // This is an alpha field and requires enabling the ShardedListAndWatch feature gate. + // +featureGate=ShardedListAndWatch + // +optional + optional string shardSelector = 15; +} + +// ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource +// that the fieldset applies to. +message ManagedFieldsEntry { + // Manager is an identifier of the workflow managing these fields. + optional string manager = 1; + + // Operation is the type of operation which lead to this ManagedFieldsEntry being created. + // The only valid values for this field are 'Apply' and 'Update'. + // +k8s:alpha(since: "1.37")=+k8s:required + optional string operation = 2; + + // APIVersion defines the version of this resource that this field set + // applies to. The format is "group/version" just like the top-level + // APIVersion field. It is necessary to track the version of a field + // set because it cannot be automatically converted. + optional string apiVersion = 3; + + // Time is the timestamp of when the ManagedFields entry was added. The + // timestamp will also be updated if a field is added, the manager + // changes any of the owned fields value or removes a field. The + // timestamp does not update when a field is removed from the entry + // because another manager took it over. + // +optional + optional Time time = 4; + + // FieldsType is the discriminator for the different fields format and version. + // There is currently only one possible value: "FieldsV1" + optional string fieldsType = 6; + + // FieldsV1 holds the first JSON version format as described in the "FieldsV1" type. + // +optional + optional FieldsV1 fieldsV1 = 7; + + // Subresource is the name of the subresource used to update that object, or + // empty string if the object was updated through the main resource. The + // value of this field is used to distinguish between managers, even if they + // share the same name. For example, a status update will be distinct from a + // regular update using the same manager name. + // Note that the APIVersion field is not related to the Subresource field and + // it always corresponds to the version of the main resource. + optional string subresource = 8; +} + +// MicroTime is version of Time with microsecond level precision. +// +// +protobuf.options.marshal=false +// +protobuf.as=Timestamp +// +protobuf.options.(gogoproto.goproto_stringer)=false +message MicroTime { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + optional int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. This field may be limited in precision depending on context. + optional int32 nanos = 2; +} + +// ObjectMeta is metadata that all persisted resources must have, which includes all objects +// users must create. +message ObjectMeta { + // Name must be unique within a namespace. Is required when creating resources, although + // some resources may allow a client to request the generation of an appropriate name + // automatically. Name is primarily intended for creation idempotence and configuration + // definition. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names + // +optional + optional string name = 1; + + // GenerateName is an optional prefix, used by the server, to generate a unique + // name ONLY IF the Name field has not been provided. + // If this field is used, the name returned to the client will be different + // than the name passed. This value will also be combined with a unique suffix. + // The provided value has the same validation rules as the Name field, + // and may be truncated by the length of the suffix required to make the value + // unique on the server. + // + // If this field is specified and the generated name exists, the server will return a 409. + // + // Applied only if Name is not specified. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency + // +optional + optional string generateName = 2; + + // Namespace defines the space within which each name must be unique. An empty namespace is + // equivalent to the "default" namespace, but "default" is the canonical representation. + // Not all objects are required to be scoped to a namespace - the value of this field for + // those objects will be empty. + // + // Must be a DNS_LABEL. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces + // +optional + optional string namespace = 3; + + // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. + // +optional + optional string selfLink = 4; + + // UID is the unique in time and space value for this object. It is typically generated by + // the server on successful creation of a resource and is not allowed to change on PUT + // operations. + // + // Populated by the system. + // Read-only. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids + // +optional + // +k8s:alpha(since: "1.37")=+k8s:optional + // +k8s:alpha(since: "1.37")=+k8s:immutable + optional string uid = 5; + + // An opaque value that represents the internal version of this object that can + // be used by clients to determine when objects have changed. May be used for optimistic + // concurrency, change detection, and the watch operation on a resource or set of resources. + // Clients must treat these values as opaque and passed unmodified back to the server. + // They may only be valid for a particular resource or set of resources. + // + // Populated by the system. + // Read-only. + // Value must be treated as opaque by clients and . + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + // +optional + optional string resourceVersion = 6; + + // A sequence number representing a specific generation of the desired state. + // Populated by the system. Read-only. + // +optional + // +k8s:alpha(since: "1.37")=+k8s:optional + // +k8s:alpha(since: "1.37")=+k8s:minimum=0 + optional int64 generation = 7; + + // CreationTimestamp is a timestamp representing the server time when this object was + // created. It is not guaranteed to be set in happens-before order across separate operations. + // Clients may not set this value. It is represented in RFC3339 form and is in UTC. + // + // Populated by the system. + // Read-only. + // Null for lists. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + // +k8s:alpha(since: "1.37")=+k8s:immutable + optional Time creationTimestamp = 8; + + // DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This + // field is set by the server when a graceful deletion is requested by the user, and is not + // directly settable by a client. The resource is expected to be deleted (no longer visible + // from resource lists, and not reachable by name) after the time in this field, once the + // finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. + // Once the deletionTimestamp is set, this value may not be unset or be set further into the + // future, although it may be shortened or the resource may be deleted prior to this time. + // For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react + // by sending a graceful termination signal to the containers in the pod. After that 30 seconds, + // the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, + // remove the pod from the API. In the presence of network partitions, this object may still + // exist after this timestamp, until an administrator or automated process can determine the + // resource is fully terminated. + // If not set, graceful deletion of the object has not been requested. + // + // Populated by the system when a graceful deletion is requested. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + // +k8s:alpha(since: "1.37")=+k8s:optional + // +k8s:alpha(since: "1.37")=+k8s:immutable + optional Time deletionTimestamp = 9; + + // Number of seconds allowed for this object to gracefully terminate before + // it will be removed from the system. Only set when deletionTimestamp is also set. + // May only be shortened. + // Read-only. + // +optional + // +k8s:alpha(since: "1.37")=+k8s:optional + // +k8s:alpha(since: "1.37")=+k8s:immutable + optional int64 deletionGracePeriodSeconds = 10; + + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. May match selectors of replication controllers + // and services. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels + // +optional + map labels = 11; + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. They are not + // queryable and should be preserved when modifying objects. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations + // +optional + map annotations = 12; + + // List of objects depended by this object. If ALL objects in the list have + // been deleted, this object will be garbage collected. If this object is managed by a controller, + // then an entry in this list will point to this controller, with the controller field set to true. + // There cannot be more than one managing controller. + // +optional + // +patchMergeKey=uid + // +patchStrategy=merge + // +listType=map + // +listMapKey=uid + // +k8s:alpha(since:"1.37")=+k8s:optional + repeated OwnerReference ownerReferences = 13; + + // Must be empty before the object is deleted from the registry. Each entry + // is an identifier for the responsible component that will remove the entry + // from the list. If the deletionTimestamp of the object is non-nil, entries + // in this list can only be removed. + // Finalizers may be processed and removed in any order. Order is NOT enforced + // because it introduces significant risk of stuck finalizers. + // finalizers is a shared field, any actor with permission can reorder it. + // If the finalizer list is processed in order, then this can lead to a situation + // in which the component responsible for the first finalizer in the list is + // waiting for a signal (field value, external system, or other) produced by a + // component responsible for a finalizer later in the list, resulting in a deadlock. + // Without enforced ordering finalizers are free to order amongst themselves and + // are not vulnerable to ordering changes in the list. + // +optional + // +patchStrategy=merge + // +listType=set + repeated string finalizers = 14; + + // ManagedFields maps workflow-id and version to the set of fields + // that are managed by that workflow. This is mostly for internal + // housekeeping, and users typically shouldn't need to set or + // understand this field. A workflow can be the user's name, a + // controller's name, or the name of a specific apply path like + // "ci-cd". The set of fields is always in the version that the + // workflow used when modifying the object. + // + // +optional + // +listType=atomic + // +k8s:alpha(since: "1.37")=+k8s:optional + repeated ManagedFieldsEntry managedFields = 17; +} + +// OwnerReference contains enough information to let you identify an owning +// object. An owning object must be in the same namespace as the dependent, or +// be cluster-scoped, so there is no namespace field. +// +structType=atomic +message OwnerReference { + // API version of the referent. + // +k8s:alpha(since:"1.37")=+k8s:required + optional string apiVersion = 5; + + // Kind of the referent. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +k8s:alpha(since:"1.37")=+k8s:required + optional string kind = 1; + + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names + // +k8s:alpha(since:"1.37")=+k8s:required + optional string name = 3; + + // UID of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids + // +k8s:alpha(since:"1.37")=+k8s:required + optional string uid = 4; + + // If true, this reference points to the managing controller. + // +optional + optional bool controller = 6; + + // If true, AND if the owner has the "foregroundDeletion" finalizer, then + // the owner cannot be deleted from the key-value store until this + // reference is removed. + // See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion + // for how the garbage collector interacts with this field and enforces the foreground deletion. + // Defaults to false. + // To set this field, a user needs "delete" permission of the owner, + // otherwise 422 (Unprocessable Entity) will be returned. + // +optional + optional bool blockOwnerDeletion = 7; +} + +// PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients +// to get access to a particular ObjectMeta schema without knowing the details of the version. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +message PartialObjectMetadata { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + // +k8s:opaqueType + optional ObjectMeta metadata = 1; +} + +// PartialObjectMetadataList contains a list of objects containing only their metadata +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +message PartialObjectMetadataList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional ListMeta metadata = 1; + + // items contains each of the included items. + repeated PartialObjectMetadata items = 2; +} + +// Patch is provided to give a concrete name and type to the Kubernetes PATCH request body. +message Patch { +} + +// PatchOptions may be provided when patching an API object. +// PatchOptions is meant to be a superset of UpdateOptions. +message PatchOptions { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + // +listType=atomic + repeated string dryRun = 1; + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. Force + // flag must be unset for non-apply patch requests. + // +optional + optional bool force = 2; + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. This + // field is required for apply requests + // (application/apply-patch) but optional for non-apply patch + // types (JsonPatch, MergePatch, StrategicMergePatch). + // +optional + optional string fieldManager = 3; + + // fieldValidation instructs the server on how to handle + // objects in the request (POST/PUT/PATCH) containing unknown + // or duplicate fields. Valid values are: + // - Ignore: This will ignore any unknown fields that are silently + // dropped from the object, and will ignore all but the last duplicate + // field that the decoder encounters. This is the default behavior + // prior to v1.23. + // - Warn: This will send a warning via the standard warning response + // header for each unknown field that is dropped from the object, and + // for each duplicate field that is encountered. The request will + // still succeed if there are no other errors, and will only persist + // the last of any duplicate fields. This is the default in v1.23+ + // - Strict: This will fail the request with a BadRequest error if + // any unknown fields would be dropped from the object, or if any + // duplicate fields are present. The error returned from the server + // will contain all unknown and duplicate fields encountered. + // +optional + optional string fieldValidation = 4; +} + +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +message Preconditions { + // Specifies the target UID. + // +optional + optional string uid = 1; + + // Specifies the target ResourceVersion + // +optional + optional string resourceVersion = 2; +} + +// RootPaths lists the paths available at root. +// For example: "/healthz", "/apis". +message RootPaths { + // paths are the paths available at root. + // +listType=atomic + repeated string paths = 1; +} + +// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match. +message ServerAddressByClientCIDR { + // The CIDR with which clients can match their IP to figure out the server address that they should use. + optional string clientCIDR = 1; + + // Address of this server, suitable for a client that matches the above CIDR. + // This can be a hostname, hostname:port, IP or IP:port. + optional string serverAddress = 2; +} + +// ShardInfo describes the shard selector that was applied to produce a list response. +// Its presence on a list response indicates the list is a filtered subset. +message ShardInfo { + // selector is the shard selector string from the request, echoed back so clients + // can verify which shard they received and merge responses from multiple shards. + // +required + optional string selector = 1; +} + +// Status is a return value for calls that don't return other objects. +message Status { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional ListMeta metadata = 1; + + // Status of the operation. + // One of: "Success" or "Failure". + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + optional string status = 2; + + // A human-readable description of the status of this operation. + // +optional + optional string message = 3; + + // A machine-readable description of why this operation is in the + // "Failure" status. If this value is empty there + // is no information available. A Reason clarifies an HTTP status + // code but does not override it. + // +optional + optional string reason = 4; + + // Extended data associated with the reason. Each reason may define its + // own extended details. This field is optional and the data returned + // is not guaranteed to conform to any schema except that defined by + // the reason type. + // +optional + optional StatusDetails details = 5; + + // Suggested HTTP return code for this status, 0 if not set. + // +optional + optional int32 code = 6; +} + +// StatusCause provides more information about an api.Status failure, including +// cases when multiple errors are encountered. +message StatusCause { + // A machine-readable description of the cause of the error. If this value is + // empty there is no information available. + // +optional + optional string reason = 1; + + // A human-readable description of the cause of the error. This field may be + // presented as-is to a reader. + // +optional + optional string message = 2; + + // The field of the resource that has caused this error, as named by its JSON + // serialization. May include dot and postfix notation for nested attributes. + // Arrays are zero-indexed. Fields may appear more than once in an array of + // causes due to fields having multiple errors. + // Optional. + // + // Examples: + // "name" - the field "name" on the current resource + // "items[0].name" - the field "name" on the first array entry in "items" + // +optional + optional string field = 3; +} + +// StatusDetails is a set of additional properties that MAY be set by the +// server to provide additional information about a response. The Reason +// field of a Status object defines what attributes will be set. Clients +// must ignore fields that do not match the defined type of each attribute, +// and should assume that any attribute may be empty, invalid, or under +// defined. +message StatusDetails { + // The name attribute of the resource associated with the status StatusReason + // (when there is a single name which can be described). + // +optional + optional string name = 1; + + // The group attribute of the resource associated with the status StatusReason. + // +optional + optional string group = 2; + + // The kind attribute of the resource associated with the status StatusReason. + // On some operations may differ from the requested resource Kind. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional string kind = 3; + + // UID of the resource. + // (when there is a single resource which can be described). + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids + // +optional + optional string uid = 6; + + // The Causes array includes more details associated with the StatusReason + // failure. Not all StatusReasons may provide detailed causes. + // +optional + // +listType=atomic + repeated StatusCause causes = 4; + + // If specified, the time in seconds before the operation should be retried. Some errors may indicate + // the client must take an alternate action - for those errors this field may indicate how long to wait + // before taking the alternate action. + // +optional + optional int32 retryAfterSeconds = 5; +} + +// TableOptions are used when a Table is requested by the caller. +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +message TableOptions { + // includeObject decides whether to include each object along with its columnar information. + // Specifying "None" will return no object, specifying "Object" will return the full object contents, and + // specifying "Metadata" (the default) will return the object's metadata in the PartialObjectMetadata kind + // in version v1beta1 of the meta.k8s.io API group. + optional string includeObject = 1; +} + +// Time is a wrapper around time.Time which supports correct +// marshaling to YAML and JSON. Wrappers are provided for many +// of the factory methods that the time package offers. +// +// +protobuf.options.marshal=false +// +protobuf.as=Timestamp +// +protobuf.options.(gogoproto.goproto_stringer)=false +message Time { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + optional int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. This field may be limited in precision depending on context. + optional int32 nanos = 2; +} + +// Timestamp is a struct that is equivalent to Time, but intended for +// protobuf marshalling/unmarshalling. It is generated into a serialization +// that matches Time. Do not use in Go structs. +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + optional int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. This field may be limited in precision depending on context. + optional int32 nanos = 2; +} + +// TypeMeta describes an individual object in an API response or request +// with strings representing the type of the object and its API schema version. +// Structures that are versioned or persisted should inline TypeMeta. +// +// +k8s:deepcopy-gen=false +message TypeMeta { + // Kind is a string value representing the REST resource this object represents. + // Servers may infer this from the endpoint the client submits requests to. + // Cannot be updated. + // In CamelCase. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional string kind = 1; + + // APIVersion defines the versioned schema of this representation of an object. + // Servers should convert recognized schemas to the latest internal value, and + // may reject unrecognized values. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + // +optional + optional string apiVersion = 2; +} + +// UpdateOptions may be provided when updating an API object. +// All fields in UpdateOptions should also be present in PatchOptions. +message UpdateOptions { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + // +listType=atomic + repeated string dryRun = 1; + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. + // +optional + optional string fieldManager = 2; + + // fieldValidation instructs the server on how to handle + // objects in the request (POST/PUT/PATCH) containing unknown + // or duplicate fields. Valid values are: + // - Ignore: This will ignore any unknown fields that are silently + // dropped from the object, and will ignore all but the last duplicate + // field that the decoder encounters. This is the default behavior + // prior to v1.23. + // - Warn: This will send a warning via the standard warning response + // header for each unknown field that is dropped from the object, and + // for each duplicate field that is encountered. The request will + // still succeed if there are no other errors, and will only persist + // the last of any duplicate fields. This is the default in v1.23+ + // - Strict: This will fail the request with a BadRequest error if + // any unknown fields would be dropped from the object, or if any + // duplicate fields are present. The error returned from the server + // will contain all unknown and duplicate fields encountered. + // +optional + optional string fieldValidation = 3; +} + +// Verbs masks the value so protobuf can generate +// +// +protobuf.nullable=true +// +protobuf.options.(gogoproto.goproto_stringer)=false +message Verbs { + // items, if empty, will result in an empty slice + + repeated string items = 1; +} + +// Event represents a single event to a watched resource. +// +// +protobuf=true +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +message WatchEvent { + optional string type = 1; + + // Object is: + // * If Type is Added or Modified: the new state of the object. + // * If Type is Deleted: the state of the object immediately before deletion. + // * If Type is Error: *Status is recommended; other types may make sense + // depending on context. + optional .k8s.io.apimachinery.pkg.runtime.RawExtension object = 2; +} + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/group_version.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/group_version.go new file mode 100644 index 0000000000..fc9e521e21 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/group_version.go @@ -0,0 +1,157 @@ +/* +Copyright 2015 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 v1 + +import ( + "encoding/json" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying +// concepts during lookup stages without having partially valid types +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +type GroupResource struct { + Group string `json:"group" protobuf:"bytes,1,opt,name=group"` + Resource string `json:"resource" protobuf:"bytes,2,opt,name=resource"` +} + +func (gr *GroupResource) String() string { + if gr == nil { + return "" + } + if len(gr.Group) == 0 { + return gr.Resource + } + return gr.Resource + "." + gr.Group +} + +// GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion +// to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +type GroupVersionResource struct { + Group string `json:"group" protobuf:"bytes,1,opt,name=group"` + Version string `json:"version" protobuf:"bytes,2,opt,name=version"` + Resource string `json:"resource" protobuf:"bytes,3,opt,name=resource"` +} + +func (gvr *GroupVersionResource) String() string { + if gvr == nil { + return "" + } + return strings.Join([]string{gvr.Group, "/", gvr.Version, ", Resource=", gvr.Resource}, "") +} + +// GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying +// concepts during lookup stages without having partially valid types +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +type GroupKind struct { + Group string `json:"group" protobuf:"bytes,1,opt,name=group"` + Kind string `json:"kind" protobuf:"bytes,2,opt,name=kind"` +} + +func (gk *GroupKind) String() string { + if gk == nil { + return "" + } + if len(gk.Group) == 0 { + return gk.Kind + } + return gk.Kind + "." + gk.Group +} + +// GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion +// to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +type GroupVersionKind struct { + Group string `json:"group" protobuf:"bytes,1,opt,name=group"` + Version string `json:"version" protobuf:"bytes,2,opt,name=version"` + Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"` +} + +func (gvk GroupVersionKind) String() string { + return gvk.Group + "/" + gvk.Version + ", Kind=" + gvk.Kind +} + +// GroupVersion contains the "group" and the "version", which uniquely identifies the API. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +type GroupVersion struct { + Group string `json:"group" protobuf:"bytes,1,opt,name=group"` + Version string `json:"version" protobuf:"bytes,2,opt,name=version"` +} + +// Empty returns true if group and version are empty +func (gv GroupVersion) Empty() bool { + return len(gv.Group) == 0 && len(gv.Version) == 0 +} + +// String puts "group" and "version" into a single "group/version" string. For the legacy v1 +// it returns "v1". +func (gv GroupVersion) String() string { + // special case the internal apiVersion for the legacy kube types + if gv.Empty() { + return "" + } + + // special case of "v1" for backward compatibility + if len(gv.Group) == 0 && gv.Version == "v1" { + return gv.Version + } + if len(gv.Group) > 0 { + return gv.Group + "/" + gv.Version + } + return gv.Version +} + +// MarshalJSON implements the json.Marshaller interface. +func (gv GroupVersion) MarshalJSON() ([]byte, error) { + s := gv.String() + if strings.Count(s, "/") > 1 { + return []byte{}, fmt.Errorf("illegal GroupVersion %v: contains more than one /", s) + } + return json.Marshal(s) +} + +func (gv *GroupVersion) unmarshal(value []byte) error { + var s string + if err := json.Unmarshal(value, &s); err != nil { + return err + } + parsed, err := schema.ParseGroupVersion(s) + if err != nil { + return err + } + gv.Group, gv.Version = parsed.Group, parsed.Version + return nil +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (gv *GroupVersion) UnmarshalJSON(value []byte) error { + return gv.unmarshal(value) +} + +// UnmarshalTEXT implements the Ugorji's encoding.TextUnmarshaler interface. +func (gv *GroupVersion) UnmarshalText(value []byte) error { + return gv.unmarshal(value) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/group_version_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/group_version_test.go new file mode 100644 index 0000000000..c50a1902ec --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/group_version_test.go @@ -0,0 +1,78 @@ +/* +Copyright 2015 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 v1 + +import ( + gojson "encoding/json" + "reflect" + "testing" + + utiljson "k8s.io/apimachinery/pkg/util/json" +) + +type GroupVersionHolder struct { + GV GroupVersion `json:"val"` +} + +func TestGroupVersionUnmarshalJSON(t *testing.T) { + cases := []struct { + input []byte + expect GroupVersion + }{ + {[]byte(`{"val": "v1"}`), GroupVersion{"", "v1"}}, + {[]byte(`{"val": "apps/v1"}`), GroupVersion{"apps", "v1"}}, + } + + for _, c := range cases { + var result GroupVersionHolder + // test golang lib's JSON codec + if err := gojson.Unmarshal([]byte(c.input), &result); err != nil { + t.Errorf("JSON codec failed to unmarshal input '%v': %v", c.input, err) + } + if !reflect.DeepEqual(result.GV, c.expect) { + t.Errorf("JSON codec failed to unmarshal input '%s': expected %+v, got %+v", c.input, c.expect, result.GV) + } + // test the utiljson codec + if err := utiljson.Unmarshal(c.input, &result); err != nil { + t.Errorf("util/json codec failed to unmarshal input '%v': %v", c.input, err) + } + if !reflect.DeepEqual(result.GV, c.expect) { + t.Errorf("util/json codec failed to unmarshal input '%s': expected %+v, got %+v", c.input, c.expect, result.GV) + } + } +} + +func TestGroupVersionMarshalJSON(t *testing.T) { + cases := []struct { + input GroupVersion + expect []byte + }{ + {GroupVersion{"", "v1"}, []byte(`{"val":"v1"}`)}, + {GroupVersion{"apps", "v1"}, []byte(`{"val":"apps/v1"}`)}, + } + + for _, c := range cases { + input := GroupVersionHolder{c.input} + result, err := gojson.Marshal(&input) + if err != nil { + t.Errorf("Failed to marshal input '%v': %v", input, err) + } + if !reflect.DeepEqual(result, c.expect) { + t.Errorf("Failed to marshal input '%+v': expected: %s, got: %s", input, c.expect, result) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go new file mode 100644 index 0000000000..45ed1e9891 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/helpers.go @@ -0,0 +1,386 @@ +/* +Copyright 2016 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 v1 + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + utiljson "k8s.io/apimachinery/pkg/util/json" +) + +// LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements +// labels.Selector +// Note: This function should be kept in sync with the selector methods in pkg/labels/selector.go +func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) { + if ps == nil { + return labels.Nothing(), nil + } + if len(ps.MatchLabels)+len(ps.MatchExpressions) == 0 { + return labels.Everything(), nil + } + requirements := make([]labels.Requirement, 0, len(ps.MatchLabels)+len(ps.MatchExpressions)) + for k, v := range ps.MatchLabels { + r, err := labels.NewRequirement(k, selection.Equals, []string{v}) + if err != nil { + return nil, err + } + requirements = append(requirements, *r) + } + for _, expr := range ps.MatchExpressions { + var op selection.Operator + switch expr.Operator { + case LabelSelectorOpIn: + op = selection.In + case LabelSelectorOpNotIn: + op = selection.NotIn + case LabelSelectorOpExists: + op = selection.Exists + case LabelSelectorOpDoesNotExist: + op = selection.DoesNotExist + default: + return nil, fmt.Errorf("%q is not a valid label selector operator", expr.Operator) + } + r, err := labels.NewRequirement(expr.Key, op, append([]string(nil), expr.Values...)) + if err != nil { + return nil, err + } + requirements = append(requirements, *r) + } + selector := labels.NewSelector() + selector = selector.Add(requirements...) + return selector, nil +} + +// LabelSelectorAsMap converts the LabelSelector api type into a map of strings, ie. the +// original structure of a label selector. Operators that cannot be converted into plain +// labels (Exists, DoesNotExist, NotIn, and In with more than one value) will result in +// an error. +func LabelSelectorAsMap(ps *LabelSelector) (map[string]string, error) { + if ps == nil { + return nil, nil + } + selector := map[string]string{} + for k, v := range ps.MatchLabels { + selector[k] = v + } + for _, expr := range ps.MatchExpressions { + switch expr.Operator { + case LabelSelectorOpIn: + if len(expr.Values) != 1 { + return selector, fmt.Errorf("operator %q without a single value cannot be converted into the old label selector format", expr.Operator) + } + // Should we do anything in case this will override a previous key-value pair? + selector[expr.Key] = expr.Values[0] + case LabelSelectorOpNotIn, LabelSelectorOpExists, LabelSelectorOpDoesNotExist: + return selector, fmt.Errorf("operator %q cannot be converted into the old label selector format", expr.Operator) + default: + return selector, fmt.Errorf("%q is not a valid selector operator", expr.Operator) + } + } + return selector, nil +} + +// ParseToLabelSelector parses a string representing a selector into a LabelSelector object. +// Note: This function should be kept in sync with the parser in pkg/labels/selector.go +func ParseToLabelSelector(selector string) (*LabelSelector, error) { + reqs, err := labels.ParseToRequirements(selector) + if err != nil { + return nil, fmt.Errorf("couldn't parse the selector string \"%s\": %v", selector, err) + } + + labelSelector := &LabelSelector{ + MatchLabels: map[string]string{}, + MatchExpressions: []LabelSelectorRequirement{}, + } + for _, req := range reqs { + var op LabelSelectorOperator + switch req.Operator() { + case selection.Equals, selection.DoubleEquals: + vals := req.Values() + if vals.Len() != 1 { + return nil, fmt.Errorf("equals operator must have exactly one value") + } + val, ok := vals.PopAny() + if !ok { + return nil, fmt.Errorf("equals operator has exactly one value but it cannot be retrieved") + } + labelSelector.MatchLabels[req.Key()] = val + continue + case selection.In: + op = LabelSelectorOpIn + case selection.NotIn: + op = LabelSelectorOpNotIn + case selection.Exists: + op = LabelSelectorOpExists + case selection.DoesNotExist: + op = LabelSelectorOpDoesNotExist + case selection.GreaterThan, selection.LessThan: + // Adding a separate case for these operators to indicate that this is deliberate + return nil, fmt.Errorf("%q isn't supported in label selectors", req.Operator()) + default: + return nil, fmt.Errorf("%q is not a valid label selector operator", req.Operator()) + } + labelSelector.MatchExpressions = append(labelSelector.MatchExpressions, LabelSelectorRequirement{ + Key: req.Key(), + Operator: op, + Values: req.Values().List(), + }) + } + return labelSelector, nil +} + +// SetAsLabelSelector converts the labels.Set object into a LabelSelector api object. +func SetAsLabelSelector(ls labels.Set) *LabelSelector { + if ls == nil { + return nil + } + + selector := &LabelSelector{ + MatchLabels: make(map[string]string, len(ls)), + } + for label, value := range ls { + selector.MatchLabels[label] = value + } + + return selector +} + +// FormatLabelSelector convert labelSelector into plain string +func FormatLabelSelector(labelSelector *LabelSelector) string { + selector, err := LabelSelectorAsSelector(labelSelector) + if err != nil { + return "" + } + + l := selector.String() + if len(l) == 0 { + l = "" + } + return l +} + +func ExtractGroupVersions(l *APIGroupList) []string { + var groupVersions []string + for _, g := range l.Groups { + for _, gv := range g.Versions { + groupVersions = append(groupVersions, gv.GroupVersion) + } + } + return groupVersions +} + +// HasAnnotation returns a bool if passed in annotation exists +func HasAnnotation(obj ObjectMeta, ann string) bool { + _, found := obj.Annotations[ann] + return found +} + +// SetMetaDataAnnotation sets the annotation and value +func SetMetaDataAnnotation(obj *ObjectMeta, ann string, value string) { + if obj.Annotations == nil { + obj.Annotations = make(map[string]string) + } + obj.Annotations[ann] = value +} + +// HasLabel returns a bool if passed in label exists +func HasLabel(obj ObjectMeta, label string) bool { + _, found := obj.Labels[label] + return found +} + +// SetMetaDataLabel sets the label and value +func SetMetaDataLabel(obj *ObjectMeta, label string, value string) { + if obj.Labels == nil { + obj.Labels = make(map[string]string) + } + obj.Labels[label] = value +} + +// SingleObject returns a ListOptions for watching a single object. +func SingleObject(meta ObjectMeta) ListOptions { + return ListOptions{ + FieldSelector: fields.OneTermEqualSelector("metadata.name", meta.Name).String(), + ResourceVersion: meta.ResourceVersion, + } +} + +// NewDeleteOptions returns a DeleteOptions indicating the resource should +// be deleted within the specified grace period. Use zero to indicate +// immediate deletion. If you would prefer to use the default grace period, +// use &metav1.DeleteOptions{} directly. +func NewDeleteOptions(grace int64) *DeleteOptions { + return &DeleteOptions{GracePeriodSeconds: &grace} +} + +// NewPreconditionDeleteOptions returns a DeleteOptions with a UID precondition set. +func NewPreconditionDeleteOptions(uid string) *DeleteOptions { + u := types.UID(uid) + p := Preconditions{UID: &u} + return &DeleteOptions{Preconditions: &p} +} + +// NewUIDPreconditions returns a Preconditions with UID set. +func NewUIDPreconditions(uid string) *Preconditions { + u := types.UID(uid) + return &Preconditions{UID: &u} +} + +// NewRVDeletionPrecondition returns a DeleteOptions with a ResourceVersion precondition set. +func NewRVDeletionPrecondition(rv string) *DeleteOptions { + p := Preconditions{ResourceVersion: &rv} + return &DeleteOptions{Preconditions: &p} +} + +// HasObjectMetaSystemFieldValues returns true if fields that are managed by the system on ObjectMeta have values. +func HasObjectMetaSystemFieldValues(meta Object) bool { + return !meta.GetCreationTimestamp().Time.IsZero() || + len(meta.GetUID()) != 0 +} + +// ResetObjectMetaForStatus forces the meta fields for a status update to match the meta fields +// for a pre-existing object. This is opt-in for new objects with Status subresource. +func ResetObjectMetaForStatus(meta, existingMeta Object) { + meta.SetDeletionTimestamp(existingMeta.GetDeletionTimestamp()) + meta.SetGeneration(existingMeta.GetGeneration()) + meta.SetSelfLink(existingMeta.GetSelfLink()) + meta.SetLabels(existingMeta.GetLabels()) + meta.SetAnnotations(existingMeta.GetAnnotations()) + meta.SetFinalizers(existingMeta.GetFinalizers()) + meta.SetOwnerReferences(existingMeta.GetOwnerReferences()) + // managedFields must be preserved since it's been modified to + // track changed fields in the status update. + //meta.SetManagedFields(existingMeta.GetManagedFields()) +} + +// MarshalJSON implements json.Marshaler +// MarshalJSON may get called on pointers or values, so implement MarshalJSON on value. +// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go +func (f FieldsV1) MarshalJSON() ([]byte, error) { + raw := f.GetRawBytes() + if len(raw) == 0 { + return []byte("null"), nil + } + if f.getContentType() == fieldsV1InvalidOrValidCBORObject { + var u map[string]interface{} + if err := cbor.Unmarshal(raw, &u); err != nil { + return nil, fmt.Errorf("metav1.FieldsV1 cbor invalid: %w", err) + } + return utiljson.Marshal(u) + } + return raw, nil +} + +// UnmarshalJSON implements json.Unmarshaler +func (f *FieldsV1) UnmarshalJSON(b []byte) error { + if f == nil { + return errors.New("metav1.FieldsV1: UnmarshalJSON on nil pointer") + } + if !bytes.Equal(b, []byte("null")) { + f.SetRawBytes(b) + } + return nil +} + +var _ json.Marshaler = FieldsV1{} +var _ json.Unmarshaler = &FieldsV1{} + +func (f FieldsV1) MarshalCBOR() ([]byte, error) { + raw := f.GetRawBytes() + if len(raw) == 0 { + return cbor.Marshal(nil) + } + if f.getContentType() == fieldsV1InvalidOrValidJSONObject { + var u map[string]interface{} + if err := utiljson.Unmarshal(raw, &u); err != nil { + return nil, fmt.Errorf("metav1.FieldsV1 json invalid: %w", err) + } + return cbor.Marshal(u) + } + return raw, nil +} + +var cborNull = []byte{0xf6} + +func (f *FieldsV1) UnmarshalCBOR(b []byte) error { + if f == nil { + return errors.New("metav1.FieldsV1: UnmarshalCBOR on nil pointer") + } + if !bytes.Equal(b, cborNull) { + f.SetRawBytes(b) + } + return nil +} + +const ( + // fieldsV1InvalidOrEmpty indicates that a FieldsV1 either contains no raw bytes or its raw + // bytes don't represent an allowable value in any supported encoding. + fieldsV1InvalidOrEmpty = iota + + // fieldsV1InvalidOrValidJSONObject indicates that a FieldV1 either contains raw bytes that + // are a valid JSON encoding of an allowable value or don't represent an allowable value in + // any supported encoding. + fieldsV1InvalidOrValidJSONObject + + // fieldsV1InvalidOrValidCBORObject indicates that a FieldV1 either contains raw bytes that + // are a valid CBOR encoding of an allowable value or don't represent an allowable value in + // any supported encoding. + fieldsV1InvalidOrValidCBORObject +) + +// getContentType returns one of fieldsV1InvalidOrEmpty, fieldsV1InvalidOrValidJSONObject, +// fieldsV1InvalidOrValidCBORObject based on the value of Raw. +// +// Raw can be encoded in JSON or CBOR and is only valid if it is empty, null, or an object (map) +// value. It is invalid if it contains a JSON string, number, boolean, or array. If Raw is nonempty +// and represents an allowable value, then the initial byte unambiguously distinguishes a +// JSON-encoded value from a CBOR-encoded value. +// +// A valid JSON-encoded value can begin with any of the four JSON whitespace characters, the first +// character 'n' of null, or '{' (0x09, 0x0a, 0x0d, 0x20, 0x6e, or 0x7b, respectively). A valid +// CBOR-encoded value can begin with the null simple value, an initial byte with major type "map", +// or, if a tag-enclosed map, an initial byte with major type "tag" (0xf6, 0xa0...0xbf, or +// 0xc6...0xdb). The two sets of valid initial bytes don't intersect. +func (f FieldsV1) getContentType() int { + reader := f.GetRawReader() + if reader.Size() > 0 { + var buf [1]byte + if _, err := reader.Read(buf[:]); err != nil { + return fieldsV1InvalidOrEmpty + } + p := buf[0] + switch p { + case 'n', '{', '\t', '\r', '\n', ' ': + return fieldsV1InvalidOrValidJSONObject + case 0xf6: // null + return fieldsV1InvalidOrValidCBORObject + default: + if p >= 0xa0 && p <= 0xbf /* map */ || p >= 0xc6 && p <= 0xdb /* tag */ { + return fieldsV1InvalidOrValidCBORObject + } + } + } + return fieldsV1InvalidOrEmpty +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go new file mode 100644 index 0000000000..b529d9b0b1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/helpers_test.go @@ -0,0 +1,483 @@ +/* +Copyright 2016 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 v1 + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/randfill" +) + +func TestLabelSelectorAsSelector(t *testing.T) { + matchLabels := map[string]string{"foo": "bar"} + matchExpressions := []LabelSelectorRequirement{{ + Key: "baz", + Operator: LabelSelectorOpIn, + Values: []string{"qux", "norf"}, + }} + mustParse := func(s string) labels.Selector { + out, e := labels.Parse(s) + if e != nil { + panic(e) + } + return out + } + tc := []struct { + in *LabelSelector + out labels.Selector + expectErr bool + }{ + {in: nil, out: labels.Nothing()}, + {in: &LabelSelector{}, out: labels.Everything()}, + { + in: &LabelSelector{MatchLabels: matchLabels}, + out: mustParse("foo=bar"), + }, + { + in: &LabelSelector{MatchExpressions: matchExpressions}, + out: mustParse("baz in (norf,qux)"), + }, + { + in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions}, + out: mustParse("baz in (norf,qux),foo=bar"), + }, + { + in: &LabelSelector{ + MatchExpressions: []LabelSelectorRequirement{{ + Key: "baz", + Operator: LabelSelectorOpExists, + Values: []string{"qux", "norf"}, + }}, + }, + expectErr: true, + }, + } + + for i, tc := range tc { + inCopy := tc.in.DeepCopy() + out, err := LabelSelectorAsSelector(tc.in) + // after calling LabelSelectorAsSelector, tc.in shouldn't be modified + if !reflect.DeepEqual(inCopy, tc.in) { + t.Errorf("[%v]expected:\n\t%#v\nbut got:\n\t%#v", i, inCopy, tc.in) + } + if err == nil && tc.expectErr { + t.Errorf("[%v]expected error but got none.", i) + } + if err != nil && !tc.expectErr { + t.Errorf("[%v]did not expect error but got: %v", i, err) + } + // fmt.Sprint() over String() as nil.String() will panic + if fmt.Sprint(out) != fmt.Sprint(tc.out) { + t.Errorf("[%v]expected:\n\t%s\nbut got:\n\t%s", i, fmt.Sprint(tc.out), fmt.Sprint(out)) + } + } +} + +func BenchmarkLabelSelectorAsSelector(b *testing.B) { + selector := &LabelSelector{ + MatchLabels: map[string]string{ + "foo": "foo", + "bar": "bar", + }, + MatchExpressions: []LabelSelectorRequirement{{ + Key: "baz", + Operator: LabelSelectorOpExists, + }}, + } + b.StartTimer() + for i := 0; i < b.N; i++ { + _, err := LabelSelectorAsSelector(selector) + if err != nil { + b.Fatal(err) + } + } +} + +func TestLabelSelectorAsMap(t *testing.T) { + matchLabels := map[string]string{"foo": "bar"} + matchExpressions := func(operator LabelSelectorOperator, values []string) []LabelSelectorRequirement { + return []LabelSelectorRequirement{{ + Key: "baz", + Operator: operator, + Values: values, + }} + } + + tests := []struct { + in *LabelSelector + out map[string]string + errString string + }{ + {in: nil, out: nil}, + { + in: &LabelSelector{MatchLabels: matchLabels}, + out: map[string]string{"foo": "bar"}, + }, + { + in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions(LabelSelectorOpIn, []string{"norf"})}, + out: map[string]string{"foo": "bar", "baz": "norf"}, + }, + { + in: &LabelSelector{MatchExpressions: matchExpressions(LabelSelectorOpIn, []string{"norf"})}, + out: map[string]string{"baz": "norf"}, + }, + { + in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions(LabelSelectorOpIn, []string{"norf", "qux"})}, + out: map[string]string{"foo": "bar"}, + errString: "without a single value cannot be converted", + }, + { + in: &LabelSelector{MatchExpressions: matchExpressions(LabelSelectorOpNotIn, []string{"norf", "qux"})}, + out: map[string]string{}, + errString: "cannot be converted", + }, + { + in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions(LabelSelectorOpExists, []string{})}, + out: map[string]string{"foo": "bar"}, + errString: "cannot be converted", + }, + { + in: &LabelSelector{MatchExpressions: matchExpressions(LabelSelectorOpDoesNotExist, []string{})}, + out: map[string]string{}, + errString: "cannot be converted", + }, + } + + for i, tc := range tests { + out, err := LabelSelectorAsMap(tc.in) + if err == nil && len(tc.errString) > 0 { + t.Errorf("[%v]expected error but got none.", i) + continue + } + if err != nil && len(tc.errString) == 0 { + t.Errorf("[%v]did not expect error but got: %v", i, err) + continue + } + if err != nil && len(tc.errString) > 0 && !strings.Contains(err.Error(), tc.errString) { + t.Errorf("[%v]expected error with %q but got: %v", i, tc.errString, err) + continue + } + if !reflect.DeepEqual(out, tc.out) { + t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out) + } + } +} + +func TestResetObjectMetaForStatus(t *testing.T) { + meta := &ObjectMeta{} + existingMeta := &ObjectMeta{} + + // fuzz the existingMeta to set every field, no nils + f := randfill.New().NilChance(0).NumElements(1, 1).MaxDepth(10) + f.Fill(existingMeta) + ResetObjectMetaForStatus(meta, existingMeta) + + // not all fields are stomped during the reset. These fields should not have been set. False + // set them all to their zero values. Before you add anything to this list, consider whether or not + // you're enforcing immutability (those are fine) and whether /status should be able to update + // these values (these are usually not fine). + + // generateName doesn't do anything after create + existingMeta.SetGenerateName("") + // resourceVersion is enforced in validation and used during the storage update + existingMeta.SetResourceVersion("") + // fields made immutable in validation + existingMeta.SetUID(types.UID("")) + existingMeta.SetName("") + existingMeta.SetNamespace("") + existingMeta.SetCreationTimestamp(Time{}) + existingMeta.SetDeletionTimestamp(nil) + existingMeta.SetDeletionGracePeriodSeconds(nil) + existingMeta.SetManagedFields(nil) + + if !reflect.DeepEqual(meta, existingMeta) { + t.Error(cmp.Diff(meta, existingMeta)) + } +} + +func TestSetMetaDataLabel(t *testing.T) { + tests := []struct { + obj *ObjectMeta + label string + value string + want map[string]string + }{ + { + obj: &ObjectMeta{}, + label: "foo", + value: "bar", + want: map[string]string{"foo": "bar"}, + }, + { + obj: &ObjectMeta{Labels: map[string]string{"foo": "bar"}}, + label: "foo", + value: "baz", + want: map[string]string{"foo": "baz"}, + }, + { + obj: &ObjectMeta{Labels: map[string]string{"foo": "bar"}}, + label: "version", + value: "1.0.0", + want: map[string]string{"foo": "bar", "version": "1.0.0"}, + }, + } + + for _, tc := range tests { + SetMetaDataLabel(tc.obj, tc.label, tc.value) + if !reflect.DeepEqual(tc.obj.Labels, tc.want) { + t.Errorf("got %v, want %v", tc.obj.Labels, tc.want) + } + } +} + +func TestFieldsV1MarshalJSON(t *testing.T) { + for _, tc := range []struct { + Name string + FieldsV1 FieldsV1 + Want []byte + Error string + }{ + { + Name: "zero-value encodes as json null", + FieldsV1: FieldsV1{}, + Want: []byte(`null`), + }, + { + Name: "cbor null is transcoded to json null", + FieldsV1: *NewFieldsV1(string([]byte{0xf6})), // null + Want: []byte(`null`), + }, + { + Name: "valid non-map cbor and valid non-object json is returned as-is", + FieldsV1: *NewFieldsV1(string([]byte{0x30})), + Want: []byte{0x30}, // Valid CBOR encoding of -17 and JSON encoding of 0! + }, + { + Name: "self-described cbor map is transcoded to json map", + FieldsV1: *NewFieldsV1(string([]byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'})), // 55799({"foo":"bar"}) + Want: []byte(`{"foo":"bar"}`), + }, + { + Name: "json object is returned as-is", + FieldsV1: *NewFieldsV1(" \t\r\n{\"foo\":\"bar\"}"), + Want: []byte(" \t\r\n{\"foo\":\"bar\"}"), + }, + { + Name: "invalid json is returned as-is", + FieldsV1: *NewFieldsV1(`{{`), + Want: []byte(`{{`), + }, + { + Name: "invalid cbor fails to transcode to json", + FieldsV1: *NewFieldsV1(string([]byte{0xa1})), + Error: "metav1.FieldsV1 cbor invalid: unexpected EOF", + }, + } { + t.Run(tc.Name, func(t *testing.T) { + got, err := tc.FieldsV1.MarshalJSON() + if err != nil { + if tc.Error == "" { + t.Fatalf("unexpected error: %v", err) + } + if msg := err.Error(); msg != tc.Error { + t.Fatalf("expected error %q, got %q", tc.Error, msg) + } + } else if tc.Error != "" { + t.Fatalf("expected error %q, got nil", tc.Error) + } + if diff := cmp.Diff(tc.Want, got); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} + +func TestFieldsV1MarshalCBOR(t *testing.T) { + for _, tc := range []struct { + Name string + FieldsV1 FieldsV1 + Want []byte + Error string + }{ + { + Name: "nil encodes as cbor null", + FieldsV1: FieldsV1{}, + Want: []byte{0xf6}, // null + }, + { + Name: "json null is transcoded to cbor null", + FieldsV1: *NewFieldsV1(string(`null`)), + Want: []byte{0xf6}, // null + }, + { + Name: "valid non-map cbor and valid non-object json is returned as-is", + FieldsV1: *NewFieldsV1(string([]byte{0x30})), + Want: []byte{0x30}, // Valid CBOR encoding of -17 and JSON encoding of 0! + }, + { + Name: "json object is transcoded to cbor map", + FieldsV1: *NewFieldsV1(" \t\r\n{\"foo\":\"bar\"}"), + Want: []byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}, + }, + { + Name: "self-described cbor map is returned as-is", + FieldsV1: *NewFieldsV1(string([]byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'})), // 55799({"foo":"bar"}) + Want: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}, // 55799({"foo":"bar"}) + }, + { + Name: "invalid json fails to transcode to cbor", + FieldsV1: *NewFieldsV1(`{{`), + Error: "metav1.FieldsV1 json invalid: invalid character '{' looking for beginning of object key string", + }, + { + Name: "invalid cbor is returned as-is", + FieldsV1: *NewFieldsV1(string([]byte{0xa1})), + Want: []byte{0xa1}, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + got, err := tc.FieldsV1.MarshalCBOR() + if err != nil { + if tc.Error == "" { + t.Fatalf("unexpected error: %v", err) + } + if msg := err.Error(); msg != tc.Error { + t.Fatalf("expected error %q, got %q", tc.Error, msg) + } + } else if tc.Error != "" { + t.Fatalf("expected error %q, got nil", tc.Error) + } + + if diff := cmp.Diff(tc.Want, got); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} + +func TestFieldsV1UnmarshalJSON(t *testing.T) { + for _, tc := range []struct { + Name string + JSON []byte + Into *FieldsV1 + Want *FieldsV1 + Error string + }{ + { + Name: "nil receiver returns error", + Into: nil, + Error: "metav1.FieldsV1: UnmarshalJSON on nil pointer", + }, + { + Name: "json null does not modify receiver", // conventional for json.Unmarshaler + JSON: []byte(`null`), + Into: NewFieldsV1(`unmodified`), + Want: NewFieldsV1(`unmodified`), + }, + { + Name: "valid input is copied verbatim", + JSON: []byte("{\"foo\":\"bar\"} \t\r\n"), + Into: &FieldsV1{}, + Want: NewFieldsV1("{\"foo\":\"bar\"} \t\r\n"), + }, + { + Name: "invalid input is copied verbatim", + JSON: []byte("{{"), + Into: &FieldsV1{}, + Want: NewFieldsV1("{{"), + }, + } { + t.Run(tc.Name, func(t *testing.T) { + got := tc.Into.DeepCopy() + err := got.UnmarshalJSON(tc.JSON) + if err != nil { + if tc.Error == "" { + t.Fatalf("unexpected error: %v", err) + } + if msg := err.Error(); msg != tc.Error { + t.Fatalf("expected error %q, got %q", tc.Error, msg) + } + } else if tc.Error != "" { + t.Fatalf("expected error %q, got nil", tc.Error) + } + + if diff := cmp.Diff(tc.Want, got); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} + +func TestFieldsV1UnmarshalCBOR(t *testing.T) { + for _, tc := range []struct { + Name string + CBOR []byte + Into *FieldsV1 + Want *FieldsV1 + Error string + }{ + { + Name: "nil receiver returns error", + Into: nil, + Want: nil, + Error: "metav1.FieldsV1: UnmarshalCBOR on nil pointer", + }, + { + Name: "cbor null does not modify receiver", + CBOR: []byte{0xf6}, + Into: NewFieldsV1(`unmodified`), + Want: NewFieldsV1(`unmodified`), + }, + { + Name: "valid input is copied verbatim", + CBOR: []byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'}, + Into: &FieldsV1{}, + Want: NewFieldsV1(string([]byte{0xa1, 0x43, 'f', 'o', 'o', 0x43, 'b', 'a', 'r'})), + }, + { + Name: "invalid input is copied verbatim", + CBOR: []byte{0xff}, // UnmarshalCBOR should never be called with malformed input, testing anyway. + Into: &FieldsV1{}, + Want: NewFieldsV1(string([]byte{0xff})), + }, + } { + t.Run(tc.Name, func(t *testing.T) { + got := tc.Into.DeepCopy() + err := got.UnmarshalCBOR(tc.CBOR) + if err != nil { + if tc.Error == "" { + t.Fatalf("unexpected error: %v", err) + } + if msg := err.Error(); msg != tc.Error { + t.Fatalf("expected error %q, got %q", tc.Error, msg) + } + } else if tc.Error != "" { + t.Fatalf("expected error %q, got nil", tc.Error) + } + + if diff := cmp.Diff(tc.Want, got); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/labels.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/labels.go new file mode 100644 index 0000000000..9b45145da6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/labels.go @@ -0,0 +1,55 @@ +/* +Copyright 2016 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 v1 + +// Clones the given selector and returns a new selector with the given key and value added. +// Returns the given selector, if labelKey is empty. +func CloneSelectorAndAddLabel(selector *LabelSelector, labelKey, labelValue string) *LabelSelector { + if labelKey == "" { + // Don't need to add a label. + return selector + } + + // Clone. + newSelector := selector.DeepCopy() + + if newSelector.MatchLabels == nil { + newSelector.MatchLabels = make(map[string]string) + } + + newSelector.MatchLabels[labelKey] = labelValue + + return newSelector +} + +// AddLabelToSelector returns a selector with the given key and value added to the given selector's MatchLabels. +func AddLabelToSelector(selector *LabelSelector, labelKey, labelValue string) *LabelSelector { + if labelKey == "" { + // Don't need to add a label. + return selector + } + if selector.MatchLabels == nil { + selector.MatchLabels = make(map[string]string) + } + selector.MatchLabels[labelKey] = labelValue + return selector +} + +// SelectorHasLabel checks if the given selector contains the given label key in its MatchLabels +func SelectorHasLabel(selector *LabelSelector, labelKey string) bool { + return len(selector.MatchLabels[labelKey]) > 0 +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/labels_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/labels_test.go new file mode 100644 index 0000000000..918b1a294c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/labels_test.go @@ -0,0 +1,123 @@ +/* +Copyright 2016 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 v1 + +import ( + "reflect" + "testing" +) + +func TestCloneSelectorAndAddLabel(t *testing.T) { + labels := map[string]string{ + "foo1": "bar1", + "foo2": "bar2", + "foo3": "bar3", + } + matchExpressions := []LabelSelectorRequirement{ + {Key: "foo", Operator: LabelSelectorOpIn, Values: []string{"foo"}}, + } + + cases := []struct { + labels map[string]string + labelKey string + labelValue string + want map[string]string + }{ + { + labels: labels, + want: labels, + }, + { + labels: labels, + labelKey: "foo4", + labelValue: "89", + want: map[string]string{ + "foo1": "bar1", + "foo2": "bar2", + "foo3": "bar3", + "foo4": "89", + }, + }, + { + labels: nil, + labelKey: "foo4", + labelValue: "12", + want: map[string]string{ + "foo4": "12", + }, + }, + } + + for _, tc := range cases { + ls_in := LabelSelector{MatchLabels: tc.labels, MatchExpressions: matchExpressions} + ls_out := LabelSelector{MatchLabels: tc.want, MatchExpressions: matchExpressions} + + got := CloneSelectorAndAddLabel(&ls_in, tc.labelKey, tc.labelValue) + if !reflect.DeepEqual(got, &ls_out) { + t.Errorf("got %v, want %v", got, tc.want) + } + } +} + +func TestAddLabelToSelector(t *testing.T) { + labels := map[string]string{ + "foo1": "bar1", + "foo2": "bar2", + "foo3": "bar3", + } + + cases := []struct { + labels map[string]string + labelKey string + labelValue string + want map[string]string + }{ + { + labels: labels, + want: labels, + }, + { + labels: labels, + labelKey: "foo4", + labelValue: "89", + want: map[string]string{ + "foo1": "bar1", + "foo2": "bar2", + "foo3": "bar3", + "foo4": "89", + }, + }, + { + labels: nil, + labelKey: "foo4", + labelValue: "12", + want: map[string]string{ + "foo4": "12", + }, + }, + } + + for _, tc := range cases { + ls_in := LabelSelector{MatchLabels: tc.labels} + ls_out := LabelSelector{MatchLabels: tc.want} + + got := AddLabelToSelector(&ls_in, tc.labelKey, tc.labelValue) + if !reflect.DeepEqual(got, &ls_out) { + t.Errorf("got %v, want %v", got, tc.want) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go new file mode 100644 index 0000000000..6ae1b095fc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/meta.go @@ -0,0 +1,185 @@ +/* +Copyright 2016 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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" +) + +// TODO: move this, Object, List, and Type to a different package +type ObjectMetaAccessor interface { + GetObjectMeta() Object +} + +// Object lets you work with object metadata from any of the versioned or +// internal API objects. Attempting to set or retrieve a field on an object that does +// not support that field (Name, UID, Namespace on lists) will be a no-op and return +// a default value. +type Object interface { + GetNamespace() string + SetNamespace(namespace string) + GetName() string + SetName(name string) + GetGenerateName() string + SetGenerateName(name string) + GetUID() types.UID + SetUID(uid types.UID) + GetResourceVersion() string + SetResourceVersion(version string) + GetGeneration() int64 + SetGeneration(generation int64) + GetSelfLink() string + SetSelfLink(selfLink string) + GetCreationTimestamp() Time + SetCreationTimestamp(timestamp Time) + GetDeletionTimestamp() *Time + SetDeletionTimestamp(timestamp *Time) + GetDeletionGracePeriodSeconds() *int64 + SetDeletionGracePeriodSeconds(*int64) + GetLabels() map[string]string + SetLabels(labels map[string]string) + GetAnnotations() map[string]string + SetAnnotations(annotations map[string]string) + GetFinalizers() []string + SetFinalizers(finalizers []string) + GetOwnerReferences() []OwnerReference + SetOwnerReferences([]OwnerReference) + GetManagedFields() []ManagedFieldsEntry + SetManagedFields(managedFields []ManagedFieldsEntry) +} + +// ListMetaAccessor retrieves the list interface from an object +type ListMetaAccessor interface { + GetListMeta() ListInterface +} + +// Common lets you work with core metadata from any of the versioned or +// internal API objects. Attempting to set or retrieve a field on an object that does +// not support that field will be a no-op and return a default value. +// TODO: move this, and TypeMeta and ListMeta, to a different package +type Common interface { + GetResourceVersion() string + SetResourceVersion(version string) + GetSelfLink() string + SetSelfLink(selfLink string) +} + +// ListInterface lets you work with list metadata from any of the versioned or +// internal API objects. Attempting to set or retrieve a field on an object that does +// not support that field will be a no-op and return a default value. +// TODO: move this, and TypeMeta and ListMeta, to a different package +type ListInterface interface { + GetResourceVersion() string + SetResourceVersion(version string) + GetSelfLink() string + SetSelfLink(selfLink string) + GetContinue() string + SetContinue(c string) + GetRemainingItemCount() *int64 + SetRemainingItemCount(c *int64) +} + +// ShardedListInterface can be implemented by list types to indicate that they +// represent a sharded subset of the full collection rather than the complete list. +type ShardedListInterface interface { + GetShardInfo() *ShardInfo + SetShardInfo(*ShardInfo) +} + +// Type exposes the type and APIVersion of versioned or internal API objects. +// TODO: move this, and TypeMeta and ListMeta, to a different package +type Type interface { + GetAPIVersion() string + SetAPIVersion(version string) + GetKind() string + SetKind(kind string) +} + +var _ ListInterface = &ListMeta{} + +func (meta *ListMeta) GetResourceVersion() string { return meta.ResourceVersion } +func (meta *ListMeta) SetResourceVersion(version string) { meta.ResourceVersion = version } +func (meta *ListMeta) GetSelfLink() string { return meta.SelfLink } +func (meta *ListMeta) SetSelfLink(selfLink string) { meta.SelfLink = selfLink } +func (meta *ListMeta) GetContinue() string { return meta.Continue } +func (meta *ListMeta) SetContinue(c string) { meta.Continue = c } +func (meta *ListMeta) GetRemainingItemCount() *int64 { return meta.RemainingItemCount } +func (meta *ListMeta) SetRemainingItemCount(c *int64) { meta.RemainingItemCount = c } +func (meta *ListMeta) GetShardInfo() *ShardInfo { return meta.ShardInfo } +func (meta *ListMeta) SetShardInfo(s *ShardInfo) { meta.ShardInfo = s } + +func (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj } + +// SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta +func (obj *TypeMeta) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} + +// GroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta +func (obj *TypeMeta) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} + +func (obj *ListMeta) GetListMeta() ListInterface { return obj } + +func (obj *ObjectMeta) GetObjectMeta() Object { return obj } + +// Namespace implements metav1.Object for any object with an ObjectMeta typed field. Allows +// fast, direct access to metadata fields for API objects. +func (meta *ObjectMeta) GetNamespace() string { return meta.Namespace } +func (meta *ObjectMeta) SetNamespace(namespace string) { meta.Namespace = namespace } +func (meta *ObjectMeta) GetName() string { return meta.Name } +func (meta *ObjectMeta) SetName(name string) { meta.Name = name } +func (meta *ObjectMeta) GetGenerateName() string { return meta.GenerateName } +func (meta *ObjectMeta) SetGenerateName(generateName string) { meta.GenerateName = generateName } +func (meta *ObjectMeta) GetUID() types.UID { return meta.UID } +func (meta *ObjectMeta) SetUID(uid types.UID) { meta.UID = uid } +func (meta *ObjectMeta) GetResourceVersion() string { return meta.ResourceVersion } +func (meta *ObjectMeta) SetResourceVersion(version string) { meta.ResourceVersion = version } +func (meta *ObjectMeta) GetGeneration() int64 { return meta.Generation } +func (meta *ObjectMeta) SetGeneration(generation int64) { meta.Generation = generation } +func (meta *ObjectMeta) GetSelfLink() string { return meta.SelfLink } +func (meta *ObjectMeta) SetSelfLink(selfLink string) { meta.SelfLink = selfLink } +func (meta *ObjectMeta) GetCreationTimestamp() Time { return meta.CreationTimestamp } +func (meta *ObjectMeta) SetCreationTimestamp(creationTimestamp Time) { + meta.CreationTimestamp = creationTimestamp +} +func (meta *ObjectMeta) GetDeletionTimestamp() *Time { return meta.DeletionTimestamp } +func (meta *ObjectMeta) SetDeletionTimestamp(deletionTimestamp *Time) { + meta.DeletionTimestamp = deletionTimestamp +} +func (meta *ObjectMeta) GetDeletionGracePeriodSeconds() *int64 { + return meta.DeletionGracePeriodSeconds +} +func (meta *ObjectMeta) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds *int64) { + meta.DeletionGracePeriodSeconds = deletionGracePeriodSeconds +} +func (meta *ObjectMeta) GetLabels() map[string]string { return meta.Labels } +func (meta *ObjectMeta) SetLabels(labels map[string]string) { meta.Labels = labels } +func (meta *ObjectMeta) GetAnnotations() map[string]string { return meta.Annotations } +func (meta *ObjectMeta) SetAnnotations(annotations map[string]string) { meta.Annotations = annotations } +func (meta *ObjectMeta) GetFinalizers() []string { return meta.Finalizers } +func (meta *ObjectMeta) SetFinalizers(finalizers []string) { meta.Finalizers = finalizers } +func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference { return meta.OwnerReferences } +func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) { + meta.OwnerReferences = references +} +func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields } +func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) { + meta.ManagedFields = managedFields +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go new file mode 100644 index 0000000000..9f302b3f36 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go @@ -0,0 +1,209 @@ +/* +Copyright 2016 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 v1 + +import ( + "encoding/json" + "time" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" +) + +const RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00" + +// MicroTime is version of Time with microsecond level precision. +// +// +protobuf.options.marshal=false +// +protobuf.as=Timestamp +// +protobuf.options.(gogoproto.goproto_stringer)=false +type MicroTime struct { + time.Time `protobuf:"-"` +} + +// DeepCopy returns a deep-copy of the MicroTime value. The underlying time.Time +// type is effectively immutable in the time API, so it is safe to +// copy-by-assign, despite the presence of (unexported) Pointer fields. +func (t *MicroTime) DeepCopyInto(out *MicroTime) { + *out = *t +} + +// NewMicroTime returns a wrapped instance of the provided time +func NewMicroTime(time time.Time) MicroTime { + return MicroTime{time} +} + +// DateMicro returns the MicroTime corresponding to the supplied parameters +// by wrapping time.Date. +func DateMicro(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) MicroTime { + return MicroTime{time.Date(year, month, day, hour, min, sec, nsec, loc)} +} + +// NowMicro returns the current local time. +func NowMicro() MicroTime { + return MicroTime{time.Now()} +} + +// IsZero returns true if the value is nil or time is zero. +func (t *MicroTime) IsZero() bool { + if t == nil { + return true + } + return t.Time.IsZero() +} + +// Before reports whether the time instant t is before u. +func (t *MicroTime) Before(u *MicroTime) bool { + if t != nil && u != nil { + return t.Time.Before(u.Time) + } + return false +} + +// Equal reports whether the time instant t is equal to u. +func (t *MicroTime) Equal(u *MicroTime) bool { + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false +} + +// BeforeTime reports whether the time instant t is before second-lever precision u. +func (t *MicroTime) BeforeTime(u *Time) bool { + if t != nil && u != nil { + return t.Time.Before(u.Time) + } + return false +} + +// EqualTime reports whether the time instant t is equal to second-lever precision u. +func (t *MicroTime) EqualTime(u *Time) bool { + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false +} + +// UnixMicro returns the local time corresponding to the given Unix time +// by wrapping time.Unix. +func UnixMicro(sec int64, nsec int64) MicroTime { + return MicroTime{time.Unix(sec, nsec)} +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (t *MicroTime) UnmarshalJSON(b []byte) error { + if len(b) == 4 && string(b) == "null" { + t.Time = time.Time{} + return nil + } + + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pt, err := time.Parse(RFC3339Micro, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +func (t *MicroTime) UnmarshalCBOR(b []byte) error { + var s *string + if err := cbor.Unmarshal(b, &s); err != nil { + return err + } + if s == nil { + t.Time = time.Time{} + return nil + } + + parsed, err := time.Parse(RFC3339Micro, *s) + if err != nil { + return err + } + + t.Time = parsed.Local() + return nil +} + +// UnmarshalQueryParameter converts from a URL query parameter value to an object +func (t *MicroTime) UnmarshalQueryParameter(str string) error { + if len(str) == 0 { + t.Time = time.Time{} + return nil + } + // Tolerate requests from older clients that used JSON serialization to build query params + if len(str) == 4 && str == "null" { + t.Time = time.Time{} + return nil + } + + pt, err := time.Parse(RFC3339Micro, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (t MicroTime) MarshalJSON() ([]byte, error) { + if t.IsZero() { + // Encode unset/nil objects as JSON's "null". + return []byte("null"), nil + } + + return json.Marshal(t.UTC().Format(RFC3339Micro)) +} + +func (t MicroTime) MarshalCBOR() ([]byte, error) { + if t.IsZero() { + return cbor.Marshal(nil) + } + return cbor.Marshal(t.UTC().Format(RFC3339Micro)) +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ MicroTime) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ MicroTime) OpenAPISchemaFormat() string { return "date-time" } + +// MarshalQueryParameter converts to a URL query parameter value +func (t MicroTime) MarshalQueryParameter() (string, error) { + if t.IsZero() { + // Encode unset/nil objects as an empty string + return "", nil + } + + return t.UTC().Format(RFC3339Micro), nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_fuzz.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_fuzz.go new file mode 100644 index 0000000000..338ea9be72 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_fuzz.go @@ -0,0 +1,40 @@ +//go:build !notest + +/* +Copyright 2020 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 v1 + +import ( + "math/rand" + "time" + + "sigs.k8s.io/randfill" +) + +// Fuzz satisfies randfill.SimpleSelfFiller. +func (t *MicroTime) RandFill(r *rand.Rand) { + if t == nil { + return + } + // Allow for about 1000 years of randomness. Accurate to a tenth of + // micro second. Leave off nanoseconds because JSON doesn't + // represent them so they can't round-trip properly. + t.Time = time.Unix(r.Int63n(1000*365*24*60*60), 1000*r.Int63n(1000000)) +} + +// ensure MicroTime implements randfill.Interface +var _ randfill.SimpleSelfFiller = &MicroTime{} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_proto.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_proto.go new file mode 100644 index 0000000000..ab68181e91 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_proto.go @@ -0,0 +1,86 @@ +/* +Copyright 2016 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 v1 + +import ( + "time" +) + +// Timestamp is declared in time_proto.go + +// Timestamp returns the Time as a new Timestamp value. +func (m *MicroTime) ProtoMicroTime() *Timestamp { + if m == nil { + return &Timestamp{} + } + + // truncate precision to microseconds to match JSON marshaling/unmarshaling + truncatedNanoseconds := time.Duration(m.Time.Nanosecond()).Truncate(time.Microsecond) + return &Timestamp{ + Seconds: m.Time.Unix(), + Nanos: int32(truncatedNanoseconds), + } +} + +// Size implements the protobuf marshalling interface. +func (m *MicroTime) Size() (n int) { + if m == nil || m.Time.IsZero() { + return 0 + } + return m.ProtoMicroTime().Size() +} + +// Reset implements the protobuf marshalling interface. +func (m *MicroTime) Unmarshal(data []byte) error { + if len(data) == 0 { + m.Time = time.Time{} + return nil + } + p := Timestamp{} + if err := p.Unmarshal(data); err != nil { + return err + } + + // truncate precision to microseconds to match JSON marshaling/unmarshaling + truncatedNanoseconds := time.Duration(p.Nanos).Truncate(time.Microsecond) + m.Time = time.Unix(p.Seconds, int64(truncatedNanoseconds)).Local() + return nil +} + +// Marshal implements the protobuf marshalling interface. +func (m *MicroTime) Marshal() (data []byte, err error) { + if m == nil || m.Time.IsZero() { + return nil, nil + } + return m.ProtoMicroTime().Marshal() +} + +// MarshalTo implements the protobuf marshalling interface. +func (m *MicroTime) MarshalTo(data []byte) (int, error) { + if m == nil || m.Time.IsZero() { + return 0, nil + } + return m.ProtoMicroTime().MarshalTo(data) +} + +// MarshalToSizedBuffer implements the protobuf marshalling interface. +func (m *MicroTime) MarshalToSizedBuffer(data []byte) (int, error) { + if m == nil || m.Time.IsZero() { + return 0, nil + } + return m.ProtoMicroTime().MarshalToSizedBuffer(data) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_test.go new file mode 100644 index 0000000000..b96baa5b77 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_test.go @@ -0,0 +1,402 @@ +/* +Copyright 2016 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 v1 + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + "time" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "sigs.k8s.io/yaml" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/randfill" +) + +type MicroTimeHolder struct { + T MicroTime `json:"t"` +} + +func TestMicroTimeMarshalYAML(t *testing.T) { + cases := []struct { + input MicroTime + result string + }{ + {MicroTime{}, "t: null\n"}, + {DateMicro(1998, time.May, 5, 1, 5, 5, 50, time.FixedZone("test", -4*60*60)), "t: \"1998-05-05T05:05:05.000000Z\"\n"}, + {DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.UTC), "t: \"1998-05-05T05:05:05.000000Z\"\n"}, + } + + for _, c := range cases { + input := MicroTimeHolder{c.input} + result, err := yaml.Marshal(&input) + if err != nil { + t.Errorf("Failed to marshal input: '%v': %v", input, err) + } + if string(result) != c.result { + t.Errorf("Failed to marshal input: '%v': expected %+v, got %q", input, c.result, string(result)) + } + } +} + +func TestMicroTimeUnmarshalYAML(t *testing.T) { + cases := []struct { + input string + result MicroTime + }{ + {"t: null\n", MicroTime{}}, + {"t: 1998-05-05T05:05:05.000000Z\n", MicroTime{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, + } + + for _, c := range cases { + var result MicroTimeHolder + if err := yaml.Unmarshal([]byte(c.input), &result); err != nil { + t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) + } + if result.T != c.result { + t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) + } + } +} + +func TestMicroTimeMarshalJSON(t *testing.T) { + cases := []struct { + input MicroTime + result string + }{ + {MicroTime{}, "{\"t\":null}"}, + {DateMicro(1998, time.May, 5, 5, 5, 5, 50, time.UTC), "{\"t\":\"1998-05-05T05:05:05.000000Z\"}"}, + {DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.UTC), "{\"t\":\"1998-05-05T05:05:05.000000Z\"}"}, + } + + for _, c := range cases { + input := MicroTimeHolder{c.input} + result, err := json.Marshal(&input) + if err != nil { + t.Errorf("Failed to marshal input: '%v': %v", input, err) + } + if string(result) != c.result { + t.Errorf("Failed to marshal input: '%v': expected %+v, got %q", input, c.result, string(result)) + } + } +} + +func TestMicroTimeUnmarshalJSON(t *testing.T) { + cases := []struct { + input string + result MicroTime + }{ + {"{\"t\":null}", MicroTime{}}, + {"{\"t\":\"1998-05-05T05:05:05.000000Z\"}", MicroTime{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, + } + + for _, c := range cases { + var result MicroTimeHolder + if err := json.Unmarshal([]byte(c.input), &result); err != nil { + t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) + } + if result.T != c.result { + t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) + } + } +} + +func TestMicroTimeMarshalCBOR(t *testing.T) { + for _, tc := range []struct { + name string + in MicroTime + out []byte + }{ + {name: "zero value", in: MicroTime{}, out: []byte{0xf6}}, // null + {name: "no fractional seconds", in: DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.UTC), out: []byte("\x58\x1b1998-05-05T05:05:05.000000Z")}, // '1998-05-05T05:05:05.000000Z' + {name: "nanoseconds truncated", in: DateMicro(1998, time.May, 5, 5, 5, 5, 5050, time.UTC), out: []byte("\x58\x1b1998-05-05T05:05:05.000005Z")}, // '1998-05-05T05:05:05.000005Z' + } { + t.Run(fmt.Sprintf("%+v", tc.in), func(t *testing.T) { + got, err := tc.in.MarshalCBOR() + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tc.out, got); diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } + }) + } +} + +func TestMicroTimeUnmarshalCBOR(t *testing.T) { + for _, tc := range []struct { + name string + in []byte + out MicroTime + errMessage string + }{ + {name: "null", in: []byte{0xf6}, out: MicroTime{}}, // null + {name: "valid", in: []byte("\x58\x1b1998-05-05T05:05:05.000000Z"), out: MicroTime{Time: Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, // '1998-05-05T05:05:05.000000Z' + {name: "invalid cbor type", in: []byte{0x07}, out: MicroTime{}, errMessage: "cbor: cannot unmarshal positive integer into Go value of type string"}, // 7 + {name: "malformed timestamp", in: []byte("\x45hello"), out: MicroTime{}, errMessage: `parsing time "hello" as "2006-01-02T15:04:05.000000Z07:00": cannot parse "hello" as "2006"`}, // 'hello' + } { + t.Run(tc.name, func(t *testing.T) { + var got MicroTime + err := got.UnmarshalCBOR(tc.in) + if err != nil { + if tc.errMessage == "" { + t.Fatalf("want nil error, got: %v", err) + } else if gotMessage := err.Error(); tc.errMessage != gotMessage { + t.Fatalf("want error: %q, got: %q", tc.errMessage, gotMessage) + } + } else if tc.errMessage != "" { + t.Fatalf("got nil error, want: %s", tc.errMessage) + } + if diff := cmp.Diff(tc.out, got); diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } + }) + } +} + +func TestMicroTimeProto(t *testing.T) { + cases := []struct { + input MicroTime + }{ + {MicroTime{}}, + {DateMicro(1998, time.May, 5, 1, 5, 5, 1000, time.Local)}, + {DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.Local)}, + } + + for _, c := range cases { + input := c.input + data, err := input.Marshal() + if err != nil { + t.Fatalf("Failed to marshal input: '%v': %v", input, err) + } + time := MicroTime{} + if err := time.Unmarshal(data); err != nil { + t.Fatalf("Failed to unmarshal output: '%v': %v", input, err) + } + if !reflect.DeepEqual(input, time) { + t.Errorf("Marshal->Unmarshal is not idempotent: '%v' vs '%v'", input, time) + } + } +} + +func TestMicroTimeEqual(t *testing.T) { + t1 := NewMicroTime(time.Now()) + cases := []struct { + name string + x *MicroTime + y *MicroTime + result bool + }{ + {"nil =? nil", nil, nil, true}, + {"!nil =? !nil", &t1, &t1, true}, + {"nil =? !nil", nil, &t1, false}, + {"!nil =? nil", &t1, nil, false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := c.x.Equal(c.y) + if result != c.result { + t.Errorf("Failed equality test for '%v', '%v': expected %+v, got %+v", c.x, c.y, c.result, result) + } + }) + } +} + +func TestMicroTimeEqualTime(t *testing.T) { + t1 := NewMicroTime(time.Now()) + t2 := NewTime(t1.Time) + cases := []struct { + name string + x *MicroTime + y *Time + result bool + }{ + {"nil =? nil", nil, nil, true}, + {"!nil =? !nil", &t1, &t2, true}, + {"nil =? !nil", nil, &t2, false}, + {"!nil =? nil", &t1, nil, false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := c.x.EqualTime(c.y) + if result != c.result { + t.Errorf("Failed equality test for '%v', '%v': expected %+v, got %+v", c.x, c.y, c.result, result) + } + }) + } +} + +func TestMicroTimeBefore(t *testing.T) { + t1 := NewMicroTime(time.Now()) + cases := []struct { + name string + x *MicroTime + y *MicroTime + }{ + {"nil PatchOptions -> UpdateOptions round-trip failed: +got: %v +want: %v`, got, update) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/register.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/register.go new file mode 100644 index 0000000000..1abdd626de --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/register.go @@ -0,0 +1,107 @@ +/* +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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +// GroupName is the group name for this API. +const GroupName = "meta.k8s.io" + +var ( + // localSchemeBuilder is used to make compiler happy for autogenerated + // conversions. However, it's not used. + schemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &schemeBuilder +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Unversioned is group version for unversioned API objects +// TODO: this should be v1 probably +var Unversioned = schema.GroupVersion{Group: "", Version: "v1"} + +// WatchEventKind is name reserved for serializing watch events. +const WatchEventKind = "WatchEvent" + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// scheme is the registry for the common types that adhere to the meta v1 API spec. +var scheme = runtime.NewScheme() + +// ParameterCodec knows about query parameters used with the meta v1 API spec. +var ParameterCodec = runtime.NewParameterCodec(scheme) + +var optionsTypes = []runtime.Object{ + &ListOptions{}, + &GetOptions{}, + &DeleteOptions{}, + &CreateOptions{}, + &UpdateOptions{}, + &PatchOptions{}, +} + +// AddToGroupVersion registers common meta types into schemas. +func AddToGroupVersion(scheme *runtime.Scheme, groupVersion schema.GroupVersion) { + scheme.AddKnownTypeWithName(groupVersion.WithKind(WatchEventKind), &WatchEvent{}) + scheme.AddKnownTypeWithName( + schema.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal}.WithKind(WatchEventKind), + &InternalEvent{}, + ) + // Supports legacy code paths, most callers should use metav1.ParameterCodec for now + scheme.AddKnownTypes(groupVersion, optionsTypes...) + // Register Unversioned types under their own special group + scheme.AddUnversionedTypes(Unversioned, + &Status{}, + &APIVersions{}, + &APIGroupList{}, + &APIGroup{}, + &APIResourceList{}, + ) + + // register manually. This usually goes through the SchemeBuilder, which we cannot use here. + utilruntime.Must(RegisterConversions(scheme)) + utilruntime.Must(RegisterDefaults(scheme)) +} + +// AddMetaToScheme registers base meta types into schemas. +func AddMetaToScheme(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Table{}, + &TableOptions{}, + &PartialObjectMetadata{}, + &PartialObjectMetadataList{}, + ) + + return nil +} + +func init() { + scheme.AddUnversionedTypes(SchemeGroupVersion, optionsTypes...) + + utilruntime.Must(AddMetaToScheme(scheme)) + + // register manually. This usually goes through the SchemeBuilder, which we cannot use here. + utilruntime.Must(RegisterDefaults(scheme)) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time.go new file mode 100644 index 0000000000..0333cfdb33 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time.go @@ -0,0 +1,211 @@ +/* +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 v1 + +import ( + "encoding/json" + "time" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" +) + +// Time is a wrapper around time.Time which supports correct +// marshaling to YAML and JSON. Wrappers are provided for many +// of the factory methods that the time package offers. +// +// +protobuf.options.marshal=false +// +protobuf.as=Timestamp +// +protobuf.options.(gogoproto.goproto_stringer)=false +type Time struct { + time.Time `protobuf:"-"` +} + +// DeepCopyInto creates a deep-copy of the Time value. The underlying time.Time +// type is effectively immutable in the time API, so it is safe to +// copy-by-assign, despite the presence of (unexported) Pointer fields. +func (t *Time) DeepCopyInto(out *Time) { + *out = *t +} + +// NewTime returns a wrapped instance of the provided time +func NewTime(time time.Time) Time { + return Time{time} +} + +// Date returns the Time corresponding to the supplied parameters +// by wrapping time.Date. +func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { + return Time{time.Date(year, month, day, hour, min, sec, nsec, loc)} +} + +// Now returns the current local time. +func Now() Time { + return Time{time.Now()} +} + +// IsZero returns true if the value is nil or time is zero. +func (t *Time) IsZero() bool { + if t == nil { + return true + } + return t.Time.IsZero() +} + +// Before reports whether the time instant t is before u. +func (t *Time) Before(u *Time) bool { + if t != nil && u != nil { + return t.Time.Before(u.Time) + } + return false +} + +// Equal reports whether the time instant t is equal to u. +func (t *Time) Equal(u *Time) bool { + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false +} + +// Unix returns the local time corresponding to the given Unix time +// by wrapping time.Unix. +func Unix(sec int64, nsec int64) Time { + return Time{time.Unix(sec, nsec)} +} + +// Rfc3339Copy returns a copy of the Time at second-level precision. +func (t Time) Rfc3339Copy() Time { + copied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339)) + return Time{copied} +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (t *Time) UnmarshalJSON(b []byte) error { + if len(b) == 4 && string(b) == "null" { + t.Time = time.Time{} + return nil + } + + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +func (t *Time) UnmarshalCBOR(b []byte) error { + var s *string + if err := cbor.Unmarshal(b, &s); err != nil { + return err + } + if s == nil { + t.Time = time.Time{} + return nil + } + + parsed, err := time.Parse(time.RFC3339, *s) + if err != nil { + return err + } + + t.Time = parsed.Local() + return nil +} + +// UnmarshalQueryParameter converts from a URL query parameter value to an object +func (t *Time) UnmarshalQueryParameter(str string) error { + if len(str) == 0 { + t.Time = time.Time{} + return nil + } + // Tolerate requests from older clients that used JSON serialization to build query params + if len(str) == 4 && str == "null" { + t.Time = time.Time{} + return nil + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (t Time) MarshalJSON() ([]byte, error) { + if t.IsZero() { + // Encode unset/nil objects as JSON's "null". + return []byte("null"), nil + } + buf := make([]byte, 0, len(time.RFC3339)+2) + buf = append(buf, '"') + // time cannot contain non escapable JSON characters + buf = t.UTC().AppendFormat(buf, time.RFC3339) + buf = append(buf, '"') + return buf, nil +} + +func (t Time) MarshalCBOR() ([]byte, error) { + if t.IsZero() { + return cbor.Marshal(nil) + } + + return cbor.Marshal(t.UTC().Format(time.RFC3339)) +} + +// ToUnstructured implements the value.UnstructuredConverter interface. +func (t Time) ToUnstructured() interface{} { + if t.IsZero() { + return nil + } + buf := make([]byte, 0, len(time.RFC3339)) + buf = t.UTC().AppendFormat(buf, time.RFC3339) + return string(buf) +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ Time) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ Time) OpenAPISchemaFormat() string { return "date-time" } + +// MarshalQueryParameter converts to a URL query parameter value +func (t Time) MarshalQueryParameter() (string, error) { + if t.IsZero() { + // Encode unset/nil objects as an empty string + return "", nil + } + + return t.UTC().Format(time.RFC3339), nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_fuzz.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_fuzz.go new file mode 100644 index 0000000000..14ec5f0586 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_fuzz.go @@ -0,0 +1,40 @@ +//go:build !notest + +/* +Copyright 2020 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 v1 + +import ( + "math/rand" + "time" + + "sigs.k8s.io/randfill" +) + +// Fuzz satisfies randfill.SimpleSelfFiller. +func (t *Time) RandFill(r *rand.Rand) { + if t == nil { + return + } + // Allow for about 1000 years of randomness. Leave off nanoseconds + // because JSON doesn't represent them so they can't round-trip + // properly. + t.Time = time.Unix(r.Int63n(1000*365*24*60*60), 0) +} + +// ensure Time implements randfill.SimpleSelfFiller +var _ randfill.SimpleSelfFiller = &Time{} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_proto.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_proto.go new file mode 100644 index 0000000000..eac8d96589 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_proto.go @@ -0,0 +1,100 @@ +/* +Copyright 2015 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 v1 + +import ( + "time" +) + +// Timestamp is a struct that is equivalent to Time, but intended for +// protobuf marshalling/unmarshalling. It is generated into a serialization +// that matches Time. Do not use in Go structs. +type Timestamp struct { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + Seconds int64 `json:"seconds" protobuf:"varint,1,opt,name=seconds"` + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. This field may be limited in precision depending on context. + Nanos int32 `json:"nanos" protobuf:"varint,2,opt,name=nanos"` +} + +// Timestamp returns the Time as a new Timestamp value. +func (m *Time) ProtoTime() *Timestamp { + if m == nil { + return &Timestamp{} + } + return &Timestamp{ + Seconds: m.Time.Unix(), + // leaving this here for the record. our JSON only handled seconds, so this results in writes by + // protobuf clients storing values that aren't read by json clients, which results in unexpected + // field mutation, which fails various validation and equality code. + // Nanos: int32(m.Time.Nanosecond()), + } +} + +// Size implements the protobuf marshalling interface. +func (m *Time) Size() (n int) { + if m == nil || m.Time.IsZero() { + return 0 + } + return m.ProtoTime().Size() +} + +// Reset implements the protobuf marshalling interface. +func (m *Time) Unmarshal(data []byte) error { + if len(data) == 0 { + m.Time = time.Time{} + return nil + } + p := Timestamp{} + if err := p.Unmarshal(data); err != nil { + return err + } + // leaving this here for the record. our JSON only handled seconds, so this results in writes by + // protobuf clients storing values that aren't read by json clients, which results in unexpected + // field mutation, which fails various validation and equality code. + // m.Time = time.Unix(p.Seconds, int64(p.Nanos)).Local() + m.Time = time.Unix(p.Seconds, int64(0)).Local() + return nil +} + +// Marshal implements the protobuf marshaling interface. +func (m *Time) Marshal() (data []byte, err error) { + if m == nil || m.Time.IsZero() { + return nil, nil + } + return m.ProtoTime().Marshal() +} + +// MarshalTo implements the protobuf marshaling interface. +func (m *Time) MarshalTo(data []byte) (int, error) { + if m == nil || m.Time.IsZero() { + return 0, nil + } + return m.ProtoTime().MarshalTo(data) +} + +// MarshalToSizedBuffer implements the protobuf reverse marshaling interface. +func (m *Time) MarshalToSizedBuffer(data []byte) (int, error) { + if m == nil || m.Time.IsZero() { + return 0, nil + } + return m.ProtoTime().MarshalToSizedBuffer(data) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_test.go new file mode 100644 index 0000000000..886425f2fb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/time_test.go @@ -0,0 +1,327 @@ +/* +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 v1 + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + "time" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "sigs.k8s.io/yaml" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/randfill" +) + +type TimeHolder struct { + T Time `json:"t"` +} + +func TestTimeMarshalYAML(t *testing.T) { + cases := []struct { + input Time + result string + }{ + {Time{}, "t: null\n"}, + {Date(1998, time.May, 5, 1, 5, 5, 50, time.FixedZone("test", -4*60*60)), "t: \"1998-05-05T05:05:05Z\"\n"}, + {Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC), "t: \"1998-05-05T05:05:05Z\"\n"}, + } + + for _, c := range cases { + input := TimeHolder{c.input} + result, err := yaml.Marshal(&input) + if err != nil { + t.Errorf("Failed to marshal input: '%v': %v", input, err) + } + if string(result) != c.result { + t.Errorf("Failed to marshal input: '%v': expected %+v, got %q", input, c.result, string(result)) + } + } +} + +func TestTimeUnmarshalYAML(t *testing.T) { + cases := []struct { + input string + result Time + }{ + {"t: null\n", Time{}}, + {"t: 1998-05-05T05:05:05Z\n", Time{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, + } + + for _, c := range cases { + var result TimeHolder + if err := yaml.Unmarshal([]byte(c.input), &result); err != nil { + t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) + } + if result.T != c.result { + t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) + } + } +} + +func TestTimeMarshalJSON(t *testing.T) { + cases := []struct { + input Time + result string + }{ + {Time{}, "{\"t\":null}"}, + {Date(1998, time.May, 5, 5, 5, 5, 50, time.UTC), "{\"t\":\"1998-05-05T05:05:05Z\"}"}, + {Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC), "{\"t\":\"1998-05-05T05:05:05Z\"}"}, + } + + for _, c := range cases { + input := TimeHolder{c.input} + result, err := json.Marshal(&input) + if err != nil { + t.Errorf("Failed to marshal input: '%v': %v", input, err) + } + if string(result) != c.result { + t.Errorf("Failed to marshal input: '%v': expected %+v, got %q", input, c.result, string(result)) + } + } +} + +func TestTimeUnmarshalJSON(t *testing.T) { + cases := []struct { + input string + result Time + }{ + {"{\"t\":null}", Time{}}, + {"{\"t\":\"1998-05-05T05:05:05Z\"}", Time{Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, + {"{\"t\":\"1998-05-05T05:05:05.123456789Z\"}", Time{Date(1998, time.May, 5, 5, 5, 5, 123456789, time.UTC).Local()}}, + } + + for _, c := range cases { + var result TimeHolder + if err := json.Unmarshal([]byte(c.input), &result); err != nil { + t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) + } + if result.T != c.result { + t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) + } + } +} + +func TestTimeMarshalJSONUnmarshalYAML(t *testing.T) { + cases := []struct { + input Time + }{ + {Time{}}, + {Date(1998, time.May, 5, 5, 5, 5, 50, time.Local).Rfc3339Copy()}, + {Date(1998, time.May, 5, 5, 5, 5, 0, time.Local).Rfc3339Copy()}, + } + + for i, c := range cases { + input := TimeHolder{c.input} + jsonMarshalled, err := json.Marshal(&input) + if err != nil { + t.Errorf("%d-1: Failed to marshal input: '%v': %v", i, input, err) + } + + var result TimeHolder + err = yaml.Unmarshal(jsonMarshalled, &result) + if err != nil { + t.Errorf("%d-2: Failed to unmarshal '%+v': %v", i, string(jsonMarshalled), err) + } + + iN, iO := input.T.Zone() + oN, oO := result.T.Zone() + if iN != oN || iO != oO { + t.Errorf("%d-3: Time zones differ before and after serialization %s:%d %s:%d", i, iN, iO, oN, oO) + } + + if input.T.UnixNano() != result.T.UnixNano() { + t.Errorf("%d-4: Failed to marshal input '%#v': got %#v", i, input, result) + } + } +} + +func TestTimeMarshalCBOR(t *testing.T) { + for _, tc := range []struct { + name string + in Time + out []byte + }{ + {name: "zero value", in: Time{}, out: []byte{0xf6}}, // null + {name: "no fractional seconds", in: Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC), out: []byte("\x541998-05-05T05:05:05Z")}, // '1998-05-05T05:05:05Z' + {name: "fractional seconds truncated", in: Date(1998, time.May, 5, 5, 5, 5, 123456789, time.UTC), out: []byte("\x541998-05-05T05:05:05Z")}, // '1998-05-05T05:05:05Z' + {name: "epoch", in: Time{Time: time.Unix(0, 0)}, out: []byte("\x541970-01-01T00:00:00Z")}, // '1970-01-01T00:00:00Z' + {name: "pre-epoch", in: Date(1960, time.January, 1, 0, 0, 0, 0, time.UTC), out: []byte("\x541960-01-01T00:00:00Z")}, // '1960-01-01T00:00:00Z' + } { + t.Run(fmt.Sprintf("%+v", tc.in), func(t *testing.T) { + got, err := tc.in.MarshalCBOR() + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tc.out, got); diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } + }) + } +} + +func TestTimeUnmarshalCBOR(t *testing.T) { + for _, tc := range []struct { + name string + in []byte + out Time + errMessage string + }{ + {name: "null", in: []byte{0xf6}, out: Time{}}, // null + {name: "no fractional seconds", in: []byte("\x58\x141998-05-05T05:05:05Z"), out: Time{Time: Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, // '1998-05-05T05:05:05Z' + {name: "fractional seconds", in: []byte("\x58\x1e1998-05-05T05:05:05.123456789Z"), out: Time{Time: Date(1998, time.May, 5, 5, 5, 5, 123456789, time.UTC).Local()}}, // '1998-05-05T05:05:05.123456789Z' + {name: "invalid cbor type", in: []byte{0x07}, out: Time{}, errMessage: "cbor: cannot unmarshal positive integer into Go value of type string"}, // 7 + {name: "malformed timestamp", in: []byte("\x45hello"), out: Time{}, errMessage: `parsing time "hello" as "2006-01-02T15:04:05Z07:00": cannot parse "hello" as "2006"`}, // 'hello' + } { + t.Run(tc.name, func(t *testing.T) { + var got Time + err := got.UnmarshalCBOR(tc.in) + if err != nil { + if tc.errMessage == "" { + t.Fatalf("want nil error, got: %v", err) + } else if gotMessage := err.Error(); tc.errMessage != gotMessage { + t.Fatalf("want error: %q, got: %q", tc.errMessage, gotMessage) + } + } else if tc.errMessage != "" { + t.Fatalf("got nil error, want: %s", tc.errMessage) + } + if diff := cmp.Diff(tc.out, got); diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } + }) + } +} + +func TestTimeProto(t *testing.T) { + cases := []struct { + input Time + }{ + {Time{}}, + {Date(1998, time.May, 5, 1, 5, 5, 0, time.Local)}, + {Date(1998, time.May, 5, 5, 5, 5, 0, time.Local)}, + } + + for _, c := range cases { + input := c.input + data, err := input.Marshal() + if err != nil { + t.Fatalf("Failed to marshal input: '%v': %v", input, err) + } + time := Time{} + if err := time.Unmarshal(data); err != nil { + t.Fatalf("Failed to unmarshal output: '%v': %v", input, err) + } + if !reflect.DeepEqual(input, time) { + t.Errorf("Marshal->Unmarshal is not idempotent: '%v' vs '%v'", input, time) + } + } +} + +func TestTimeEqual(t *testing.T) { + t1 := NewTime(time.Now()) + cases := []struct { + name string + x *Time + y *Time + result bool + }{ + {"nil =? nil", nil, nil, true}, + {"!nil =? !nil", &t1, &t1, true}, + {"nil =? !nil", nil, &t1, false}, + {"!nil =? nil", &t1, nil, false}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := c.x.Equal(c.y) + if result != c.result { + t.Errorf("Failed equality test for '%v', '%v': expected %+v, got %+v", c.x, c.y, c.result, result) + } + }) + } +} + +func TestTimeBefore(t *testing.T) { + t1 := NewTime(time.Now()) + cases := []struct { + name string + x *Time + y *Time + }{ + {"nil Unmarshal is not idempotent: '%v' vs '%v'", input, resource) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go new file mode 100644 index 0000000000..592445d9c8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go @@ -0,0 +1,551 @@ +/* +Copyright 2015 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 unstructured + +import ( + gojson "encoding/json" + "fmt" + "io" + "math/big" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/klog/v2" +) + +// NestedFieldCopy returns a deep copy of the value of a nested field. +// Returns false if the value is missing. +// No error is returned for a nil field. +// +// Note: fields passed to this function are treated as keys within the passed +// object; no array/slice syntax is supported. +func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + return runtime.DeepCopyJSONValue(val), true, nil +} + +// NestedFieldNoCopy returns a reference to a nested field. +// Returns false if value is not found and an error if unable +// to traverse obj. +// +// Note: fields passed to this function are treated as keys within the passed +// object; no array/slice syntax is supported. +func NestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) { + var val interface{} = obj + + for i, field := range fields { + if val == nil { + return nil, false, nil + } + if m, ok := val.(map[string]interface{}); ok { + val, ok = m[field] + if !ok { + return nil, false, nil + } + } else { + return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]interface{}", jsonPath(fields[:i+1]), val, val) + } + } + return val, true, nil +} + +// NestedString returns the string value of a nested field. +// Returns false if value is not found and an error if not a string. +func NestedString(obj map[string]interface{}, fields ...string) (string, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return "", found, err + } + s, ok := val.(string) + if !ok { + return "", false, fmt.Errorf("%v accessor error: %v is of the type %T, expected string", jsonPath(fields), val, val) + } + return s, true, nil +} + +// NestedBool returns the bool value of a nested field. +// Returns false if value is not found and an error if not a bool. +func NestedBool(obj map[string]interface{}, fields ...string) (bool, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return false, found, err + } + b, ok := val.(bool) + if !ok { + return false, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected bool", jsonPath(fields), val, val) + } + return b, true, nil +} + +// NestedFloat64 returns the float64 value of a nested field. +// Returns false if value is not found and an error if not a float64. +func NestedFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return 0, found, err + } + f, ok := val.(float64) + if !ok { + return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected float64", jsonPath(fields), val, val) + } + return f, true, nil +} + +// NestedInt64 returns the int64 value of a nested field. +// Returns false if value is not found and an error if not an int64. +func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return 0, found, err + } + i, ok := val.(int64) + if !ok { + return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected int64", jsonPath(fields), val, val) + } + return i, true, nil +} + +// NestedNumberAsFloat64 returns the float64 value of a nested field. If the field's value is a +// float64, it is returned. If the field's value is an int64 that can be losslessly converted to +// float64, it will be converted and returned. Returns false if value is not found and an error if +// not a float64 or an int64 that can be accurately represented as a float64. +func NestedNumberAsFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return 0, found, err + } + switch x := val.(type) { + case int64: + f, accuracy := big.NewInt(x).Float64() + if accuracy != big.Exact { + return 0, false, fmt.Errorf("%v accessor error: int64 value %v cannot be losslessly converted to float64", jsonPath(fields), x) + } + return f, true, nil + case float64: + return x, true, nil + default: + return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected float64 or int64", jsonPath(fields), val, val) + } +} + +// NestedStringSlice returns a copy of []string value of a nested field. +// Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice. +func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + m, ok := val.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []interface{}", jsonPath(fields), val, val) + } + strSlice := make([]string, 0, len(m)) + for _, v := range m { + if str, ok := v.(string); ok { + strSlice = append(strSlice, str) + } else { + return nil, false, fmt.Errorf("%v accessor error: contains non-string key in the slice: %v is of the type %T, expected string", jsonPath(fields), v, v) + } + } + return strSlice, true, nil +} + +// NestedSlice returns a deep copy of []interface{} value of a nested field. +// Returns false if value is not found and an error if not a []interface{}. +func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + _, ok := val.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []interface{}", jsonPath(fields), val, val) + } + return runtime.DeepCopyJSONValue(val).([]interface{}), true, nil +} + +// NestedStringMap returns a copy of map[string]string value of a nested field. +// Returns false if value is not found and an error if not a map[string]interface{} or contains non-string values in the map. +func NestedStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) { + m, found, err := nestedMapNoCopy(obj, false, fields...) + if !found || err != nil { + return nil, found, err + } + strMap := make(map[string]string, len(m)) + for k, v := range m { + if str, ok := v.(string); ok { + strMap[k] = str + } else { + return nil, false, fmt.Errorf("%v accessor error: contains non-string value in the map under key %q: %v is of the type %T, expected string", jsonPath(fields), k, v, v) + } + } + return strMap, true, nil +} + +// NestedNullCoercingStringMap returns a copy of map[string]string value of a nested field. +// Returns `nil, true, nil` if the value exists and is explicitly null. +// Returns `nil, false, err` if the value is not a map or a null value, or is a map and contains non-string non-null values. +// Null values in the map are coerced to "" to match json decoding behavior. +func NestedNullCoercingStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) { + m, found, err := nestedMapNoCopy(obj, true, fields...) + if !found || err != nil || m == nil { + return nil, found, err + } + strMap := make(map[string]string, len(m)) + for k, v := range m { + if str, ok := v.(string); ok { + strMap[k] = str + } else if v == nil { + strMap[k] = "" + } else { + return nil, false, fmt.Errorf("%v accessor error: contains non-string value in the map under key %q: %v is of the type %T, expected string", jsonPath(fields), k, v, v) + } + } + return strMap, true, nil +} + +// NestedMap returns a deep copy of map[string]interface{} value of a nested field. +// Returns false if value is not found and an error if not a map[string]interface{}. +func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) { + m, found, err := nestedMapNoCopy(obj, false, fields...) + if !found || err != nil { + return nil, found, err + } + return runtime.DeepCopyJSON(m), true, nil +} + +// nestedMapNoCopy returns a map[string]interface{} value of a nested field. +// Returns false if value is not found and an error if not a map[string]interface{}. +func nestedMapNoCopy(obj map[string]interface{}, tolerateNil bool, fields ...string) (map[string]interface{}, bool, error) { + val, found, err := NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + if val == nil && tolerateNil { + return nil, true, nil + } + m, ok := val.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]interface{}", jsonPath(fields), val, val) + } + return m, true, nil +} + +// SetNestedField sets the value of a nested field to a deep copy of the value provided. +// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedField(obj map[string]interface{}, value interface{}, fields ...string) error { + return setNestedFieldNoCopy(obj, runtime.DeepCopyJSONValue(value), fields...) +} + +func setNestedFieldNoCopy(obj map[string]interface{}, value interface{}, fields ...string) error { + m := obj + + for i, field := range fields[:len(fields)-1] { + if val, ok := m[field]; ok { + if valMap, ok := val.(map[string]interface{}); ok { + m = valMap + } else { + return fmt.Errorf("value cannot be set because %v is not a map[string]interface{}", jsonPath(fields[:i+1])) + } + } else { + newVal := make(map[string]interface{}) + m[field] = newVal + m = newVal + } + } + m[fields[len(fields)-1]] = value + return nil +} + +// SetNestedStringSlice sets the string slice value of a nested field. +// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedStringSlice(obj map[string]interface{}, value []string, fields ...string) error { + m := make([]interface{}, 0, len(value)) // convert []string into []interface{} + for _, v := range value { + m = append(m, v) + } + return setNestedFieldNoCopy(obj, m, fields...) +} + +// SetNestedSlice sets the slice value of a nested field. +// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedSlice(obj map[string]interface{}, value []interface{}, fields ...string) error { + return SetNestedField(obj, value, fields...) +} + +// SetNestedStringMap sets the map[string]string value of a nested field. +// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedStringMap(obj map[string]interface{}, value map[string]string, fields ...string) error { + m := make(map[string]interface{}, len(value)) // convert map[string]string into map[string]interface{} + for k, v := range value { + m[k] = v + } + return setNestedFieldNoCopy(obj, m, fields...) +} + +// SetNestedMap sets the map[string]interface{} value of a nested field. +// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}. +func SetNestedMap(obj map[string]interface{}, value map[string]interface{}, fields ...string) error { + return SetNestedField(obj, value, fields...) +} + +// RemoveNestedField removes the nested field from the obj. +func RemoveNestedField(obj map[string]interface{}, fields ...string) { + m := obj + for _, field := range fields[:len(fields)-1] { + if x, ok := m[field].(map[string]interface{}); ok { + m = x + } else { + return + } + } + delete(m, fields[len(fields)-1]) +} + +func getNestedString(obj map[string]interface{}, fields ...string) string { + val, found, err := NestedString(obj, fields...) + if !found || err != nil { + return "" + } + return val +} + +func getNestedInt64Pointer(obj map[string]interface{}, fields ...string) *int64 { + val, found, err := NestedInt64(obj, fields...) + if !found || err != nil { + return nil + } + return &val +} + +func jsonPath(fields []string) string { + return "." + strings.Join(fields, ".") +} + +func extractOwnerReference(v map[string]interface{}) metav1.OwnerReference { + // though this field is a *bool, but when decoded from JSON, it's + // unmarshalled as bool. + var controllerPtr *bool + if controller, found, err := NestedBool(v, "controller"); err == nil && found { + controllerPtr = &controller + } + var blockOwnerDeletionPtr *bool + if blockOwnerDeletion, found, err := NestedBool(v, "blockOwnerDeletion"); err == nil && found { + blockOwnerDeletionPtr = &blockOwnerDeletion + } + return metav1.OwnerReference{ + Kind: getNestedString(v, "kind"), + Name: getNestedString(v, "name"), + APIVersion: getNestedString(v, "apiVersion"), + UID: types.UID(getNestedString(v, "uid")), + Controller: controllerPtr, + BlockOwnerDeletion: blockOwnerDeletionPtr, + } +} + +// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured +// type, which can be used for generic access to objects without a predefined scheme. +// TODO: move into serializer/json. +var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{} + +type unstructuredJSONScheme struct{} + +const unstructuredJSONSchemeIdentifier runtime.Identifier = "unstructuredJSON" + +func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + var err error + if obj != nil { + err = s.decodeInto(data, obj) + } else { + obj, err = s.decode(data) + } + + if err != nil { + return nil, nil, err + } + + gvk := obj.GetObjectKind().GroupVersionKind() + if len(gvk.Kind) == 0 { + return nil, &gvk, runtime.NewMissingKindErr(string(data)) + } + // TODO(109023): require apiVersion here as well + + return obj, &gvk, nil +} + +func (s unstructuredJSONScheme) Encode(obj runtime.Object, w io.Writer) error { + if co, ok := obj.(runtime.CacheableObject); ok { + return co.CacheEncode(s.Identifier(), s.doEncode, w) + } + return s.doEncode(obj, w) +} + +func (unstructuredJSONScheme) doEncode(obj runtime.Object, w io.Writer) error { + switch t := obj.(type) { + case *Unstructured: + return json.NewEncoder(w).Encode(t.Object) + case *UnstructuredList: + items := make([]interface{}, 0, len(t.Items)) + for _, i := range t.Items { + items = append(items, i.Object) + } + listObj := make(map[string]interface{}, len(t.Object)+1) + for k, v := range t.Object { // Make a shallow copy + listObj[k] = v + } + listObj["items"] = items + return json.NewEncoder(w).Encode(listObj) + case *runtime.Unknown: + // TODO: Unstructured needs to deal with ContentType. + _, err := w.Write(t.Raw) + return err + default: + return json.NewEncoder(w).Encode(t) + } +} + +// Identifier implements runtime.Encoder interface. +func (unstructuredJSONScheme) Identifier() runtime.Identifier { + return unstructuredJSONSchemeIdentifier +} + +func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) { + type detector struct { + Items gojson.RawMessage `json:"items"` + } + var det detector + if err := json.Unmarshal(data, &det); err != nil { + return nil, err + } + + if det.Items != nil { + list := &UnstructuredList{} + err := s.decodeToList(data, list) + return list, err + } + + // No Items field, so it wasn't a list. + unstruct := &Unstructured{} + err := s.decodeToUnstructured(data, unstruct) + return unstruct, err +} + +func (s unstructuredJSONScheme) decodeInto(data []byte, obj runtime.Object) error { + switch x := obj.(type) { + case *Unstructured: + return s.decodeToUnstructured(data, x) + case *UnstructuredList: + return s.decodeToList(data, x) + default: + return json.Unmarshal(data, x) + } +} + +func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error { + m := make(map[string]interface{}) + if err := json.Unmarshal(data, &m); err != nil { + return err + } + + unstruct.Object = m + + return nil +} + +func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { + type decodeList struct { + Items []gojson.RawMessage `json:"items"` + } + + var dList decodeList + if err := json.Unmarshal(data, &dList); err != nil { + return err + } + + if err := json.Unmarshal(data, &list.Object); err != nil { + return err + } + + // For typed lists, e.g., a PodList, API server doesn't set each item's + // APIVersion and Kind. We need to set it. + listAPIVersion := list.GetAPIVersion() + listKind := list.GetKind() + itemKind := strings.TrimSuffix(listKind, "List") + + delete(list.Object, "items") + list.Items = make([]Unstructured, 0, len(dList.Items)) + for _, i := range dList.Items { + unstruct := &Unstructured{} + if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { + return err + } + // This is hacky. Set the item's Kind and APIVersion to those inferred + // from the List. + if len(unstruct.GetKind()) == 0 && len(unstruct.GetAPIVersion()) == 0 { + unstruct.SetKind(itemKind) + unstruct.SetAPIVersion(listAPIVersion) + } + list.Items = append(list.Items, *unstruct) + } + return nil +} + +type jsonFallbackEncoder struct { + encoder runtime.Encoder + identifier runtime.Identifier +} + +func NewJSONFallbackEncoder(encoder runtime.Encoder) runtime.Encoder { + result := map[string]string{ + "name": "fallback", + "base": string(encoder.Identifier()), + } + identifier, err := gojson.Marshal(result) + if err != nil { + //nolint:logcheck // Should not be reached. + klog.Fatalf("Failed marshaling identifier for jsonFallbackEncoder: %v", err) + } + return &jsonFallbackEncoder{ + encoder: encoder, + identifier: runtime.Identifier(identifier), + } +} + +func (c *jsonFallbackEncoder) Encode(obj runtime.Object, w io.Writer) error { + // There is no need to handle runtime.CacheableObject, as we only + // fallback to other encoders here. + err := c.encoder.Encode(obj, w) + if runtime.IsNotRegisteredError(err) { + switch obj.(type) { + case *Unstructured, *UnstructuredList: + return UnstructuredJSONScheme.Encode(obj, w) + } + } + return err +} + +// Identifier implements runtime.Encoder interface. +func (c *jsonFallbackEncoder) Identifier() runtime.Identifier { + return c.identifier +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers_test.go new file mode 100644 index 0000000000..7bbb9f91f1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers_test.go @@ -0,0 +1,388 @@ +/* +Copyright 2017 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 unstructured + +import ( + "io/ioutil" + "math" + "reflect" + "strings" + "sync" + "testing" + + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" + + "github.com/stretchr/testify/assert" +) + +// TestCodecOfUnstructuredList tests that there are no data races in Encode(). +// i.e. that it does not mutate the object being encoded. +func TestCodecOfUnstructuredList(t *testing.T) { + var wg sync.WaitGroup + concurrency := 10 + list := UnstructuredList{ + Object: map[string]interface{}{}, + } + wg.Add(concurrency) + for i := 0; i < concurrency; i++ { + go func() { + defer wg.Done() + assert.NoError(t, UnstructuredJSONScheme.Encode(&list, ioutil.Discard)) + }() + } + wg.Wait() +} + +func TestRemoveNestedField(t *testing.T) { + obj := map[string]interface{}{ + "x": map[string]interface{}{ + "y": 1, + "a": "foo", + }, + } + RemoveNestedField(obj, "x", "a") + assert.Len(t, obj["x"], 1) + RemoveNestedField(obj, "x", "y") + assert.Empty(t, obj["x"]) + RemoveNestedField(obj, "x") + assert.Empty(t, obj) + RemoveNestedField(obj, "x") // Remove of a non-existent field + assert.Empty(t, obj) +} + +func TestNestedFieldNoCopy(t *testing.T) { + target := map[string]interface{}{"foo": "bar"} + + obj := map[string]interface{}{ + "a": map[string]interface{}{ + "b": target, + "c": nil, + "d": []interface{}{"foo"}, + "e": []interface{}{ + map[string]interface{}{ + "f": "bar", + }, + }, + }, + } + + // case 1: field exists and is non-nil + res, exists, err := NestedFieldNoCopy(obj, "a", "b") + assert.True(t, exists) + assert.NoError(t, err) + assert.Equal(t, target, res) + target["foo"] = "baz" + assert.Equal(t, target["foo"], res.(map[string]interface{})["foo"], "result should be a reference to the expected item") + + // case 2: field exists and is nil + res, exists, err = NestedFieldNoCopy(obj, "a", "c") + assert.True(t, exists) + assert.NoError(t, err) + assert.Nil(t, res) + + // case 3: error traversing obj + res, exists, err = NestedFieldNoCopy(obj, "a", "d", "foo") + assert.False(t, exists) + assert.Error(t, err) + assert.Nil(t, res) + + // case 4: field does not exist + res, exists, err = NestedFieldNoCopy(obj, "a", "g") + assert.False(t, exists) + assert.NoError(t, err) + assert.Nil(t, res) + + // case 5: intermediate field does not exist + res, exists, err = NestedFieldNoCopy(obj, "a", "g", "f") + assert.False(t, exists) + assert.NoError(t, err) + assert.Nil(t, res) + + // case 6: intermediate field is null + // (background: happens easily in YAML) + res, exists, err = NestedFieldNoCopy(obj, "a", "c", "f") + assert.False(t, exists) + assert.NoError(t, err) + assert.Nil(t, res) + + // case 7: array/slice syntax is not supported + // (background: users may expect this to be supported) + res, exists, err = NestedFieldNoCopy(obj, "a", "e[0]") + assert.False(t, exists) + assert.NoError(t, err) + assert.Nil(t, res) +} + +func TestNestedFieldCopy(t *testing.T) { + target := map[string]interface{}{"foo": "bar"} + + obj := map[string]interface{}{ + "a": map[string]interface{}{ + "b": target, + "c": nil, + "d": []interface{}{"foo"}, + }, + } + + // case 1: field exists and is non-nil + res, exists, err := NestedFieldCopy(obj, "a", "b") + assert.True(t, exists) + assert.NoError(t, err) + assert.Equal(t, target, res) + target["foo"] = "baz" + assert.NotEqual(t, target["foo"], res.(map[string]interface{})["foo"], "result should be a copy of the expected item") + + // case 2: field exists and is nil + res, exists, err = NestedFieldCopy(obj, "a", "c") + assert.True(t, exists) + assert.NoError(t, err) + assert.Nil(t, res) + + // case 3: error traversing obj + res, exists, err = NestedFieldCopy(obj, "a", "d", "foo") + assert.False(t, exists) + assert.Error(t, err) + assert.Nil(t, res) + + // case 4: field does not exist + res, exists, err = NestedFieldCopy(obj, "a", "e") + assert.False(t, exists) + assert.NoError(t, err) + assert.Nil(t, res) +} + +func TestCacheableObject(t *testing.T) { + runtimetesting.CacheableObjectTest(t, UnstructuredJSONScheme) +} + +func TestSetNestedStringSlice(t *testing.T) { + obj := map[string]interface{}{ + "x": map[string]interface{}{ + "y": 1, + "a": "foo", + }, + } + + err := SetNestedStringSlice(obj, []string{"bar"}, "x", "z") + assert.NoError(t, err) + assert.Len(t, obj["x"], 3) + assert.Len(t, obj["x"].(map[string]interface{})["z"], 1) + assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].([]interface{})[0]) +} + +func TestSetNestedSlice(t *testing.T) { + obj := map[string]interface{}{ + "x": map[string]interface{}{ + "y": 1, + "a": "foo", + }, + } + + err := SetNestedSlice(obj, []interface{}{"bar"}, "x", "z") + assert.NoError(t, err) + assert.Len(t, obj["x"], 3) + assert.Len(t, obj["x"].(map[string]interface{})["z"], 1) + assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].([]interface{})[0]) +} + +func TestSetNestedStringMap(t *testing.T) { + obj := map[string]interface{}{ + "x": map[string]interface{}{ + "y": 1, + "a": "foo", + }, + } + + err := SetNestedStringMap(obj, map[string]string{"b": "bar"}, "x", "z") + assert.NoError(t, err) + assert.Len(t, obj["x"], 3) + assert.Len(t, obj["x"].(map[string]interface{})["z"], 1) + assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"]) +} + +func TestSetNestedMap(t *testing.T) { + obj := map[string]interface{}{ + "x": map[string]interface{}{ + "y": 1, + "a": "foo", + }, + } + + err := SetNestedMap(obj, map[string]interface{}{"b": "bar"}, "x", "z") + assert.NoError(t, err) + assert.Len(t, obj["x"], 3) + assert.Len(t, obj["x"].(map[string]interface{})["z"], 1) + assert.Equal(t, "bar", obj["x"].(map[string]interface{})["z"].(map[string]interface{})["b"]) +} + +func TestNestedNumberAsFloat64(t *testing.T) { + for _, tc := range []struct { + name string + obj map[string]interface{} + path []string + wantFloat64 float64 + wantBool bool + wantErrMessage string + }{ + { + name: "not found", + obj: nil, + path: []string{"missing"}, + wantFloat64: 0, + wantBool: false, + wantErrMessage: "", + }, + { + name: "found float64", + obj: map[string]interface{}{"value": float64(42)}, + path: []string{"value"}, + wantFloat64: 42, + wantBool: true, + wantErrMessage: "", + }, + { + name: "found unexpected type bool", + obj: map[string]interface{}{"value": true}, + path: []string{"value"}, + wantFloat64: 0, + wantBool: false, + wantErrMessage: ".value accessor error: true is of the type bool, expected float64 or int64", + }, + { + name: "found int64", + obj: map[string]interface{}{"value": int64(42)}, + path: []string{"value"}, + wantFloat64: 42, + wantBool: true, + wantErrMessage: "", + }, + { + name: "found int64 not representable as float64", + obj: map[string]interface{}{"value": int64(math.MaxInt64)}, + path: []string{"value"}, + wantFloat64: 0, + wantBool: false, + wantErrMessage: ".value accessor error: int64 value 9223372036854775807 cannot be losslessly converted to float64", + }, + } { + t.Run(tc.name, func(t *testing.T) { + gotFloat64, gotBool, gotErr := NestedNumberAsFloat64(tc.obj, tc.path...) + if gotFloat64 != tc.wantFloat64 { + t.Errorf("got %v, wanted %v", gotFloat64, tc.wantFloat64) + } + if gotBool != tc.wantBool { + t.Errorf("got %t, wanted %t", gotBool, tc.wantBool) + } + if tc.wantErrMessage != "" { + if gotErr == nil { + t.Errorf("got nil error, wanted %s", tc.wantErrMessage) + } else if gotErrMessage := gotErr.Error(); gotErrMessage != tc.wantErrMessage { + t.Errorf("wanted error %q, got: %v", gotErrMessage, tc.wantErrMessage) + } + } else if gotErr != nil { + t.Errorf("wanted nil error, got %v", gotErr) + } + }) + } +} + +func TestNestedNullCoercingStringMap(t *testing.T) { + for _, tc := range []struct { + name string + obj map[string]interface{} + path []string + wantObj map[string]string + wantFound bool + wantErrMessage string + }{ + { + name: "missing map", + obj: nil, + path: []string{"path"}, + wantObj: nil, + wantFound: false, + wantErrMessage: "", + }, + { + name: "null map", + obj: map[string]interface{}{"path": nil}, + path: []string{"path"}, + wantObj: nil, + wantFound: true, + wantErrMessage: "", + }, + { + name: "non map", + obj: map[string]interface{}{"path": 0}, + path: []string{"path"}, + wantObj: nil, + wantFound: false, + wantErrMessage: "type int", + }, + { + name: "empty map", + obj: map[string]interface{}{"path": map[string]interface{}{}}, + path: []string{"path"}, + wantObj: map[string]string{}, + wantFound: true, + wantErrMessage: "", + }, + { + name: "string value", + obj: map[string]interface{}{"path": map[string]interface{}{"a": "1", "b": "2"}}, + path: []string{"path"}, + wantObj: map[string]string{"a": "1", "b": "2"}, + wantFound: true, + wantErrMessage: "", + }, + { + name: "null value", + obj: map[string]interface{}{"path": map[string]interface{}{"a": "1", "b": nil}}, + path: []string{"path"}, + wantObj: map[string]string{"a": "1", "b": ""}, + wantFound: true, + wantErrMessage: "", + }, + { + name: "invalid value", + obj: map[string]interface{}{"path": map[string]interface{}{"a": "1", "b": nil, "c": 0}}, + path: []string{"path"}, + wantObj: nil, + wantFound: false, + wantErrMessage: `key "c": 0`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + gotObj, gotFound, gotErr := NestedNullCoercingStringMap(tc.obj, tc.path...) + if !reflect.DeepEqual(gotObj, tc.wantObj) { + t.Errorf("got %#v, wanted %#v", gotObj, tc.wantObj) + } + if gotFound != tc.wantFound { + t.Errorf("got %v, wanted %v", gotFound, tc.wantFound) + } + if tc.wantErrMessage != "" { + if gotErr == nil { + t.Errorf("got nil error, wanted %s", tc.wantErrMessage) + } else if gotErrMessage := gotErr.Error(); !strings.Contains(gotErrMessage, tc.wantErrMessage) { + t.Errorf("wanted error %q, got: %v", gotErrMessage, tc.wantErrMessage) + } + } else if gotErr != nil { + t.Errorf("wanted nil error, got %v", gotErr) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go new file mode 100644 index 0000000000..fdb0c86297 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go @@ -0,0 +1,493 @@ +/* +Copyright 2015 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 unstructured + +import ( + "bytes" + "errors" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +// Unstructured allows objects that do not have Golang structs registered to be manipulated +// generically. This can be used to deal with the API objects from a plug-in. Unstructured +// objects still have functioning TypeMeta features-- kind, version, etc. +// +// WARNING: This object has accessors for the v1 standard metadata. You *MUST NOT* use this +// type if you are dealing with objects that are not in the server meta v1 schema. +// +// TODO: make the serialization part of this type distinct from the field accessors. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:deepcopy-gen=true +type Unstructured struct { + // Object is a JSON compatible map with string, float, int, bool, []interface{}, or + // map[string]interface{} + // children. + Object map[string]interface{} +} + +var _ metav1.Object = &Unstructured{} +var _ runtime.Unstructured = &Unstructured{} +var _ metav1.ListInterface = &Unstructured{} + +func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } + +func (obj *Unstructured) IsList() bool { + field, ok := obj.Object["items"] + if !ok { + return false + } + _, ok = field.([]interface{}) + return ok +} +func (obj *Unstructured) ToList() (*UnstructuredList, error) { + if !obj.IsList() { + // return an empty list back + return &UnstructuredList{Object: obj.Object}, nil + } + + ret := &UnstructuredList{} + ret.Object = obj.Object + + err := obj.EachListItem(func(item runtime.Object) error { + castItem := item.(*Unstructured) + ret.Items = append(ret.Items, *castItem) + return nil + }) + if err != nil { + return nil, err + } + + return ret, nil +} + +func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error { + field, ok := obj.Object["items"] + if !ok { + return errors.New("content is not a list") + } + items, ok := field.([]interface{}) + if !ok { + return fmt.Errorf("content is not a list: %T", field) + } + for _, item := range items { + child, ok := item.(map[string]interface{}) + if !ok { + return fmt.Errorf("items member is not an object: %T", child) + } + if err := fn(&Unstructured{Object: child}); err != nil { + return err + } + } + return nil +} + +func (obj *Unstructured) EachListItemWithAlloc(fn func(runtime.Object) error) error { + // EachListItem has allocated a new Object for the user, we can use it directly. + return obj.EachListItem(fn) +} + +func (obj *Unstructured) UnstructuredContent() map[string]interface{} { + if obj.Object == nil { + return make(map[string]interface{}) + } + return obj.Object +} + +func (obj *Unstructured) SetUnstructuredContent(content map[string]interface{}) { + obj.Object = content +} + +// MarshalJSON ensures that the unstructured object produces proper +// JSON when passed to Go's standard JSON library. +func (u *Unstructured) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + err := UnstructuredJSONScheme.Encode(u, &buf) + return buf.Bytes(), err +} + +// UnmarshalJSON ensures that the unstructured object properly decodes +// JSON when passed to Go's standard JSON library. +func (u *Unstructured) UnmarshalJSON(b []byte) error { + _, _, err := UnstructuredJSONScheme.Decode(b, nil, u) + return err +} + +// NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data. +// This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info. +func (in *Unstructured) NewEmptyInstance() runtime.Unstructured { + out := new(Unstructured) + if in != nil { + out.GetObjectKind().SetGroupVersionKind(in.GetObjectKind().GroupVersionKind()) + } + return out +} + +func (in *Unstructured) DeepCopy() *Unstructured { + if in == nil { + return nil + } + out := new(Unstructured) + *out = *in + out.Object = runtime.DeepCopyJSON(in.Object) + return out +} + +func (u *Unstructured) setNestedField(value interface{}, fields ...string) { + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + SetNestedField(u.Object, value, fields...) +} + +func (u *Unstructured) setNestedStringSlice(value []string, fields ...string) { + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + SetNestedStringSlice(u.Object, value, fields...) +} + +func (u *Unstructured) setNestedSlice(value []interface{}, fields ...string) { + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + SetNestedSlice(u.Object, value, fields...) +} + +func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) { + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + SetNestedStringMap(u.Object, value, fields...) +} + +func (u *Unstructured) GetOwnerReferences() []metav1.OwnerReference { + field, found, err := NestedFieldNoCopy(u.Object, "metadata", "ownerReferences") + if !found || err != nil { + return nil + } + original, ok := field.([]interface{}) + if !ok { + return nil + } + ret := make([]metav1.OwnerReference, 0, len(original)) + for _, obj := range original { + o, ok := obj.(map[string]interface{}) + if !ok { + // expected map[string]interface{}, got something else + return nil + } + ret = append(ret, extractOwnerReference(o)) + } + return ret +} + +func (u *Unstructured) SetOwnerReferences(references []metav1.OwnerReference) { + if references == nil { + RemoveNestedField(u.Object, "metadata", "ownerReferences") + return + } + + newReferences := make([]interface{}, 0, len(references)) + for _, reference := range references { + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&reference) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to convert Owner Reference: %v", err)) + continue + } + newReferences = append(newReferences, out) + } + u.setNestedField(newReferences, "metadata", "ownerReferences") +} + +func (u *Unstructured) GetAPIVersion() string { + return getNestedString(u.Object, "apiVersion") +} + +func (u *Unstructured) SetAPIVersion(version string) { + u.setNestedField(version, "apiVersion") +} + +func (u *Unstructured) GetKind() string { + return getNestedString(u.Object, "kind") +} + +func (u *Unstructured) SetKind(kind string) { + u.setNestedField(kind, "kind") +} + +func (u *Unstructured) GetNamespace() string { + return getNestedString(u.Object, "metadata", "namespace") +} + +func (u *Unstructured) SetNamespace(namespace string) { + if len(namespace) == 0 { + RemoveNestedField(u.Object, "metadata", "namespace") + return + } + u.setNestedField(namespace, "metadata", "namespace") +} + +func (u *Unstructured) GetName() string { + return getNestedString(u.Object, "metadata", "name") +} + +func (u *Unstructured) SetName(name string) { + if len(name) == 0 { + RemoveNestedField(u.Object, "metadata", "name") + return + } + u.setNestedField(name, "metadata", "name") +} + +func (u *Unstructured) GetGenerateName() string { + return getNestedString(u.Object, "metadata", "generateName") +} + +func (u *Unstructured) SetGenerateName(generateName string) { + if len(generateName) == 0 { + RemoveNestedField(u.Object, "metadata", "generateName") + return + } + u.setNestedField(generateName, "metadata", "generateName") +} + +func (u *Unstructured) GetUID() types.UID { + return types.UID(getNestedString(u.Object, "metadata", "uid")) +} + +func (u *Unstructured) SetUID(uid types.UID) { + if len(string(uid)) == 0 { + RemoveNestedField(u.Object, "metadata", "uid") + return + } + u.setNestedField(string(uid), "metadata", "uid") +} + +func (u *Unstructured) GetResourceVersion() string { + return getNestedString(u.Object, "metadata", "resourceVersion") +} + +func (u *Unstructured) SetResourceVersion(resourceVersion string) { + if len(resourceVersion) == 0 { + RemoveNestedField(u.Object, "metadata", "resourceVersion") + return + } + u.setNestedField(resourceVersion, "metadata", "resourceVersion") +} + +func (u *Unstructured) GetGeneration() int64 { + val, found, err := NestedInt64(u.Object, "metadata", "generation") + if !found || err != nil { + return 0 + } + return val +} + +func (u *Unstructured) SetGeneration(generation int64) { + if generation == 0 { + RemoveNestedField(u.Object, "metadata", "generation") + return + } + u.setNestedField(generation, "metadata", "generation") +} + +func (u *Unstructured) GetSelfLink() string { + return getNestedString(u.Object, "metadata", "selfLink") +} + +func (u *Unstructured) SetSelfLink(selfLink string) { + if len(selfLink) == 0 { + RemoveNestedField(u.Object, "metadata", "selfLink") + return + } + u.setNestedField(selfLink, "metadata", "selfLink") +} + +func (u *Unstructured) GetContinue() string { + return getNestedString(u.Object, "metadata", "continue") +} + +func (u *Unstructured) SetContinue(c string) { + if len(c) == 0 { + RemoveNestedField(u.Object, "metadata", "continue") + return + } + u.setNestedField(c, "metadata", "continue") +} + +func (u *Unstructured) GetRemainingItemCount() *int64 { + return getNestedInt64Pointer(u.Object, "metadata", "remainingItemCount") +} + +func (u *Unstructured) SetRemainingItemCount(c *int64) { + if c == nil { + RemoveNestedField(u.Object, "metadata", "remainingItemCount") + } else { + u.setNestedField(*c, "metadata", "remainingItemCount") + } +} + +func (u *Unstructured) GetCreationTimestamp() metav1.Time { + var timestamp metav1.Time + timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "creationTimestamp")) + return timestamp +} + +func (u *Unstructured) SetCreationTimestamp(timestamp metav1.Time) { + ts, _ := timestamp.MarshalQueryParameter() + if len(ts) == 0 || timestamp.Time.IsZero() { + RemoveNestedField(u.Object, "metadata", "creationTimestamp") + return + } + u.setNestedField(ts, "metadata", "creationTimestamp") +} + +func (u *Unstructured) GetDeletionTimestamp() *metav1.Time { + var timestamp metav1.Time + timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "deletionTimestamp")) + if timestamp.IsZero() { + return nil + } + return ×tamp +} + +func (u *Unstructured) SetDeletionTimestamp(timestamp *metav1.Time) { + if timestamp == nil { + RemoveNestedField(u.Object, "metadata", "deletionTimestamp") + return + } + ts, _ := timestamp.MarshalQueryParameter() + u.setNestedField(ts, "metadata", "deletionTimestamp") +} + +func (u *Unstructured) GetDeletionGracePeriodSeconds() *int64 { + val, found, err := NestedInt64(u.Object, "metadata", "deletionGracePeriodSeconds") + if !found || err != nil { + return nil + } + return &val +} + +func (u *Unstructured) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds *int64) { + if deletionGracePeriodSeconds == nil { + RemoveNestedField(u.Object, "metadata", "deletionGracePeriodSeconds") + return + } + u.setNestedField(*deletionGracePeriodSeconds, "metadata", "deletionGracePeriodSeconds") +} + +func (u *Unstructured) GetLabels() map[string]string { + m, _, _ := NestedNullCoercingStringMap(u.Object, "metadata", "labels") + return m +} + +func (u *Unstructured) SetLabels(labels map[string]string) { + if labels == nil { + RemoveNestedField(u.Object, "metadata", "labels") + return + } + u.setNestedMap(labels, "metadata", "labels") +} + +func (u *Unstructured) GetAnnotations() map[string]string { + m, _, _ := NestedNullCoercingStringMap(u.Object, "metadata", "annotations") + return m +} + +func (u *Unstructured) SetAnnotations(annotations map[string]string) { + if annotations == nil { + RemoveNestedField(u.Object, "metadata", "annotations") + return + } + u.setNestedMap(annotations, "metadata", "annotations") +} + +func (u *Unstructured) SetGroupVersionKind(gvk schema.GroupVersionKind) { + u.SetAPIVersion(gvk.GroupVersion().String()) + u.SetKind(gvk.Kind) +} + +func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind { + gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) + if err != nil { + return schema.GroupVersionKind{} + } + gvk := gv.WithKind(u.GetKind()) + return gvk +} + +func (u *Unstructured) GetFinalizers() []string { + val, _, _ := NestedStringSlice(u.Object, "metadata", "finalizers") + return val +} + +func (u *Unstructured) SetFinalizers(finalizers []string) { + if finalizers == nil { + RemoveNestedField(u.Object, "metadata", "finalizers") + return + } + u.setNestedStringSlice(finalizers, "metadata", "finalizers") +} + +func (u *Unstructured) GetManagedFields() []metav1.ManagedFieldsEntry { + v, found, err := NestedFieldNoCopy(u.Object, "metadata", "managedFields") + if !found || err != nil { + return nil + } + items, ok := v.([]interface{}) + if !ok { + return nil + } + managedFields := []metav1.ManagedFieldsEntry{} + for _, item := range items { + m, ok := item.(map[string]interface{}) + if !ok { + utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object, item %v is not a map", item)) + return nil + } + out := metav1.ManagedFieldsEntry{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, &out); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to retrieve managedFields for object: %v", err)) + return nil + } + managedFields = append(managedFields, out) + } + return managedFields +} + +func (u *Unstructured) SetManagedFields(managedFields []metav1.ManagedFieldsEntry) { + if managedFields == nil { + RemoveNestedField(u.Object, "metadata", "managedFields") + return + } + items := []interface{}{} + for _, managedFieldsEntry := range managedFields { + out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&managedFieldsEntry) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to set managedFields for object: %v", err)) + return + } + items = append(items, out) + } + u.setNestedSlice(items, "metadata", "managedFields") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_conversion_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_conversion_test.go new file mode 100644 index 0000000000..8b4d092ec7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_conversion_test.go @@ -0,0 +1,532 @@ +/* +Copyright 2019 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 unstructured_test + +import ( + "fmt" + "reflect" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/apis/testapigroup" + testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/test" +) + +func TestObjectToUnstructuredConversion(t *testing.T) { + scheme, _ := test.TestScheme() + testCases := []struct { + name string + objectToConvert runtime.Object + expectedErr error + expectedConvertedUnstructured *unstructured.Unstructured + }{ + { + name: "convert nil object to unstructured should fail", + objectToConvert: nil, + expectedErr: fmt.Errorf("unable to convert object type to Unstructured, must be a runtime.Object"), + expectedConvertedUnstructured: &unstructured.Unstructured{}, + }, + { + name: "convert versioned empty object to unstructured should work", + objectToConvert: &testapigroupv1.Carp{}, + expectedConvertedUnstructured: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{}, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{}, + }, + }, + }, + { + name: "convert valid versioned object to unstructured should work", + objectToConvert: &testapigroupv1.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroupv1.CarpSpec{ + Hostname: "example.com", + }, + }, + expectedConvertedUnstructured: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + }, + { + name: "convert hub-versioned object to unstructured should fail", + objectToConvert: &testapigroup.Carp{}, + expectedErr: fmt.Errorf("unable to convert the internal object type *testapigroup.Carp to Unstructured without providing a preferred version to convert to"), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + outUnstructured := &unstructured.Unstructured{} + err := scheme.Convert(testCase.objectToConvert, outUnstructured, nil) + if err != nil { + assert.Equal(t, testCase.expectedErr, err) + return + } + assert.Equal(t, testCase.expectedConvertedUnstructured, outUnstructured) + }) + } +} + +func TestUnstructuredToObjectConversion(t *testing.T) { + scheme, _ := test.TestScheme() + testCases := []struct { + name string + unstructuredToConvert *unstructured.Unstructured + convertingObject runtime.Object + expectPanic bool + expectedErrFunc func(err error) bool + expectedConvertedObject runtime.Object + }{ + { + name: "convert empty unstructured w/o gvk to versioned object should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + convertingObject: &testapigroupv1.Carp{}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) + }, + }, + { + name: "convert empty versioned unstructured to versioned object should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + }, + convertingObject: &testapigroupv1.Carp{}, + expectedConvertedObject: &testapigroupv1.Carp{}, + }, + { + name: "convert empty unstructured w/o gvk to versioned object should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + convertingObject: &testapigroupv1.Carp{}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) + }, + }, + { + name: "convert valid versioned unstructured to versioned object should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + convertingObject: &testapigroupv1.Carp{}, + expectedConvertedObject: &testapigroupv1.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroupv1.CarpSpec{ + Hostname: "example.com", + }, + }, + }, + { + name: "convert valid versioned unstructured to hub-versioned object should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + convertingObject: &testapigroup.Carp{}, + expectedConvertedObject: &testapigroup.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroup.CarpSpec{ + Hostname: "example.com", + }, + }, + }, + { + name: "convert unexisting-versioned unstructured to hub-versioned object should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v9", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + convertingObject: &testapigroup.Carp{}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewNotRegisteredGVKErrForTarget( + scheme.Name(), + schema.GroupVersionKind{Group: "", Version: "v9", Kind: "Carp"}, + nil, + )) + }, + }, + { + name: "convert valid versioned unstructured to object w/ a mismatching kind should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + convertingObject: &metav1.CreateOptions{}, + expectedErrFunc: func(err error) bool { + return strings.HasPrefix(err.Error(), "converting (v1.Carp) to (v1.CreateOptions):") + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + defer func() { + v := recover() + assert.Equal(t, testCase.expectPanic, v != nil, "unexpected panic") + }() + outObject := testCase.convertingObject.DeepCopyObject() + // Convert by specifying destination object + err := scheme.Convert(testCase.unstructuredToConvert, outObject, nil) + if err != nil { + if testCase.expectedErrFunc != nil { + if !testCase.expectedErrFunc(err) { + t.Errorf("error mismatched: %v", err) + } + } + return + } + assert.Equal(t, testCase.expectedConvertedObject, outObject) + }) + } +} + +func TestUnstructuredToGVConversion(t *testing.T) { + scheme, _ := test.TestScheme() + // HACK: registering fake internal/v1beta1 api + scheme.AddKnownTypes(schema.GroupVersion{Group: "foo", Version: "v1beta1"}, &testapigroup.Carp{}) + scheme.AddKnownTypes(schema.GroupVersion{Group: "foo", Version: "__internal"}, &testapigroup.Carp{}) + + testCases := []struct { + name string + unstructuredToConvert *unstructured.Unstructured + targetGV schema.GroupVersion + expectPanic bool + expectedErrFunc func(err error) bool + expectedConvertedObject runtime.Object + }{ + { + name: "convert versioned unstructured to valid external version should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "v1"}, + expectedConvertedObject: &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Carp", + }, + }, + }, + { + name: "convert hub-versioned unstructured to hub version should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "__internal", + "kind": "Carp", + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "__internal"}, + expectedConvertedObject: &testapigroup.Carp{}, + }, + { + name: "convert empty unstructured w/o gvk to versioned should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + targetGV: schema.GroupVersion{Group: "", Version: "v1"}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewMissingKindErr("unstructured object has no kind")) + }, + expectedConvertedObject: nil, + }, + { + name: "convert versioned unstructured to mismatching external version should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + }, + targetGV: schema.GroupVersion{Group: "foo", Version: "v1beta1"}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewNotRegisteredErrForTarget( + scheme.Name(), reflect.TypeOf(testapigroupv1.Carp{}), schema.GroupVersion{Group: "foo", Version: "v1beta1"})) + }, + expectedConvertedObject: nil, + }, + { + name: "convert versioned unstructured to mismatching internal version should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + }, + targetGV: schema.GroupVersion{Group: "foo", Version: "__internal"}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewNotRegisteredErrForTarget( + scheme.Name(), reflect.TypeOf(testapigroupv1.Carp{}), schema.GroupVersion{Group: "foo", Version: "__internal"})) + }, + expectedConvertedObject: nil, + }, + { + name: "convert valid versioned unstructured to its own version should work", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "v1"}, + expectedConvertedObject: &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Carp", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroupv1.CarpSpec{ + Hostname: "example.com", + }, + }, + }, + { + name: "convert valid versioned unstructured to hub-version should work ignoring type meta", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "__internal"}, + expectedConvertedObject: &testapigroup.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "noxu", + }, + Spec: testapigroup.CarpSpec{ + Hostname: "example.com", + }, + }, + }, + { + name: "convert valid versioned unstructured to unexisting version should fail", + unstructuredToConvert: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "noxu", + }, + "spec": map[string]interface{}{ + "hostname": "example.com", + }, + "status": map[string]interface{}{}, + }, + }, + targetGV: schema.GroupVersion{Group: "", Version: "v9"}, + expectedErrFunc: func(err error) bool { + return reflect.DeepEqual(err, runtime.NewNotRegisteredGVKErrForTarget( + scheme.Name(), + schema.GroupVersionKind{Group: "", Version: "v9", Kind: "Carp"}, + nil, + )) + }, + expectedConvertedObject: nil, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + defer func() { + v := recover() + assert.Equal(t, testCase.expectPanic, v != nil, "unexpected panic") + }() + // Convert by specifying destination object + outObject, err := scheme.ConvertToVersion(testCase.unstructuredToConvert, testCase.targetGV) + if testCase.expectedErrFunc != nil { + if !testCase.expectedErrFunc(err) { + t.Errorf("error mismatched: %v", err) + } + } + assert.Equal(t, testCase.expectedConvertedObject, outObject) + }) + } +} + +func TestUnstructuredToUnstructuredConversion(t *testing.T) { + // eventually, we don't want any inter-unstructured conversion happen, but for now, the conversion + // just copy/pastes + scheme, _ := test.TestScheme() + inUnstructured := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + }, + } + outUnstructured := &unstructured.Unstructured{} + err := scheme.Convert(inUnstructured, outUnstructured, nil) + assert.NoError(t, err) + assert.Equal(t, inUnstructured, outUnstructured) +} + +func benchmarkCarp() *testapigroupv1.Carp { + t := metav1.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + return &testapigroupv1.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + Spec: testapigroupv1.CarpSpec{ + RestartPolicy: "restart", + NodeSelector: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + ServiceAccountName: "service-account", + HostNetwork: false, + HostPID: true, + Subdomain: "hostname.subdomain.namespace.svc.domain", + }, + Status: testapigroupv1.CarpStatus{ + Phase: "phase", + Conditions: []testapigroupv1.CarpCondition{ + { + Type: "condition1", + Status: "true", + LastProbeTime: t, + LastTransitionTime: t, + Reason: "reason", + Message: "message", + }, + }, + Message: "message", + Reason: "reason", + HostIP: "1.2.3.4", + }, + } +} + +func BenchmarkToUnstructured(b *testing.B) { + carp := benchmarkCarp() + converter := runtime.DefaultUnstructuredConverter + b.ResetTimer() + + for i := 0; i < b.N; i++ { + result, err := converter.ToUnstructured(carp) + if err != nil { + b.Fatalf("Unexpected conversion error: %v", err) + } + if len(result) != 3 { + b.Errorf("Unexpected conversion result: %#v", result) + } + } +} + +func BenchmarkFromUnstructured(b *testing.B) { + carp := benchmarkCarp() + converter := runtime.DefaultUnstructuredConverter + unstr, err := converter.ToUnstructured(carp) + if err != nil { + b.Fatalf("Unexpected conversion error: %v", err) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + result := testapigroupv1.Carp{} + if err := converter.FromUnstructured(unstr, &result); err != nil { + b.Fatalf("Unexpected conversion error: %v", err) + } + if result.Status.Phase != "phase" { + b.Errorf("Unexpected conversion result: %#v", result) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list.go new file mode 100644 index 0000000000..82beda2a29 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list.go @@ -0,0 +1,219 @@ +/* +Copyright 2015 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 unstructured + +import ( + "bytes" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var _ runtime.Unstructured = &UnstructuredList{} +var _ metav1.ListInterface = &UnstructuredList{} + +// UnstructuredList allows lists that do not have Golang structs +// registered to be manipulated generically. This can be used to deal +// with the API lists from a plug-in. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:deepcopy-gen=true +type UnstructuredList struct { + Object map[string]interface{} + + // Items is a list of unstructured objects. + Items []Unstructured `json:"items"` +} + +func (u *UnstructuredList) GetObjectKind() schema.ObjectKind { return u } + +func (u *UnstructuredList) IsList() bool { return true } + +func (u *UnstructuredList) EachListItem(fn func(runtime.Object) error) error { + for i := range u.Items { + if err := fn(&u.Items[i]); err != nil { + return err + } + } + return nil +} + +func (u *UnstructuredList) EachListItemWithAlloc(fn func(runtime.Object) error) error { + for i := range u.Items { + if err := fn(&Unstructured{Object: u.Items[i].Object}); err != nil { + return err + } + } + return nil +} + +// NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data. +// This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info. +func (u *UnstructuredList) NewEmptyInstance() runtime.Unstructured { + out := new(UnstructuredList) + if u != nil { + out.SetGroupVersionKind(u.GroupVersionKind()) + } + return out +} + +// UnstructuredContent returns a map contain an overlay of the Items field onto +// the Object field. Items always overwrites overlay. +func (u *UnstructuredList) UnstructuredContent() map[string]interface{} { + out := make(map[string]interface{}, len(u.Object)+1) + + // shallow copy every property + for k, v := range u.Object { + out[k] = v + } + + items := make([]interface{}, len(u.Items)) + for i, item := range u.Items { + items[i] = item.UnstructuredContent() + } + out["items"] = items + return out +} + +// SetUnstructuredContent obeys the conventions of List and keeps Items and the items +// array in sync. If items is not an array of objects in the incoming map, then any +// mismatched item will be removed. +func (obj *UnstructuredList) SetUnstructuredContent(content map[string]interface{}) { + obj.Object = content + if content == nil { + obj.Items = nil + return + } + items, ok := obj.Object["items"].([]interface{}) + if !ok || items == nil { + items = []interface{}{} + } + unstructuredItems := make([]Unstructured, 0, len(items)) + newItems := make([]interface{}, 0, len(items)) + for _, item := range items { + o, ok := item.(map[string]interface{}) + if !ok { + continue + } + unstructuredItems = append(unstructuredItems, Unstructured{Object: o}) + newItems = append(newItems, o) + } + obj.Items = unstructuredItems + obj.Object["items"] = newItems +} + +func (u *UnstructuredList) DeepCopy() *UnstructuredList { + if u == nil { + return nil + } + out := new(UnstructuredList) + *out = *u + out.Object = runtime.DeepCopyJSON(u.Object) + out.Items = make([]Unstructured, len(u.Items)) + for i := range u.Items { + u.Items[i].DeepCopyInto(&out.Items[i]) + } + return out +} + +// MarshalJSON ensures that the unstructured list object produces proper +// JSON when passed to Go's standard JSON library. +func (u *UnstructuredList) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + err := UnstructuredJSONScheme.Encode(u, &buf) + return buf.Bytes(), err +} + +// UnmarshalJSON ensures that the unstructured list object properly +// decodes JSON when passed to Go's standard JSON library. +func (u *UnstructuredList) UnmarshalJSON(b []byte) error { + _, _, err := UnstructuredJSONScheme.Decode(b, nil, u) + return err +} + +func (u *UnstructuredList) GetAPIVersion() string { + return getNestedString(u.Object, "apiVersion") +} + +func (u *UnstructuredList) SetAPIVersion(version string) { + u.setNestedField(version, "apiVersion") +} + +func (u *UnstructuredList) GetKind() string { + return getNestedString(u.Object, "kind") +} + +func (u *UnstructuredList) SetKind(kind string) { + u.setNestedField(kind, "kind") +} + +func (u *UnstructuredList) GetResourceVersion() string { + return getNestedString(u.Object, "metadata", "resourceVersion") +} + +func (u *UnstructuredList) SetResourceVersion(version string) { + u.setNestedField(version, "metadata", "resourceVersion") +} + +func (u *UnstructuredList) GetSelfLink() string { + return getNestedString(u.Object, "metadata", "selfLink") +} + +func (u *UnstructuredList) SetSelfLink(selfLink string) { + u.setNestedField(selfLink, "metadata", "selfLink") +} + +func (u *UnstructuredList) GetContinue() string { + return getNestedString(u.Object, "metadata", "continue") +} + +func (u *UnstructuredList) SetContinue(c string) { + u.setNestedField(c, "metadata", "continue") +} + +func (u *UnstructuredList) GetRemainingItemCount() *int64 { + return getNestedInt64Pointer(u.Object, "metadata", "remainingItemCount") +} + +func (u *UnstructuredList) SetRemainingItemCount(c *int64) { + if c == nil { + RemoveNestedField(u.Object, "metadata", "remainingItemCount") + } else { + u.setNestedField(*c, "metadata", "remainingItemCount") + } +} + +func (u *UnstructuredList) SetGroupVersionKind(gvk schema.GroupVersionKind) { + u.SetAPIVersion(gvk.GroupVersion().String()) + u.SetKind(gvk.Kind) +} + +func (u *UnstructuredList) GroupVersionKind() schema.GroupVersionKind { + gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) + if err != nil { + return schema.GroupVersionKind{} + } + gvk := gv.WithKind(u.GetKind()) + return gvk +} + +func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) { + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + SetNestedField(u.Object, value, fields...) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go new file mode 100644 index 0000000000..d3c42b519a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go @@ -0,0 +1,86 @@ +/* +Copyright 2017 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 unstructured + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnstructuredList(t *testing.T) { + list := &UnstructuredList{ + Object: map[string]interface{}{"kind": "List", "apiVersion": "v1"}, + Items: []Unstructured{ + {Object: map[string]interface{}{"kind": "Pod", "apiVersion": "v1", "metadata": map[string]interface{}{"name": "test"}}}, + }, + } + content := list.UnstructuredContent() + items := content["items"].([]interface{}) + require.Len(t, items, 1) + val, found, err := NestedFieldCopy(items[0].(map[string]interface{}), "metadata", "name") + require.True(t, found) + require.NoError(t, err) + assert.Equal(t, "test", val) +} + +func TestNilDeletionTimestamp(t *testing.T) { + var u Unstructured + del := u.GetDeletionTimestamp() + if del != nil { + t.Errorf("unexpected non-nil deletion timestamp: %v", del) + } + u.SetDeletionTimestamp(u.GetDeletionTimestamp()) + del = u.GetDeletionTimestamp() + if del != nil { + t.Errorf("unexpected non-nil deletion timestamp: %v", del) + } + _, ok := u.Object["metadata"] + assert.False(t, ok) + + now := metav1.Now() + u.SetDeletionTimestamp(&now) + assert.Equal(t, now.Unix(), u.GetDeletionTimestamp().Unix()) + u.SetDeletionTimestamp(nil) + metadata := u.Object["metadata"].(map[string]interface{}) + _, ok = metadata["deletionTimestamp"] + assert.False(t, ok) +} + +func TestEmptyCreationTimestampIsOmitted(t *testing.T) { + var u Unstructured + now := metav1.Now() + + // set an initial creationTimestamp and ensure the field exists + u.SetCreationTimestamp(now) + metadata := u.Object["metadata"].(map[string]interface{}) + _, exists := metadata["creationTimestamp"] + if !exists { + t.Fatalf("unexpected missing creationTimestamp") + } + + // set an empty timestamp and ensure the field no longer exists + u.SetCreationTimestamp(metav1.Time{}) + metadata = u.Object["metadata"].(map[string]interface{}) + creationTimestamp, exists := metadata["creationTimestamp"] + if exists { + t.Errorf("unexpected creation timestamp field: %q", creationTimestamp) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go new file mode 100644 index 0000000000..889083eccb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_test.go @@ -0,0 +1,476 @@ +/* +Copyright 2018 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 unstructured_test + +import ( + "bytes" + "math/big" + "math/rand" + "os" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/randfill" + + "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" + "k8s.io/apimachinery/pkg/api/equality" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + cborserializer "k8s.io/apimachinery/pkg/runtime/serializer/cbor" + jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json" +) + +func TestNilUnstructuredContent(t *testing.T) { + var u unstructured.Unstructured + uCopy := u.DeepCopy() + content := u.UnstructuredContent() + expContent := make(map[string]interface{}) + assert.EqualValues(t, expContent, content) + assert.Equal(t, uCopy, &u) +} + +// TestUnstructuredMetadataRoundTrip checks that metadata accessors +// correctly set the metadata for unstructured objects. +// First, it fuzzes an empty ObjectMeta and sets this value as the metadata for an unstructured object. +// Next, it uses metadata accessor methods to set these fuzzed values to another unstructured object. +// Finally, it checks that both the unstructured objects are equal. +func TestUnstructuredMetadataRoundTrip(t *testing.T) { + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + seed := rand.Int63() + fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs) + + N := 1000 + for i := 0; i < N; i++ { + u := &unstructured.Unstructured{Object: map[string]interface{}{}} + uCopy := u.DeepCopy() + metadata := &metav1.ObjectMeta{} + fuzzer.Fill(metadata) + + if err := setObjectMeta(u, metadata); err != nil { + t.Fatalf("unexpected error setting fuzzed ObjectMeta: %v", err) + } + setObjectMetaUsingAccessors(u, uCopy) + + if !equality.Semantic.DeepEqual(u, uCopy) { + t.Errorf("diff: %v", cmp.Diff(u, uCopy)) + } + } +} + +// TestUnstructuredMetadataOmitempty checks that ObjectMeta omitempty +// semantics are enforced for unstructured objects. +// The fuzzing test above should catch these cases but this is here just to be safe. +// Example: the metadata.clusterName field has the omitempty json tag +// so if it is set to it's zero value (""), it should be removed from the metadata map. +func TestUnstructuredMetadataOmitempty(t *testing.T) { + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + seed := rand.Int63() + fuzzer := fuzzer.FuzzerFor(metafuzzer.Funcs, rand.NewSource(seed), codecs) + + // fuzz to make sure we don't miss any function calls below + u := &unstructured.Unstructured{Object: map[string]interface{}{}} + metadata := &metav1.ObjectMeta{} + fuzzer.Fill(metadata) + if err := setObjectMeta(u, metadata); err != nil { + t.Fatalf("unexpected error setting fuzzed ObjectMeta: %v", err) + } + + // set zero values for all fields in metadata explicitly + // to check that omitempty fields having zero values are never set + u.SetName("") + u.SetGenerateName("") + u.SetNamespace("") + u.SetSelfLink("") + u.SetUID("") + u.SetResourceVersion("") + u.SetGeneration(0) + u.SetCreationTimestamp(metav1.Time{}) + u.SetDeletionTimestamp(nil) + u.SetDeletionGracePeriodSeconds(nil) + u.SetLabels(nil) + u.SetAnnotations(nil) + u.SetOwnerReferences(nil) + u.SetFinalizers(nil) + u.SetManagedFields(nil) + + gotMetadata, _, err := unstructured.NestedFieldNoCopy(u.UnstructuredContent(), "metadata") + if err != nil { + t.Error(err) + } + emptyMetadata := make(map[string]interface{}) + + if !reflect.DeepEqual(gotMetadata, emptyMetadata) { + t.Errorf("expected %v, got %v", emptyMetadata, gotMetadata) + } +} + +// TestRoundTripJSONCBORUnstructured performs fuzz testing for roundtrip for +// unstructured object between JSON and CBOR +func TestRoundTripJSONCBORUnstructured(t *testing.T) { + roundtripType[*unstructured.Unstructured](t) +} + +// TestRoundTripJSONCBORUnstructuredList performs fuzz testing for roundtrip for +// unstructuredList object between JSON and CBOR +func TestRoundTripJSONCBORUnstructuredList(t *testing.T) { + roundtripType[*unstructured.UnstructuredList](t) +} + +func setObjectMeta(u *unstructured.Unstructured, objectMeta *metav1.ObjectMeta) error { + if objectMeta == nil { + unstructured.RemoveNestedField(u.UnstructuredContent(), "metadata") + return nil + } + metadata, err := runtime.DefaultUnstructuredConverter.ToUnstructured(objectMeta) + if err != nil { + return err + } + u.UnstructuredContent()["metadata"] = metadata + return nil +} + +func setObjectMetaUsingAccessors(u, uCopy *unstructured.Unstructured) { + uCopy.SetName(u.GetName()) + uCopy.SetGenerateName(u.GetGenerateName()) + uCopy.SetNamespace(u.GetNamespace()) + uCopy.SetSelfLink(u.GetSelfLink()) + uCopy.SetUID(u.GetUID()) + uCopy.SetResourceVersion(u.GetResourceVersion()) + uCopy.SetGeneration(u.GetGeneration()) + uCopy.SetCreationTimestamp(u.GetCreationTimestamp()) + uCopy.SetDeletionTimestamp(u.GetDeletionTimestamp()) + uCopy.SetDeletionGracePeriodSeconds(u.GetDeletionGracePeriodSeconds()) + uCopy.SetLabels(u.GetLabels()) + uCopy.SetAnnotations(u.GetAnnotations()) + uCopy.SetOwnerReferences(u.GetOwnerReferences()) + uCopy.SetFinalizers(u.GetFinalizers()) + uCopy.SetManagedFields(u.GetManagedFields()) +} + +// roundtripType performs fuzz testing for roundtrip conversion for +// unstructured or unstructuredList object between two formats (A and B) in forward +// and backward directions +// Original and final unstructured/list are compared along with all intermediate ones +func roundtripType[U runtime.Unstructured](t *testing.T) { + scheme := runtime.NewScheme() + fuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, unstructuredFuzzerFuncs), rand.NewSource(getSeed(t)), serializer.NewCodecFactory(scheme)) + + jS := jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, scheme, scheme, jsonserializer.SerializerOptions{}) + cS := cborserializer.NewSerializer(scheme, scheme) + + for i := 0; i < 50; i++ { + original := reflect.New(reflect.TypeFor[U]().Elem()).Interface().(runtime.Unstructured) + fuzzer.Fill(original) + // unstructured -> JSON > unstructured > CBOR -> unstructured -> JSON -> unstructured + roundtrip(t, original, jS, cS) + // unstructured -> CBOR > unstructured > JSON -> unstructured -> CBOR -> unstructured + roundtrip(t, original, cS, jS) + } +} + +// roundtrip tests that an Unstructured object roundtrips faithfully along the +// sequence Unstructured -> A -> Unstructured -> B -> Unstructured -> A -> Unstructured, +// given serializers for two encodings A and B. The final object and both intermediate +// objects must all be equal to the original. +func roundtrip(t *testing.T, original runtime.Unstructured, a, b runtime.Serializer) { + var buf bytes.Buffer + + buf.Reset() + // (original) Unstructured -> A + if err := a.Encode(original, &buf); err != nil { + t.Fatalf("error encoding original unstructured to A: %v", err) + } + // A -> intermediate unstructured + uA := reflect.New(reflect.TypeOf(original).Elem()).Interface().(runtime.Object) + uA, _, err := a.Decode(buf.Bytes(), nil, uA) + if err != nil { + t.Fatalf("error decoding A to unstructured: %v", err) + } + + // Compare original unstructured vs intermediate unstructured + tmp, ok := uA.(runtime.Unstructured) + if !ok { + t.Fatalf("unexpected type %T for unstructured", tmp) + } + if !unstructuredEqual(t, original, uA.(runtime.Unstructured)) { + t.Fatalf("original unstructured differed from unstructured via A: %v", cmp.Diff(original, uA)) + } + + buf.Reset() + // intermediate unstructured -> B + if err := b.Encode(uA, &buf); err != nil { + t.Fatalf("error encoding unstructured to B: %v", err) + } + // B -> intermediate unstructured + uB := reflect.New(reflect.TypeOf(original).Elem()).Interface().(runtime.Object) + uB, _, err = b.Decode(buf.Bytes(), nil, uB) + if err != nil { + t.Fatalf("error decoding B to unstructured: %v", err) + } + + // compare original vs intermediate unstructured + tmp, ok = uB.(runtime.Unstructured) + if !ok { + t.Fatalf("unexpected type %T for unstructured", tmp) + } + if !unstructuredEqual(t, original, uB.(runtime.Unstructured)) { + t.Fatalf("unstructured via A differed from unstructured via B: %v", cmp.Diff(original, uB)) + } + + // intermediate unstructured -> A + buf.Reset() + if err := a.Encode(uB, &buf); err != nil { + t.Fatalf("error encoding unstructured to A: %v", err) + } + // A -> final unstructured + final := reflect.New(reflect.TypeOf(original).Elem()).Interface().(runtime.Object) + final, _, err = a.Decode(buf.Bytes(), nil, final) + if err != nil { + t.Fatalf("error decoding A to unstructured: %v", err) + } + + // Compare original unstructured vs final unstructured + tmp, ok = final.(runtime.Unstructured) + if !ok { + t.Fatalf("unexpected type %T for unstructured", tmp) + } + if !unstructuredEqual(t, original, final.(runtime.Unstructured)) { + t.Errorf("object changed during unstructured->A->unstructured->B->unstructured roundtrip, diff: %s", cmp.Diff(original, final)) + } +} + +func getSeed(t *testing.T) int64 { + seed := int64(time.Now().Nanosecond()) + if override := os.Getenv("TEST_RAND_SEED"); len(override) > 0 { + overrideSeed, err := strconv.ParseInt(override, 10, 64) + if err != nil { + t.Fatal(err) + } + seed = overrideSeed + t.Logf("using overridden seed: %d", seed) + } else { + t.Logf("seed (override with TEST_RAND_SEED if desired): %d", seed) + } + return seed +} + +const ( + maxUnstructuredDepth = 64 + maxUnstructuredFanOut = 5 +) + +func unstructuredFuzzerFuncs(codecs serializer.CodecFactory) []interface{} { + return []interface{}{ + func(u *unstructured.Unstructured, c randfill.Continue) { + obj := make(map[string]interface{}) + obj["apiVersion"] = generateValidAPIVersionString(c) + obj["kind"] = generateNonEmptyString(c) + for j := c.Intn(maxUnstructuredFanOut); j >= 0; j-- { + obj[c.String(0)] = generateRandomTypeValue(maxUnstructuredDepth, c) + } + u.Object = obj + }, + func(ul *unstructured.UnstructuredList, c randfill.Continue) { + obj := make(map[string]interface{}) + obj["apiVersion"] = generateValidAPIVersionString(c) + obj["kind"] = generateNonEmptyString(c) + for j := c.Intn(maxUnstructuredFanOut); j >= 0; j-- { + obj[c.String(0)] = generateRandomTypeValue(maxUnstructuredDepth, c) + } + for j := c.Intn(maxUnstructuredFanOut); j >= 0; j-- { + var item = unstructured.Unstructured{} + c.Fill(&item) + ul.Items = append(ul.Items, item) + } + ul.Object = obj + }, + } +} + +func generateNonEmptyString(c randfill.Continue) string { + temp := c.String(0) + for len(temp) == 0 { + temp = c.String(0) + } + return temp +} + +// generateNonEmptyNoSlashString generates a non-empty string without any slashes +func generateNonEmptyNoSlashString(c randfill.Continue) string { + temp := strings.ReplaceAll(generateNonEmptyString(c), "/", "") + for len(temp) == 0 { + temp = strings.ReplaceAll(generateNonEmptyString(c), "/", "") + } + return temp +} + +// generateValidAPIVersionString generates valid apiVersion string with formats: +// / or +func generateValidAPIVersionString(c randfill.Continue) string { + if c.Bool() { + return generateNonEmptyNoSlashString(c) + "/" + generateNonEmptyNoSlashString(c) + } else { + return generateNonEmptyNoSlashString(c) + } +} + +// generateRandomTypeValue generates fuzzed valid JSON data types: +// 1. numbers (float64, int64) +// 2. string (utf-8 encodings) +// 3. boolean +// 4. array ([]interface{}) +// 5. object (map[string]interface{}) +// 6. null +// Decoding into unstructured can only produce a nil interface{} value or the +// concrete types map[string]interface{}, []interface{}, int64, float64, string, and bool +// If a value of other types is put into an unstructured, it will roundtrip +// to one of the above list of supported types. For example, if Time type is used, +// it will be encoded into a RFC 3339 format string such as "2001-02-03T12:34:56Z" +// and when decoding into Unstructured, there is no information to indicate +// that this string was originally produced by encoding a metav1.Time. +// All external-versioned builtin types are exercised through RoundtripToUnstructured +// in apitesting package. Types like metav1.Time are implicitly being exercised +// because they appear as fields in those types. +func generateRandomTypeValue(depth int, c randfill.Continue) interface{} { + t := c.Rand.Intn(120) + // If the max depth for unstructured is reached, only add non-recursive types + // which is 20+ in range + if depth == 0 { + t = 20 + c.Rand.Intn(120-20) + } + + switch { + case t < 10: + item := make([]interface{}, c.Intn(maxUnstructuredFanOut)) + for k := range item { + item[k] = generateRandomTypeValue(depth-1, c) + } + return item + case t < 20: + item := map[string]interface{}{} + for j := c.Intn(maxUnstructuredFanOut); j >= 0; j-- { + item[c.String(0)] = generateRandomTypeValue(depth-1, c) + } + return item + case t < 40: + // Only valid UTF-8 encodings + var item string + c.Fill(&item) + return item + case t < 60: + var item int64 + c.Fill(&item) + return item + case t < 80: + var item bool + c.Fill(&item) + return item + case t < 100: + return c.Rand.NormFloat64() + case t < 120: + return nil + default: + panic("invalid case") + } +} + +func unstructuredEqual(t *testing.T, a, b runtime.Unstructured) bool { + return anyEqual(t, a.UnstructuredContent(), b.UnstructuredContent()) +} + +// numberEqual asserts equality of two numbers which one is int64 and one is float64 +// In JSON, a non-decimal float64 is converted to int64 automatically in case the +// float64 fits into int64 range. Otherwise, the non-decimal float64 remains a float. +// As a result, this func does an int64 to float64 conversion using math/big package +// to ensure the conversion is lossless before comparison. +func numberEqual(a int64, b float64) bool { + // Ensure roundtrip int64 to float64 conversion is lossless + f, accuracy := big.NewInt(a).Float64() + if accuracy == big.Exact { + // Distinction between int64 and float64 is not preserved during JSON roundtrip for all numbers. + return f == b + } + return false +} + +func anyEqual(t *testing.T, a, b interface{}) bool { + switch b.(type) { + case nil, bool, string, int64, float64, []interface{}, map[string]interface{}: + default: + t.Fatalf("unexpected value %v of type %T", b, b) + } + + switch ac := a.(type) { + case nil, bool, string: + return ac == b + case int64: + if bc, ok := b.(float64); ok { + return numberEqual(ac, bc) + } + return ac == b + case float64: + if bc, ok := b.(int64); ok { + return numberEqual(bc, ac) + } + return ac == b + case []interface{}: + bc, ok := b.([]interface{}) + if !ok { + return false + } + if len(ac) != len(bc) { + return false + } + for i, aa := range ac { + if !anyEqual(t, aa, bc[i]) { + return false + } + } + return true + case map[string]interface{}: + bc, ok := b.(map[string]interface{}) + if !ok { + return false + } + if len(ac) != len(bc) { + return false + } + for k, aa := range ac { + bb, ok := bc[k] + if !ok { + return false + } + if !anyEqual(t, aa, bb) { + return false + } + } + return true + default: + t.Fatalf("unexpected value %v of type %T", a, a) + } + return true +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme/scheme.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme/scheme.go new file mode 100644 index 0000000000..f8f5ec8560 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme/scheme.go @@ -0,0 +1,129 @@ +/* +Copyright 2018 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 unstructuredscheme + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/versioning" +) + +var scheme = runtime.NewScheme() + +// NewUnstructuredNegotiatedSerializer returns a simple, negotiated serializer +func NewUnstructuredNegotiatedSerializer() runtime.NegotiatedSerializer { + return unstructuredNegotiatedSerializer{ + scheme: scheme, + typer: NewUnstructuredObjectTyper(), + creator: NewUnstructuredCreator(), + } +} + +type unstructuredNegotiatedSerializer struct { + scheme *runtime.Scheme + typer runtime.ObjectTyper + creator runtime.ObjectCreater +} + +func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { + return []runtime.SerializerInfo{ + { + MediaType: "application/json", + MediaTypeType: "application", + MediaTypeSubType: "json", + EncodesAsText: true, + Serializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{}), + PrettySerializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{Pretty: true}), + StreamSerializer: &runtime.StreamSerializerInfo{ + EncodesAsText: true, + Serializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{}), + Framer: json.Framer, + }, + }, + { + MediaType: "application/yaml", + MediaTypeType: "application", + MediaTypeSubType: "yaml", + EncodesAsText: true, + Serializer: json.NewSerializerWithOptions(json.DefaultMetaFactory, s.creator, s.typer, json.SerializerOptions{Yaml: true}), + }, + } +} + +func (s unstructuredNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + return versioning.NewDefaultingCodecForScheme(s.scheme, encoder, nil, gv, nil) +} + +func (s unstructuredNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { + return versioning.NewDefaultingCodecForScheme(s.scheme, nil, decoder, nil, gv) +} + +type unstructuredObjectTyper struct { +} + +// NewUnstructuredObjectTyper returns an object typer that can deal with unstructured things +func NewUnstructuredObjectTyper() runtime.ObjectTyper { + return unstructuredObjectTyper{} +} + +func (t unstructuredObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { + // Delegate for things other than Unstructured. + if _, ok := obj.(runtime.Unstructured); !ok { + return nil, false, fmt.Errorf("cannot type %T", obj) + } + gvk := obj.GetObjectKind().GroupVersionKind() + if len(gvk.Kind) == 0 { + return nil, false, runtime.NewMissingKindErr("object has no kind field ") + } + if len(gvk.Version) == 0 { + return nil, false, runtime.NewMissingVersionErr("object has no apiVersion field") + } + + return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil +} + +func (t unstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { + return true +} + +type unstructuredCreator struct{} + +// NewUnstructuredCreator returns a simple object creator that always returns an unstructured +func NewUnstructuredCreator() runtime.ObjectCreater { + return unstructuredCreator{} +} + +func (c unstructuredCreator) New(kind schema.GroupVersionKind) (runtime.Object, error) { + ret := &unstructured.Unstructured{} + ret.SetGroupVersionKind(kind) + return ret, nil +} + +type unstructuredDefaulter struct { +} + +// NewUnstructuredDefaulter returns defaulter suitable for unstructured types that doesn't default anything +func NewUnstructuredDefaulter() runtime.ObjectDefaulter { + return unstructuredDefaulter{} +} + +func (d unstructuredDefaulter) Default(in runtime.Object) { +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/zz_generated.deepcopy.go new file mode 100644 index 0000000000..fe8250dd6f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/zz_generated.deepcopy.go @@ -0,0 +1,56 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package unstructured + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Unstructured) DeepCopyInto(out *Unstructured) { + clone := in.DeepCopy() + *out = *clone + return +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Unstructured) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UnstructuredList) DeepCopyInto(out *UnstructuredList) { + clone := in.DeepCopy() + *out = *clone + return +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UnstructuredList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/doc.go new file mode 100644 index 0000000000..3b9cb30f2a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-input=k8s.io/apimachinery/pkg/apis/meta/v1 + +// Package validation holds generated validations for meta/v1 types. +package validation diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go new file mode 100644 index 0000000000..b9ec344270 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go @@ -0,0 +1,433 @@ +/* +Copyright 2015 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 validation + +import ( + "context" + "fmt" + "regexp" + "unicode" + + "k8s.io/apimachinery/pkg/api/operation" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + + "k8s.io/utils/ptr" +) + +// LabelSelectorValidationOptions is a struct that can be passed to ValidateLabelSelector to record the validate options +type LabelSelectorValidationOptions struct { + // Allow invalid label value in selector + AllowInvalidLabelValueInSelector bool + + // Allows an operator that is not interpretable to pass validation. This is useful for cases where a broader check + // can be performed, as in a *SubjectAccessReview + AllowUnknownOperatorInRequirement bool +} + +// LabelSelectorHasInvalidLabelValue returns true if the given selector contains an invalid label value in a match expression. +// This is useful for determining whether AllowInvalidLabelValueInSelector should be set to true when validating an update +// based on existing persisted invalid values. +func LabelSelectorHasInvalidLabelValue(ps *metav1.LabelSelector) bool { + if ps == nil { + return false + } + for _, e := range ps.MatchExpressions { + for _, v := range e.Values { + if len(validation.IsValidLabelValue(v)) > 0 { + return true + } + } + } + return false +} + +// ValidateLabelSelector validate the LabelSelector according to the opts and returns any validation errors. +// opts.AllowInvalidLabelValueInSelector is only expected to be set to true when required for backwards compatibility with existing invalid data. +func ValidateLabelSelector(ps *metav1.LabelSelector, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if ps == nil { + return allErrs + } + allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...) + for i, expr := range ps.MatchExpressions { + allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, opts, fldPath.Child("matchExpressions").Index(i))...) + } + return allErrs +} + +// ValidateLabelSelectorRequirement validate the requirement according to the opts and returns any validation errors. +// opts.AllowInvalidLabelValueInSelector is only expected to be set to true when required for backwards compatibility with existing invalid data. +func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + switch sr.Operator { + case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn: + if len(sr.Values) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) + } + case metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist: + if len(sr.Values) > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) + } + default: + if !opts.AllowUnknownOperatorInRequirement { + allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator")) + } + } + allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...) + if !opts.AllowInvalidLabelValueInSelector { + for valueIndex, value := range sr.Values { + for _, msg := range validation.IsValidLabelValue(value) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("values").Index(valueIndex), value, msg)) + } + } + } + return allErrs +} + +// ValidateLabelName validates that the label name is correctly defined. +func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsQualifiedName(labelName) { + allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg).WithOrigin("format=k8s-label-key")) + } + return allErrs +} + +// ValidateLabels validates that a set of labels are correctly defined. +func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for k, v := range labels { + allErrs = append(allErrs, ValidateLabelName(k, fldPath)...) + for _, msg := range validation.IsValidLabelValue(v) { + allErrs = append(allErrs, field.Invalid(fldPath, v, msg).WithOrigin("format=k8s-label-value")) + } + } + return allErrs +} + +// FieldSelectorValidationOptions is a struct that can be passed to ValidateFieldSelectorRequirement to record the validate options +type FieldSelectorValidationOptions struct { + // Allows an operator that is not interpretable to pass validation. This is useful for cases where a broader check + // can be performed, as in a *SubjectAccessReview + AllowUnknownOperatorInRequirement bool +} + +// ValidateLabelSelectorRequirement validates the requirement according to the opts and returns any validation errors. +func ValidateFieldSelectorRequirement(requirement metav1.FieldSelectorRequirement, opts FieldSelectorValidationOptions, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(requirement.Key) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("key"), "must be specified")) + } + + switch requirement.Operator { + case metav1.FieldSelectorOpIn, metav1.FieldSelectorOpNotIn: + if len(requirement.Values) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) + } + case metav1.FieldSelectorOpExists, metav1.FieldSelectorOpDoesNotExist: + if len(requirement.Values) > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) + } + default: + if !opts.AllowUnknownOperatorInRequirement { + allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), requirement.Operator, "not a valid selector operator")) + } + } + + return allErrs +} + +func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList { + allErrs := field.ErrorList{} + //lint:file-ignore SA1019 Keep validation for deprecated OrphanDependents option until it's being removed + if options.OrphanDependents != nil && options.PropagationPolicy != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("propagationPolicy"), options.PropagationPolicy, "orphanDependents and deletionPropagation cannot be both set")) + } + if options.PropagationPolicy != nil && + *options.PropagationPolicy != metav1.DeletePropagationForeground && + *options.PropagationPolicy != metav1.DeletePropagationBackground && + *options.PropagationPolicy != metav1.DeletePropagationOrphan { + allErrs = append(allErrs, field.NotSupported(field.NewPath("propagationPolicy"), options.PropagationPolicy, []string{string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground), string(metav1.DeletePropagationOrphan), "nil"})) + } + allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) + allErrs = append(allErrs, ValidateIgnoreStoreReadError(field.NewPath("ignoreStoreReadErrorWithClusterBreakingPotential"), options)...) + return allErrs +} + +func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) + allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) + allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) + return allErrs +} + +func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) + allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) + allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) + return allErrs +} + +func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList { + allErrs := field.ErrorList{} + switch patchType { + case types.ApplyYAMLPatchType, types.ApplyCBORPatchType: + if options.FieldManager == "" { + // This field is defaulted to "kubectl" by kubectl, but HAS TO be explicitly set by controllers. + allErrs = append(allErrs, field.Required(field.NewPath("fieldManager"), "is required for apply patch")) + } + default: + if options.Force != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch")) + } + } + allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) + allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) + allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) + return allErrs +} + +var FieldManagerMaxLength = 128 + +// ValidateFieldManager valides that the fieldManager is the proper length and +// only has printable characters. +func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // the field can not be set as a `*string`, so a empty string ("") is + // considered as not set and is defaulted by the rest of the process + // (unless apply is used, in which case it is required). + if len(fieldManager) > FieldManagerMaxLength { + allErrs = append(allErrs, field.TooLong(fldPath, "" /*unused*/, FieldManagerMaxLength)) + } + // Verify that all characters are printable. + for i, r := range fieldManager { + if !unicode.IsPrint(r) { + allErrs = append(allErrs, field.Invalid(fldPath, fieldManager, fmt.Sprintf("invalid character %#U (at position %d)", r, i))) + } + } + + return allErrs +} + +var allowedDryRunValues = sets.NewString(metav1.DryRunAll) + +// ValidateDryRun validates that a dryRun query param only contains allowed values. +func ValidateDryRun(fldPath *field.Path, dryRun []string) field.ErrorList { + allErrs := field.ErrorList{} + if !allowedDryRunValues.HasAll(dryRun...) { + allErrs = append(allErrs, field.NotSupported(fldPath, dryRun, allowedDryRunValues.List())) + } + return allErrs +} + +var allowedFieldValidationValues = sets.NewString("", metav1.FieldValidationIgnore, metav1.FieldValidationWarn, metav1.FieldValidationStrict) + +// ValidateFieldValidation validates that a fieldValidation query param only contains allowed values. +func ValidateFieldValidation(fldPath *field.Path, fieldValidation string) field.ErrorList { + allErrs := field.ErrorList{} + if !allowedFieldValidationValues.Has(fieldValidation) { + allErrs = append(allErrs, field.NotSupported(fldPath, fieldValidation, allowedFieldValidationValues.List())) + } + return allErrs + +} + +const UninitializedStatusUpdateErrorMsg string = `must not update status when the object is uninitialized` + +// ValidateTableOptions returns any invalid flags on TableOptions. +func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList { + var allErrs field.ErrorList + switch opts.IncludeObject { + case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "": + default: + allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty")) + } + return allErrs +} + +const MaxSubresourceNameLength = 256 + +// ManagedFieldsValidationOption specifies options for validating managed fields. +type ManagedFieldsValidationOption int + +const ( + // CoveredByDeclarative indicates whether errors should be marked as covered by declarative validation. + CoveredByDeclarative ManagedFieldsValidationOption = iota + 1 +) + +// ValidateManagedFields validates a list of managed fields. +func ValidateManagedFields(fieldsList []metav1.ManagedFieldsEntry, fldPath *field.Path, opts ...ManagedFieldsValidationOption) field.ErrorList { + coveredByDeclarative := false + for _, opt := range opts { + if opt == CoveredByDeclarative { + coveredByDeclarative = true + } + } + var allErrs field.ErrorList + for i, fields := range fieldsList { + fldPath := fldPath.Index(i) + switch fields.Operation { + case "": + err := field.Required(fldPath.Child("operation"), "must not be empty") + if coveredByDeclarative { + err = err.MarkCoveredByDeclarative() + } + allErrs = append(allErrs, err) + case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate: + default: + err := field.NotSupported(fldPath.Child("operation"), fields.Operation, []metav1.ManagedFieldsOperationType{metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate}) + if coveredByDeclarative { + err = err.MarkCoveredByDeclarative() + } + allErrs = append(allErrs, err) + } + if len(fields.FieldsType) > 0 && fields.FieldsType != "FieldsV1" { + allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldsType"), fields.FieldsType, "must be `FieldsV1`")) + } + allErrs = append(allErrs, ValidateFieldManager(fields.Manager, fldPath.Child("manager"))...) + + if len(fields.Subresource) > MaxSubresourceNameLength { + allErrs = append(allErrs, field.TooLong(fldPath.Child("subresource"), "" /*unused*/, MaxSubresourceNameLength)) + } + } + return allErrs +} + +func ValidateConditions(conditions []metav1.Condition, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + conditionTypeToFirstIndex := map[string]int{} + for i, condition := range conditions { + if _, ok := conditionTypeToFirstIndex[condition.Type]; ok { + allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), condition.Type).MarkCoveredByDeclarative()) + } else { + conditionTypeToFirstIndex[condition.Type] = i + } + + allErrs = append(allErrs, ValidateCondition(condition, fldPath.Index(i))...) + } + + return allErrs +} + +// validConditionStatuses is used internally to check validity and provide a good message +var validConditionStatuses = sets.NewString(string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown)) + +const ( + maxReasonLen = 1 * 1024 + maxMessageLen = 32 * 1024 +) + +func ValidateCondition(condition metav1.Condition, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + // type is set and is a valid format + if len(condition.Type) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("type"), "is required").MarkCoveredByDeclarative()) + } else { + allErrs = append(allErrs, ValidateLabelName(condition.Type, fldPath.Child("type"))...) + } + + // status is set and is an accepted value + if len(condition.Status) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("status"), "").MarkCoveredByDeclarative()) + } else if !validConditionStatuses.Has(string(condition.Status)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, validConditionStatuses.List()).MarkCoveredByDeclarative()) + } + + if condition.ObservedGeneration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("observedGeneration"), condition.ObservedGeneration, "must be greater than or equal to zero").WithOrigin("minimum").MarkCoveredByDeclarative()) + } + + if condition.LastTransitionTime.IsZero() { + allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "").MarkCoveredByDeclarative()) + } + + if len(condition.Reason) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "").MarkCoveredByDeclarative()) + } else { + for _, currErr := range IsValidConditionReason(condition.Reason) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("reason"), condition.Reason, currErr)) + } + if len(condition.Reason) > maxReasonLen { + allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), "" /*unused*/, maxReasonLen).WithOrigin("maxBytes").MarkCoveredByDeclarative()) + } + } + + if len(condition.Message) > maxMessageLen { + allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), "" /*unused*/, maxMessageLen)) + } + + return allErrs +} + +const conditionReasonFmt string = "[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?" +const conditionReasonErrMsg string = "a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_'" + +var conditionReasonRegexp = regexp.MustCompile("^" + conditionReasonFmt + "$") + +// IsValidConditionReason tests for a string that conforms to rules for condition +// reasons. This checks the format, but not the length. +func IsValidConditionReason(value string) []string { + if !conditionReasonRegexp.MatchString(value) { + return []string{validation.RegexError(conditionReasonErrMsg, conditionReasonFmt, "my_name", "MY_NAME", "MyName", "ReasonA,ReasonB", "ReasonA:ReasonB")} + } + return nil +} + +// ValidateIgnoreStoreReadError validates that delete options are valid when +// ignoreStoreReadErrorWithClusterBreakingPotential is enabled +func ValidateIgnoreStoreReadError(fldPath *field.Path, options *metav1.DeleteOptions) field.ErrorList { + allErrs := field.ErrorList{} + if enabled := ptr.Deref[bool](options.IgnoreStoreReadErrorWithClusterBreakingPotential, false); !enabled { + return allErrs + } + + if options.PropagationPolicy != nil { + allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .propagationPolicy")) + } + //nolint:staticcheck // Keep validation for deprecated OrphanDependents option until it's being removed + if options.OrphanDependents != nil { + allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .orphanDependents")) + } + if options.GracePeriodSeconds != nil { + allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .gracePeriodSeconds")) + } + if options.Preconditions != nil { + allErrs = append(allErrs, field.Invalid(fldPath, true, "cannot be set together with .preconditions")) + } + + return allErrs +} + +// ValidateCustom_Condition_LastTransitionTime is wired into the generated +// declarative validation by +k8s:customValidation on Condition.LastTransitionTime. +// It enforces that the field is set, mirroring the handwritten check in +// ValidateCondition. +func ValidateCustom_Condition_LastTransitionTime(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *metav1.Time) field.ErrorList { + if value.IsZero() { + return field.ErrorList{field.Required(fldPath, "")} + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation_test.go new file mode 100644 index 0000000000..d7e421037c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation_test.go @@ -0,0 +1,646 @@ +/* +Copyright 2016 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 validation + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" + + "k8s.io/utils/ptr" +) + +func TestValidateLabels(t *testing.T) { + successCases := []map[string]string{ + {"simple": "bar"}, + {"now-with-dashes": "bar"}, + {"1-starts-with-num": "bar"}, + {"1234": "bar"}, + {"simple/simple": "bar"}, + {"now-with-dashes/simple": "bar"}, + {"now-with-dashes/now-with-dashes": "bar"}, + {"now.with.dots/simple": "bar"}, + {"now-with.dashes-and.dots/simple": "bar"}, + {"1-num.2-num/3-num": "bar"}, + {"1234/5678": "bar"}, + {"1.2.3.4/5678": "bar"}, + {"UpperCaseAreOK123": "bar"}, + {"goodvalue": "123_-.BaR"}, + } + for i := range successCases { + errs := ValidateLabels(successCases[i], field.NewPath("field")) + if len(errs) != 0 { + t.Errorf("case[%d] expected success, got %#v", i, errs) + } + } + + namePartErrMsg := "name part must consist of" + nameErrMsg := "a valid label key must consist of" + labelErrMsg := "a valid label must be an empty string or consist of" + maxLengthErrMsg := "must be no more than" + + labelNameErrorCases := []struct { + labels map[string]string + expect string + }{ + {map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg}, + {map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg}, + {map[string]string{"only/one/slash": "bar"}, nameErrMsg}, + {map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg}, + } + for i := range labelNameErrorCases { + errs := ValidateLabels(labelNameErrorCases[i].labels, field.NewPath("field")) + if len(errs) != 1 { + t.Errorf("case[%d]: expected failure", i) + } else { + if !strings.Contains(errs[0].Detail, labelNameErrorCases[i].expect) { + t.Errorf("case[%d]: error details do not include %q: %q", i, labelNameErrorCases[i].expect, errs[0].Detail) + } + } + } + + labelValueErrorCases := []struct { + labels map[string]string + expect string + }{ + {map[string]string{"toolongvalue": strings.Repeat("a", 64)}, maxLengthErrMsg}, + {map[string]string{"backslashesinvalue": "some\\bad\\value"}, labelErrMsg}, + {map[string]string{"nocommasallowed": "bad,value"}, labelErrMsg}, + {map[string]string{"strangecharsinvalue": "?#$notsogood"}, labelErrMsg}, + } + for i := range labelValueErrorCases { + errs := ValidateLabels(labelValueErrorCases[i].labels, field.NewPath("field")) + if len(errs) != 1 { + t.Errorf("case[%d]: expected failure", i) + } else { + if !strings.Contains(errs[0].Detail, labelValueErrorCases[i].expect) { + t.Errorf("case[%d]: error details do not include %q: %q", i, labelValueErrorCases[i].expect, errs[0].Detail) + } + } + } +} + +func TestValidDryRun(t *testing.T) { + tests := [][]string{ + {}, + {"All"}, + {"All", "All"}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { + if errs := ValidateDryRun(field.NewPath("dryRun"), test); len(errs) != 0 { + t.Errorf("%v should be a valid dry-run value: %v", test, errs) + } + }) + } +} + +func TestInvalidDryRun(t *testing.T) { + tests := [][]string{ + {"False"}, + {"All", "False"}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { + if len(ValidateDryRun(field.NewPath("dryRun"), test)) == 0 { + t.Errorf("%v shouldn't be a valid dry-run value", test) + } + }) + } +} + +func TestValidateDeleteOptionsWithIgnoreStoreReadError(t *testing.T) { + fieldPath := field.NewPath("ignoreStoreReadErrorWithClusterBreakingPotential") + tests := []struct { + name string + opts metav1.DeleteOptions + expectedErrors field.ErrorList + }{ + { + name: "option is nil", + opts: metav1.DeleteOptions{ + IgnoreStoreReadErrorWithClusterBreakingPotential: nil, + DryRun: []string{"All"}, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "option is false, PropagationPolicy is set", + opts: metav1.DeleteOptions{ + IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](false), + DryRun: []string{"All"}, + PropagationPolicy: ptr.To[metav1.DeletionPropagation](metav1.DeletePropagationBackground), + GracePeriodSeconds: ptr.To[int64](0), + Preconditions: &metav1.Preconditions{}, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "option is false, OrphanDependents is set", + opts: metav1.DeleteOptions{ + IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](false), + DryRun: []string{"All"}, + //nolint:staticcheck // until it's being removed + OrphanDependents: ptr.To[bool](true), + GracePeriodSeconds: ptr.To[int64](0), + Preconditions: &metav1.Preconditions{}, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "option is true, PropagationPolicy is set", + opts: metav1.DeleteOptions{ + IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](true), + DryRun: []string{"All"}, + PropagationPolicy: ptr.To[metav1.DeletionPropagation](metav1.DeletePropagationBackground), + GracePeriodSeconds: ptr.To[int64](0), + Preconditions: &metav1.Preconditions{}, + }, + expectedErrors: field.ErrorList{ + field.Invalid(fieldPath, true, "cannot be set together with .propagationPolicy"), + field.Invalid(fieldPath, true, "cannot be set together with .gracePeriodSeconds"), + field.Invalid(fieldPath, true, "cannot be set together with .preconditions"), + }, + }, + { + name: "option is true, OrphanDependents is set", + opts: metav1.DeleteOptions{ + IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](true), + DryRun: []string{"All"}, + //nolint:staticcheck // until it's being removed + OrphanDependents: ptr.To[bool](true), + GracePeriodSeconds: ptr.To[int64](0), + Preconditions: &metav1.Preconditions{}, + }, + expectedErrors: field.ErrorList{ + field.Invalid(fieldPath, true, "cannot be set together with .orphanDependents"), + field.Invalid(fieldPath, true, "cannot be set together with .gracePeriodSeconds"), + field.Invalid(fieldPath, true, "cannot be set together with .preconditions"), + }, + }, + { + name: "option is true, no other option is set", + opts: metav1.DeleteOptions{ + IgnoreStoreReadErrorWithClusterBreakingPotential: ptr.To[bool](false), + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "option is true, dry-run is set (should be allowed)", + opts: metav1.DeleteOptions{ + IgnoreStoreReadErrorWithClusterBreakingPotential: new(true), + DryRun: []string{"All"}, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "option is true, dry-run is set to an invalid value", + opts: metav1.DeleteOptions{ + IgnoreStoreReadErrorWithClusterBreakingPotential: new(true), + DryRun: []string{"Invalid"}, + }, + expectedErrors: field.ErrorList{ + field.NotSupported(field.NewPath("dryRun"), []string{"Invalid"}, []string{"All"}), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + errGot := ValidateDeleteOptions(&test.opts) + if !cmp.Equal(test.expectedErrors, errGot) { + t.Errorf("expected error(s) to match, diff: %s", cmp.Diff(test.expectedErrors, errGot)) + } + }) + } +} + +func TestValidPatchOptions(t *testing.T) { + tests := []struct { + opts metav1.PatchOptions + patchType types.PatchType + }{{ + opts: metav1.PatchOptions{ + Force: ptr.To(true), + FieldManager: "kubectl", + }, + patchType: types.ApplyYAMLPatchType, + }, { + opts: metav1.PatchOptions{ + FieldManager: "kubectl", + }, + patchType: types.ApplyYAMLPatchType, + }, { + opts: metav1.PatchOptions{ + Force: ptr.To(true), + FieldManager: "kubectl", + }, + patchType: types.ApplyCBORPatchType, + }, { + opts: metav1.PatchOptions{ + FieldManager: "kubectl", + }, + patchType: types.ApplyCBORPatchType, + }, { + opts: metav1.PatchOptions{}, + patchType: types.MergePatchType, + }, { + opts: metav1.PatchOptions{ + FieldManager: "patcher", + }, + patchType: types.MergePatchType, + }} + + for _, test := range tests { + t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) { + errs := ValidatePatchOptions(&test.opts, test.patchType) + if len(errs) != 0 { + t.Fatalf("Expected no failures, got: %v", errs) + } + }) + } +} + +func TestInvalidPatchOptions(t *testing.T) { + tests := []struct { + opts metav1.PatchOptions + patchType types.PatchType + }{ + // missing manager + { + opts: metav1.PatchOptions{}, + patchType: types.ApplyYAMLPatchType, + }, + // missing manager + { + opts: metav1.PatchOptions{}, + patchType: types.ApplyCBORPatchType, + }, + // force on non-apply + { + opts: metav1.PatchOptions{ + Force: ptr.To(true), + }, + patchType: types.MergePatchType, + }, + // manager and force on non-apply + { + opts: metav1.PatchOptions{ + FieldManager: "kubectl", + Force: ptr.To(false), + }, + patchType: types.MergePatchType, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) { + errs := ValidatePatchOptions(&test.opts, test.patchType) + if len(errs) == 0 { + t.Fatal("Expected failures, got none.") + } + }) + } +} + +func TestValidateFieldManagerValid(t *testing.T) { + tests := []string{ + "filedManager", + "你好", // Hello + "🍔", + } + + for _, test := range tests { + t.Run(test, func(t *testing.T) { + errs := ValidateFieldManager(test, field.NewPath("fieldManager")) + if len(errs) != 0 { + t.Errorf("Validation failed: %v", errs) + } + }) + } +} + +func TestValidateFieldManagerInvalid(t *testing.T) { + tests := []string{ + "field\nmanager", // Contains invalid character \n + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // Has 129 chars + } + + for _, test := range tests { + t.Run(test, func(t *testing.T) { + errs := ValidateFieldManager(test, field.NewPath("fieldManager")) + if len(errs) == 0 { + t.Errorf("Validation should have failed") + } + }) + } +} + +func TestValidateManagedFieldsInvalid(t *testing.T) { + tests := []metav1.ManagedFieldsEntry{{ + Operation: metav1.ManagedFieldsOperationUpdate, + FieldsType: "RandomVersion", + APIVersion: "v1", + }, { + Operation: "RandomOperation", + FieldsType: "FieldsV1", + APIVersion: "v1", + }, { + // Operation is missing + FieldsType: "FieldsV1", + APIVersion: "v1", + }, { + Operation: metav1.ManagedFieldsOperationUpdate, + FieldsType: "FieldsV1", + // Invalid fieldManager + Manager: "field\nmanager", + APIVersion: "v1", + }, { + Operation: metav1.ManagedFieldsOperationApply, + FieldsType: "FieldsV1", + APIVersion: "v1", + Subresource: "TooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLongTooLong", + }} + + for _, test := range tests { + t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) { + errs := ValidateManagedFields([]metav1.ManagedFieldsEntry{test}, field.NewPath("managedFields")) + if len(errs) == 0 { + t.Errorf("Validation should have failed") + } + errs = ValidateManagedFields([]metav1.ManagedFieldsEntry{test}, field.NewPath("managedFields"), CoveredByDeclarative) + if len(errs) == 0 { + t.Errorf("Validation with CoveredByDeclarative should have failed") + } + }) + } +} + +func TestValidateMangedFieldsValid(t *testing.T) { + tests := []metav1.ManagedFieldsEntry{{ + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "v1", + // FieldsType is missing + }, { + Operation: metav1.ManagedFieldsOperationUpdate, + FieldsType: "FieldsV1", + APIVersion: "v1", + }, { + Operation: metav1.ManagedFieldsOperationApply, + FieldsType: "FieldsV1", + APIVersion: "v1", + Subresource: "scale", + }, { + Operation: metav1.ManagedFieldsOperationApply, + FieldsType: "FieldsV1", + APIVersion: "v1", + Manager: "🍔", + }} + + for _, test := range tests { + t.Run(fmt.Sprintf("%#v", test), func(t *testing.T) { + err := ValidateManagedFields([]metav1.ManagedFieldsEntry{test}, field.NewPath("managedFields")) + if err != nil { + t.Errorf("Validation failed: %v", err) + } + err = ValidateManagedFields([]metav1.ManagedFieldsEntry{test}, field.NewPath("managedFields"), CoveredByDeclarative) + if err != nil { + t.Errorf("Validation with CoveredByDeclarative failed: %v", err) + } + }) + } +} + +func TestValidateConditions(t *testing.T) { + tests := []struct { + name string + conditions []metav1.Condition + validateErrs func(t *testing.T, errs field.ErrorList) + }{{ + name: "bunch-of-invalid-fields", + conditions: []metav1.Condition{{ + Type: ":invalid", + Status: "unknown", + ObservedGeneration: -1, + LastTransitionTime: metav1.Time{}, + Reason: "invalid;val", + Message: "", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[0].type: Invalid value: ":invalid": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + needle = `status.conditions[0].status: Unsupported value: "unknown": supported values: "False", "True", "Unknown"` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + needle = `status.conditions[0].observedGeneration: Invalid value: -1: must be greater than or equal to zero` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + needle = `status.conditions[0].lastTransitionTime: Required value` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + needle = `status.conditions[0].reason: Invalid value: "invalid;val": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', or 'ReasonA,ReasonB', or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + }, + }, { + name: "duplicates", + conditions: []metav1.Condition{{ + Type: "First", + }, { + Type: "Second", + }, { + Type: "First", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[2]: Duplicate value: "First"` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + }, + }, { + name: "colon-allowed-in-reason", + conditions: []metav1.Condition{{ + Type: "First", + Reason: "valid:val", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[0].reason` + if hasPrefixError(errs, needle) { + t.Errorf("has %q in\n%v", needle, errorsAsString(errs)) + } + }, + }, { + name: "comma-allowed-in-reason", + conditions: []metav1.Condition{{ + Type: "First", + Reason: "valid,val", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[0].reason` + if hasPrefixError(errs, needle) { + t.Errorf("has %q in\n%v", needle, errorsAsString(errs)) + } + }, + }, { + name: "reason-does-not-end-in-delimiter", + conditions: []metav1.Condition{{ + Type: "First", + Reason: "valid,val:", + }}, + validateErrs: func(t *testing.T, errs field.ErrorList) { + needle := `status.conditions[0].reason: Invalid value: "valid,val:": a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', or 'ReasonA,ReasonB', or 'ReasonA:ReasonB', regex used for validation is '[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?')` + if !hasError(errs, needle) { + t.Errorf("missing %q in\n%v", needle, errorsAsString(errs)) + } + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + errs := ValidateConditions(test.conditions, field.NewPath("status").Child("conditions")) + test.validateErrs(t, errs) + }) + } +} + +func TestLabelSelectorMatchExpression(t *testing.T) { + testCases := []struct { + name string + labelSelector *metav1.LabelSelector + wantErrorNumber int + validateErrs func(t *testing.T, errs field.ErrorList) + }{{ + name: "Valid LabelSelector", + labelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value"}, + }}, + }, + wantErrorNumber: 0, + validateErrs: nil, + }, { + name: "MatchExpression's key name isn't valid", + labelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "-key", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"value"}, + }}, + }, + wantErrorNumber: 1, + validateErrs: func(t *testing.T, errs field.ErrorList) { + errMessage := "name part must consist of alphanumeric characters" + if !partStringInErrorMessage(errs, errMessage) { + t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) + } + }, + }, { + name: "MatchExpression's operator isn't valid", + labelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key", + Operator: "abc", + Values: []string{"value"}, + }}, + }, + wantErrorNumber: 1, + validateErrs: func(t *testing.T, errs field.ErrorList) { + errMessage := "not a valid selector operator" + if !partStringInErrorMessage(errs, errMessage) { + t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) + } + }, + }, { + name: "MatchExpression's value name isn't valid", + labelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "key", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"-value"}, + }}, + }, + wantErrorNumber: 1, + validateErrs: func(t *testing.T, errs field.ErrorList) { + errMessage := "a valid label must be an empty string or consist of" + if !partStringInErrorMessage(errs, errMessage) { + t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs)) + } + }, + }} + for index, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, field.NewPath("labelSelector")) + if len(allErrs) != testCase.wantErrorNumber { + t.Errorf("case[%d]: expected failure", index) + } + if len(allErrs) >= 1 && testCase.validateErrs != nil { + testCase.validateErrs(t, allErrs) + } + }) + } +} + +func hasError(errs field.ErrorList, needle string) bool { + for _, curr := range errs { + if curr.Error() == needle { + return true + } + } + return false +} + +func hasPrefixError(errs field.ErrorList, prefix string) bool { + for _, curr := range errs { + if strings.HasPrefix(curr.Error(), prefix) { + return true + } + } + return false +} + +func partStringInErrorMessage(errs field.ErrorList, prefix string) bool { + for _, curr := range errs { + if strings.Contains(curr.Error(), prefix) { + return true + } + } + return false +} + +func errorsAsString(errs field.ErrorList) string { + messages := []string{} + for _, curr := range errs { + messages = append(messages, curr.Error()) + } + return strings.Join(messages, "\n") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/zz_generated.validations.go new file mode 100644 index 0000000000..f79373a26d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/validation/zz_generated.validations.go @@ -0,0 +1,638 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package validation + +import ( + context "context" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +// Validate_Condition validates an instance of Condition according +// to declarative validation rules in the API schema. +func Validate_Condition( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *v1.Condition) (errs field.ErrorList) { + + { // field v1.Condition.Type + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.Condition) *string { + return &oldObj.Type + }) + errs = append(errs, fn(fldPath.Child("type"), &obj.Type, oldVal, oldObj != nil)...) + } + + { // field v1.Condition.Status + fn := func( + fldPath *field.Path, + obj, oldObj *v1.ConditionStatus, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_ConditionStatus(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.Condition) *v1.ConditionStatus { + return &oldObj.Status + }) + errs = append(errs, fn(fldPath.Child("status"), &obj.Status, oldVal, oldObj != nil)...) + } + + { // field v1.Condition.ObservedGeneration + fn := func( + fldPath *field.Path, + obj, oldObj *int64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.Condition) *int64 { + return &oldObj.ObservedGeneration + }) + errs = append(errs, fn(fldPath.Child("observedGeneration"), &obj.ObservedGeneration, oldVal, oldObj != nil)...) + } + + { // field v1.Condition.LastTransitionTime + fn := func( + fldPath *field.Path, + obj, oldObj *v1.Time, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // custom validation + if e := ValidateCustom_Condition_LastTransitionTime(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.Condition) *v1.Time { + return &oldObj.LastTransitionTime + }) + errs = append(errs, fn(fldPath.Child("lastTransitionTime"), &obj.LastTransitionTime, oldVal, oldObj != nil)...) + } + + { // field v1.Condition.Reason + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 1024).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.Condition) *string { + return &oldObj.Reason + }) + errs = append(errs, fn(fldPath.Child("reason"), &obj.Reason, oldVal, oldObj != nil)...) + } + + // field v1.Condition.Message has no validation + return errs +} + +var symbolsForConditionStatus = sets.New(v1.ConditionFalse, v1.ConditionTrue, v1.ConditionUnknown) + +// Validate_ConditionStatus validates an instance of ConditionStatus according +// to declarative validation rules in the API schema. +func Validate_ConditionStatus( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *v1.ConditionStatus) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForConditionStatus, nil).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ManagedFieldsEntry validates an instance of ManagedFieldsEntry according +// to declarative validation rules in the API schema. +func Validate_ManagedFieldsEntry( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *v1.ManagedFieldsEntry) (errs field.ErrorList) { + + // field v1.ManagedFieldsEntry.Manager has no validation + + { // field v1.ManagedFieldsEntry.Operation + fn := func( + fldPath *field.Path, + obj, oldObj *v1.ManagedFieldsOperationType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_ManagedFieldsOperationType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.ManagedFieldsEntry) *v1.ManagedFieldsOperationType { + return &oldObj.Operation + }) + errs = append(errs, fn(fldPath.Child("operation"), &obj.Operation, oldVal, oldObj != nil)...) + } + + // field v1.ManagedFieldsEntry.APIVersion has no validation + // field v1.ManagedFieldsEntry.Time has no validation + // field v1.ManagedFieldsEntry.FieldsType has no validation + // field v1.ManagedFieldsEntry.FieldsV1 has no validation + // field v1.ManagedFieldsEntry.Subresource has no validation + return errs +} + +var symbolsForManagedFieldsOperationType = sets.New(v1.ManagedFieldsOperationApply, v1.ManagedFieldsOperationUpdate) + +// Validate_ManagedFieldsOperationType validates an instance of ManagedFieldsOperationType according +// to declarative validation rules in the API schema. +func Validate_ManagedFieldsOperationType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *v1.ManagedFieldsOperationType) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForManagedFieldsOperationType, nil).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ObjectMeta validates an instance of ObjectMeta according +// to declarative validation rules in the API schema. +func Validate_ObjectMeta( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *v1.ObjectMeta) (errs field.ErrorList) { + + // field v1.ObjectMeta.Name has no validation + // field v1.ObjectMeta.GenerateName has no validation + // field v1.ObjectMeta.Namespace has no validation + // field v1.ObjectMeta.SelfLink has no validation + + { // field v1.ObjectMeta.UID + fn := func( + fldPath *field.Path, + obj, oldObj *types.UID, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.ObjectMeta) *types.UID { + return &oldObj.UID + }) + errs = append(errs, fn(fldPath.Child("uid"), &obj.UID, oldVal, oldObj != nil)...) + } + + // field v1.ObjectMeta.ResourceVersion has no validation + + { // field v1.ObjectMeta.Generation + fn := func( + fldPath *field.Path, + obj, oldObj *int64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.ObjectMeta) *int64 { + return &oldObj.Generation + }) + errs = append(errs, fn(fldPath.Child("generation"), &obj.Generation, oldVal, oldObj != nil)...) + } + + { // field v1.ObjectMeta.CreationTimestamp + fn := func( + fldPath *field.Path, + obj, oldObj *v1.Time, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.ObjectMeta) *v1.Time { + return &oldObj.CreationTimestamp + }) + errs = append(errs, fn(fldPath.Child("creationTimestamp"), &obj.CreationTimestamp, oldVal, oldObj != nil)...) + } + + { // field v1.ObjectMeta.DeletionTimestamp + fn := func( + fldPath *field.Path, + obj, oldObj *v1.Time, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.ObjectMeta) *v1.Time { + return oldObj.DeletionTimestamp + }) + errs = append(errs, fn(fldPath.Child("deletionTimestamp"), obj.DeletionTimestamp, oldVal, oldObj != nil)...) + } + + { // field v1.ObjectMeta.DeletionGracePeriodSeconds + fn := func( + fldPath *field.Path, + obj, oldObj *int64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.ObjectMeta) *int64 { + return oldObj.DeletionGracePeriodSeconds + }) + errs = append(errs, fn(fldPath.Child("deletionGracePeriodSeconds"), obj.DeletionGracePeriodSeconds, oldVal, oldObj != nil)...) + } + + // field v1.ObjectMeta.Labels has no validation + // field v1.ObjectMeta.Annotations has no validation + + { // field v1.ObjectMeta.OwnerReferences + fn := func( + fldPath *field.Path, + obj, oldObj []v1.OwnerReference, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OwnerReference); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.ObjectMeta) []v1.OwnerReference { + return oldObj.OwnerReferences + }) + errs = append(errs, fn(fldPath.Child("ownerReferences"), obj.OwnerReferences, oldVal, oldObj != nil)...) + } + + // field v1.ObjectMeta.Finalizers has no validation + + { // field v1.ObjectMeta.ManagedFields + fn := func( + fldPath *field.Path, + obj, oldObj []v1.ManagedFieldsEntry, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ManagedFieldsEntry); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.ObjectMeta) []v1.ManagedFieldsEntry { + return oldObj.ManagedFields + }) + errs = append(errs, fn(fldPath.Child("managedFields"), obj.ManagedFields, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_OwnerReference validates an instance of OwnerReference according +// to declarative validation rules in the API schema. +func Validate_OwnerReference( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *v1.OwnerReference) (errs field.ErrorList) { + + { // field v1.OwnerReference.APIVersion + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.OwnerReference) *string { + return &oldObj.APIVersion + }) + errs = append(errs, fn(fldPath.Child("apiVersion"), &obj.APIVersion, oldVal, oldObj != nil)...) + } + + { // field v1.OwnerReference.Kind + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.OwnerReference) *string { + return &oldObj.Kind + }) + errs = append(errs, fn(fldPath.Child("kind"), &obj.Kind, oldVal, oldObj != nil)...) + } + + { // field v1.OwnerReference.Name + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.OwnerReference) *string { + return &oldObj.Name + }) + errs = append(errs, fn(fldPath.Child("name"), &obj.Name, oldVal, oldObj != nil)...) + } + + { // field v1.OwnerReference.UID + fn := func( + fldPath *field.Path, + obj, oldObj *types.UID, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *v1.OwnerReference) *types.UID { + return &oldObj.UID + }) + errs = append(errs, fn(fldPath.Child("uid"), &obj.UID, oldVal, oldObj != nil)...) + } + + // field v1.OwnerReference.Controller has no validation + // field v1.OwnerReference.BlockOwnerDeletion has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/watch.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/watch.go new file mode 100644 index 0000000000..58f0773803 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/watch.go @@ -0,0 +1,89 @@ +/* +Copyright 2015 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 v1 + +import ( + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" +) + +// Event represents a single event to a watched resource. +// +// +protobuf=true +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type WatchEvent struct { + Type string `json:"type" protobuf:"bytes,1,opt,name=type"` + + // Object is: + // * If Type is Added or Modified: the new state of the object. + // * If Type is Deleted: the state of the object immediately before deletion. + // * If Type is Error: *Status is recommended; other types may make sense + // depending on context. + Object runtime.RawExtension `json:"object" protobuf:"bytes,2,opt,name=object"` +} + +func Convert_watch_Event_To_v1_WatchEvent(in *watch.Event, out *WatchEvent, s conversion.Scope) error { + out.Type = string(in.Type) + switch t := in.Object.(type) { + case *runtime.Unknown: + // TODO: handle other fields on Unknown and detect type + out.Object.Raw = t.Raw + case nil: + default: + out.Object.Object = in.Object + } + return nil +} + +func Convert_v1_InternalEvent_To_v1_WatchEvent(in *InternalEvent, out *WatchEvent, s conversion.Scope) error { + return Convert_watch_Event_To_v1_WatchEvent((*watch.Event)(in), out, s) +} + +func Convert_v1_WatchEvent_To_watch_Event(in *WatchEvent, out *watch.Event, s conversion.Scope) error { + out.Type = watch.EventType(in.Type) + if in.Object.Object != nil { + out.Object = in.Object.Object + } else if in.Object.Raw != nil { + // TODO: handle other fields on Unknown and detect type + out.Object = &runtime.Unknown{ + Raw: in.Object.Raw, + ContentType: runtime.ContentTypeJSON, + } + } + return nil +} + +func Convert_v1_WatchEvent_To_v1_InternalEvent(in *WatchEvent, out *InternalEvent, s conversion.Scope) error { + return Convert_v1_WatchEvent_To_watch_Event(in, (*watch.Event)(out), s) +} + +// InternalEvent makes watch.Event versioned +// +protobuf=false +type InternalEvent watch.Event + +func (e *InternalEvent) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (e *WatchEvent) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (e *InternalEvent) DeepCopyObject() runtime.Object { + if c := e.DeepCopy(); c != nil { + return c + } else { + return nil + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.conversion.go new file mode 100644 index 0000000000..735cbcb08f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.conversion.go @@ -0,0 +1,548 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1 + +import ( + url "net/url" + unsafe "unsafe" + + resource "k8s.io/apimachinery/pkg/api/resource" + conversion "k8s.io/apimachinery/pkg/conversion" + fields "k8s.io/apimachinery/pkg/fields" + labels "k8s.io/apimachinery/pkg/labels" + runtime "k8s.io/apimachinery/pkg/runtime" + intstr "k8s.io/apimachinery/pkg/util/intstr" + watch "k8s.io/apimachinery/pkg/watch" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*CreateOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1_CreateOptions(a.(*url.Values), b.(*CreateOptions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*DeleteOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1_DeleteOptions(a.(*url.Values), b.(*DeleteOptions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*GetOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1_GetOptions(a.(*url.Values), b.(*GetOptions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*ListOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1_ListOptions(a.(*url.Values), b.(*ListOptions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*PatchOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1_PatchOptions(a.(*url.Values), b.(*PatchOptions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*TableOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1_TableOptions(a.(*url.Values), b.(*TableOptions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*UpdateOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1_UpdateOptions(a.(*url.Values), b.(*UpdateOptions), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*map[string]string)(nil), (*LabelSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Map_string_To_string_To_v1_LabelSelector(a.(*map[string]string), b.(*LabelSelector), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**bool)(nil), (*bool)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_bool_To_bool(a.(**bool), b.(*bool), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**float64)(nil), (*float64)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_float64_To_float64(a.(**float64), b.(*float64), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**int32)(nil), (*int32)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_int32_To_int32(a.(**int32), b.(*int32), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**int64)(nil), (*int)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_int64_To_int(a.(**int64), b.(*int), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**int64)(nil), (*int64)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_int64_To_int64(a.(**int64), b.(*int64), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**intstr.IntOrString)(nil), (*intstr.IntOrString)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_intstr_IntOrString_To_intstr_IntOrString(a.(**intstr.IntOrString), b.(*intstr.IntOrString), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**string)(nil), (*string)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_string_To_string(a.(**string), b.(*string), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**Duration)(nil), (*Duration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_v1_Duration_To_v1_Duration(a.(**Duration), b.(*Duration), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (**DeletionPropagation)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_Pointer_v1_DeletionPropagation(a.(*[]string), b.(**DeletionPropagation), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (**Time)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_Pointer_v1_Time(a.(*[]string), b.(**Time), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (*[]int32)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_Slice_int32(a.(*[]string), b.(*[]int32), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (*IncludeObjectPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_v1_IncludeObjectPolicy(a.(*[]string), b.(*IncludeObjectPolicy), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (*ResourceVersionMatch)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_v1_ResourceVersionMatch(a.(*[]string), b.(*ResourceVersionMatch), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (*Time)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_v1_Time(a.(*[]string), b.(*Time), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*bool)(nil), (**bool)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_bool_To_Pointer_bool(a.(*bool), b.(**bool), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*fields.Selector)(nil), (*string)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_fields_Selector_To_string(a.(*fields.Selector), b.(*string), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*float64)(nil), (**float64)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_float64_To_Pointer_float64(a.(*float64), b.(**float64), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*int32)(nil), (**int32)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_int32_To_Pointer_int32(a.(*int32), b.(**int32), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*int64)(nil), (**int64)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_int64_To_Pointer_int64(a.(*int64), b.(**int64), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*int)(nil), (**int64)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_int_To_Pointer_int64(a.(*int), b.(**int64), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*intstr.IntOrString)(nil), (**intstr.IntOrString)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_intstr_IntOrString_To_Pointer_intstr_IntOrString(a.(*intstr.IntOrString), b.(**intstr.IntOrString), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*intstr.IntOrString)(nil), (*intstr.IntOrString)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_intstr_IntOrString_To_intstr_IntOrString(a.(*intstr.IntOrString), b.(*intstr.IntOrString), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*labels.Selector)(nil), (*string)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_labels_Selector_To_string(a.(*labels.Selector), b.(*string), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*resource.Quantity)(nil), (*resource.Quantity)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_Quantity_To_resource_Quantity(a.(*resource.Quantity), b.(*resource.Quantity), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*string)(nil), (**string)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_string_To_Pointer_string(a.(*string), b.(**string), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*string)(nil), (*fields.Selector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_string_To_fields_Selector(a.(*string), b.(*fields.Selector), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*string)(nil), (*labels.Selector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_string_To_labels_Selector(a.(*string), b.(*labels.Selector), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*url.Values)(nil), (*DeleteOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1_DeleteOptions(a.(*url.Values), b.(*DeleteOptions), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*DeleteOptions)(nil), (*DeleteOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_DeleteOptions_To_v1_DeleteOptions(a.(*DeleteOptions), b.(*DeleteOptions), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*Duration)(nil), (**Duration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Duration_To_Pointer_v1_Duration(a.(*Duration), b.(**Duration), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*InternalEvent)(nil), (*WatchEvent)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_InternalEvent_To_v1_WatchEvent(a.(*InternalEvent), b.(*WatchEvent), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*LabelSelector)(nil), (*map[string]string)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_LabelSelector_To_Map_string_To_string(a.(*LabelSelector), b.(*map[string]string), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ListMeta)(nil), (*ListMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ListMeta_To_v1_ListMeta(a.(*ListMeta), b.(*ListMeta), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*MicroTime)(nil), (*MicroTime)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_MicroTime_To_v1_MicroTime(a.(*MicroTime), b.(*MicroTime), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*Time)(nil), (*Time)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Time_To_v1_Time(a.(*Time), b.(*Time), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*TypeMeta)(nil), (*TypeMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_TypeMeta_To_v1_TypeMeta(a.(*TypeMeta), b.(*TypeMeta), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*WatchEvent)(nil), (*InternalEvent)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_WatchEvent_To_v1_InternalEvent(a.(*WatchEvent), b.(*InternalEvent), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*WatchEvent)(nil), (*watch.Event)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_WatchEvent_To_watch_Event(a.(*WatchEvent), b.(*watch.Event), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*watch.Event)(nil), (*WatchEvent)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_watch_Event_To_v1_WatchEvent(a.(*watch.Event), b.(*WatchEvent), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_url_Values_To_v1_CreateOptions(in *url.Values, out *CreateOptions, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { + out.DryRun = *(*[]string)(unsafe.Pointer(&values)) + } else { + out.DryRun = nil + } + if values, ok := map[string][]string(*in)["fieldManager"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldManager, s); err != nil { + return err + } + } else { + out.FieldManager = "" + } + if values, ok := map[string][]string(*in)["fieldValidation"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldValidation, s); err != nil { + return err + } + } else { + out.FieldValidation = "" + } + return nil +} + +// Convert_url_Values_To_v1_CreateOptions is an autogenerated conversion function. +func Convert_url_Values_To_v1_CreateOptions(in *url.Values, out *CreateOptions, s conversion.Scope) error { + return autoConvert_url_Values_To_v1_CreateOptions(in, out, s) +} + +func autoConvert_url_Values_To_v1_DeleteOptions(in *url.Values, out *DeleteOptions, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["gracePeriodSeconds"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_Pointer_int64(&values, &out.GracePeriodSeconds, s); err != nil { + return err + } + } else { + out.GracePeriodSeconds = nil + } + // INFO: in.Preconditions opted out of conversion generation + if values, ok := map[string][]string(*in)["orphanDependents"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_Pointer_bool(&values, &out.OrphanDependents, s); err != nil { + return err + } + } else { + out.OrphanDependents = nil + } + if values, ok := map[string][]string(*in)["propagationPolicy"]; ok && len(values) > 0 { + if err := Convert_Slice_string_To_Pointer_v1_DeletionPropagation(&values, &out.PropagationPolicy, s); err != nil { + return err + } + } else { + out.PropagationPolicy = nil + } + if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { + out.DryRun = *(*[]string)(unsafe.Pointer(&values)) + } else { + out.DryRun = nil + } + if values, ok := map[string][]string(*in)["ignoreStoreReadErrorWithClusterBreakingPotential"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_Pointer_bool(&values, &out.IgnoreStoreReadErrorWithClusterBreakingPotential, s); err != nil { + return err + } + } else { + out.IgnoreStoreReadErrorWithClusterBreakingPotential = nil + } + return nil +} + +func autoConvert_url_Values_To_v1_GetOptions(in *url.Values, out *GetOptions, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["resourceVersion"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.ResourceVersion, s); err != nil { + return err + } + } else { + out.ResourceVersion = "" + } + return nil +} + +// Convert_url_Values_To_v1_GetOptions is an autogenerated conversion function. +func Convert_url_Values_To_v1_GetOptions(in *url.Values, out *GetOptions, s conversion.Scope) error { + return autoConvert_url_Values_To_v1_GetOptions(in, out, s) +} + +func autoConvert_url_Values_To_v1_ListOptions(in *url.Values, out *ListOptions, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["labelSelector"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.LabelSelector, s); err != nil { + return err + } + } else { + out.LabelSelector = "" + } + if values, ok := map[string][]string(*in)["fieldSelector"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldSelector, s); err != nil { + return err + } + } else { + out.FieldSelector = "" + } + if values, ok := map[string][]string(*in)["watch"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_bool(&values, &out.Watch, s); err != nil { + return err + } + } else { + out.Watch = false + } + if values, ok := map[string][]string(*in)["allowWatchBookmarks"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_bool(&values, &out.AllowWatchBookmarks, s); err != nil { + return err + } + } else { + out.AllowWatchBookmarks = false + } + if values, ok := map[string][]string(*in)["resourceVersion"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.ResourceVersion, s); err != nil { + return err + } + } else { + out.ResourceVersion = "" + } + if values, ok := map[string][]string(*in)["resourceVersionMatch"]; ok && len(values) > 0 { + if err := Convert_Slice_string_To_v1_ResourceVersionMatch(&values, &out.ResourceVersionMatch, s); err != nil { + return err + } + } else { + out.ResourceVersionMatch = "" + } + if values, ok := map[string][]string(*in)["timeoutSeconds"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_Pointer_int64(&values, &out.TimeoutSeconds, s); err != nil { + return err + } + } else { + out.TimeoutSeconds = nil + } + if values, ok := map[string][]string(*in)["limit"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_int64(&values, &out.Limit, s); err != nil { + return err + } + } else { + out.Limit = 0 + } + if values, ok := map[string][]string(*in)["continue"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.Continue, s); err != nil { + return err + } + } else { + out.Continue = "" + } + if values, ok := map[string][]string(*in)["sendInitialEvents"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_Pointer_bool(&values, &out.SendInitialEvents, s); err != nil { + return err + } + } else { + out.SendInitialEvents = nil + } + if values, ok := map[string][]string(*in)["shardSelector"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.ShardSelector, s); err != nil { + return err + } + } else { + out.ShardSelector = "" + } + return nil +} + +// Convert_url_Values_To_v1_ListOptions is an autogenerated conversion function. +func Convert_url_Values_To_v1_ListOptions(in *url.Values, out *ListOptions, s conversion.Scope) error { + return autoConvert_url_Values_To_v1_ListOptions(in, out, s) +} + +func autoConvert_url_Values_To_v1_PatchOptions(in *url.Values, out *PatchOptions, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { + out.DryRun = *(*[]string)(unsafe.Pointer(&values)) + } else { + out.DryRun = nil + } + if values, ok := map[string][]string(*in)["force"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_Pointer_bool(&values, &out.Force, s); err != nil { + return err + } + } else { + out.Force = nil + } + if values, ok := map[string][]string(*in)["fieldManager"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldManager, s); err != nil { + return err + } + } else { + out.FieldManager = "" + } + if values, ok := map[string][]string(*in)["fieldValidation"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldValidation, s); err != nil { + return err + } + } else { + out.FieldValidation = "" + } + return nil +} + +// Convert_url_Values_To_v1_PatchOptions is an autogenerated conversion function. +func Convert_url_Values_To_v1_PatchOptions(in *url.Values, out *PatchOptions, s conversion.Scope) error { + return autoConvert_url_Values_To_v1_PatchOptions(in, out, s) +} + +func autoConvert_url_Values_To_v1_TableOptions(in *url.Values, out *TableOptions, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["-"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_bool(&values, &out.NoHeaders, s); err != nil { + return err + } + } else { + out.NoHeaders = false + } + if values, ok := map[string][]string(*in)["includeObject"]; ok && len(values) > 0 { + if err := Convert_Slice_string_To_v1_IncludeObjectPolicy(&values, &out.IncludeObject, s); err != nil { + return err + } + } else { + out.IncludeObject = "" + } + return nil +} + +// Convert_url_Values_To_v1_TableOptions is an autogenerated conversion function. +func Convert_url_Values_To_v1_TableOptions(in *url.Values, out *TableOptions, s conversion.Scope) error { + return autoConvert_url_Values_To_v1_TableOptions(in, out, s) +} + +func autoConvert_url_Values_To_v1_UpdateOptions(in *url.Values, out *UpdateOptions, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { + out.DryRun = *(*[]string)(unsafe.Pointer(&values)) + } else { + out.DryRun = nil + } + if values, ok := map[string][]string(*in)["fieldManager"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldManager, s); err != nil { + return err + } + } else { + out.FieldManager = "" + } + if values, ok := map[string][]string(*in)["fieldValidation"]; ok && len(values) > 0 { + if err := runtime.Convert_Slice_string_To_string(&values, &out.FieldValidation, s); err != nil { + return err + } + } else { + out.FieldValidation = "" + } + return nil +} + +// Convert_url_Values_To_v1_UpdateOptions is an autogenerated conversion function. +func Convert_url_Values_To_v1_UpdateOptions(in *url.Values, out *UpdateOptions, s conversion.Scope) error { + return autoConvert_url_Values_To_v1_UpdateOptions(in, out, s) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..eb7eb80d9b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.deepcopy.go @@ -0,0 +1,1218 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + types "k8s.io/apimachinery/pkg/types" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIGroup) DeepCopyInto(out *APIGroup) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Versions != nil { + in, out := &in.Versions, &out.Versions + *out = make([]GroupVersionForDiscovery, len(*in)) + copy(*out, *in) + } + out.PreferredVersion = in.PreferredVersion + if in.ServerAddressByClientCIDRs != nil { + in, out := &in.ServerAddressByClientCIDRs, &out.ServerAddressByClientCIDRs + *out = make([]ServerAddressByClientCIDR, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGroup. +func (in *APIGroup) DeepCopy() *APIGroup { + if in == nil { + return nil + } + out := new(APIGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIGroupList) DeepCopyInto(out *APIGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]APIGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGroupList. +func (in *APIGroupList) DeepCopy() *APIGroupList { + if in == nil { + return nil + } + out := new(APIGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIResource) DeepCopyInto(out *APIResource) { + *out = *in + if in.Verbs != nil { + in, out := &in.Verbs, &out.Verbs + *out = make(Verbs, len(*in)) + copy(*out, *in) + } + if in.ShortNames != nil { + in, out := &in.ShortNames, &out.ShortNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Categories != nil { + in, out := &in.Categories, &out.Categories + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResource. +func (in *APIResource) DeepCopy() *APIResource { + if in == nil { + return nil + } + out := new(APIResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIResourceList) DeepCopyInto(out *APIResourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.APIResources != nil { + in, out := &in.APIResources, &out.APIResources + *out = make([]APIResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceList. +func (in *APIResourceList) DeepCopy() *APIResourceList { + if in == nil { + return nil + } + out := new(APIResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIResourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIVersions) DeepCopyInto(out *APIVersions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Versions != nil { + in, out := &in.Versions, &out.Versions + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ServerAddressByClientCIDRs != nil { + in, out := &in.ServerAddressByClientCIDRs, &out.ServerAddressByClientCIDRs + *out = make([]ServerAddressByClientCIDR, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersions. +func (in *APIVersions) DeepCopy() *APIVersions { + if in == nil { + return nil + } + out := new(APIVersions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIVersions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplyOptions) DeepCopyInto(out *ApplyOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplyOptions. +func (in *ApplyOptions) DeepCopy() *ApplyOptions { + if in == nil { + return nil + } + out := new(ApplyOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CreateOptions) DeepCopyInto(out *CreateOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CreateOptions. +func (in *CreateOptions) DeepCopy() *CreateOptions { + if in == nil { + return nil + } + out := new(CreateOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CreateOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeleteOptions) DeepCopyInto(out *DeleteOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.GracePeriodSeconds != nil { + in, out := &in.GracePeriodSeconds, &out.GracePeriodSeconds + *out = new(int64) + **out = **in + } + if in.Preconditions != nil { + in, out := &in.Preconditions, &out.Preconditions + *out = new(Preconditions) + (*in).DeepCopyInto(*out) + } + if in.OrphanDependents != nil { + in, out := &in.OrphanDependents, &out.OrphanDependents + *out = new(bool) + **out = **in + } + if in.PropagationPolicy != nil { + in, out := &in.PropagationPolicy, &out.PropagationPolicy + *out = new(DeletionPropagation) + **out = **in + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IgnoreStoreReadErrorWithClusterBreakingPotential != nil { + in, out := &in.IgnoreStoreReadErrorWithClusterBreakingPotential, &out.IgnoreStoreReadErrorWithClusterBreakingPotential + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeleteOptions. +func (in *DeleteOptions) DeepCopy() *DeleteOptions { + if in == nil { + return nil + } + out := new(DeleteOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DeleteOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Duration) DeepCopyInto(out *Duration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Duration. +func (in *Duration) DeepCopy() *Duration { + if in == nil { + return nil + } + out := new(Duration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FieldSelectorRequirement) DeepCopyInto(out *FieldSelectorRequirement) { + *out = *in + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FieldSelectorRequirement. +func (in *FieldSelectorRequirement) DeepCopy() *FieldSelectorRequirement { + if in == nil { + return nil + } + out := new(FieldSelectorRequirement) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GetOptions) DeepCopyInto(out *GetOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GetOptions. +func (in *GetOptions) DeepCopy() *GetOptions { + if in == nil { + return nil + } + out := new(GetOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GetOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GroupKind) DeepCopyInto(out *GroupKind) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupKind. +func (in *GroupKind) DeepCopy() *GroupKind { + if in == nil { + return nil + } + out := new(GroupKind) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GroupResource) DeepCopyInto(out *GroupResource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupResource. +func (in *GroupResource) DeepCopy() *GroupResource { + if in == nil { + return nil + } + out := new(GroupResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GroupVersion) DeepCopyInto(out *GroupVersion) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupVersion. +func (in *GroupVersion) DeepCopy() *GroupVersion { + if in == nil { + return nil + } + out := new(GroupVersion) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GroupVersionForDiscovery) DeepCopyInto(out *GroupVersionForDiscovery) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupVersionForDiscovery. +func (in *GroupVersionForDiscovery) DeepCopy() *GroupVersionForDiscovery { + if in == nil { + return nil + } + out := new(GroupVersionForDiscovery) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GroupVersionKind) DeepCopyInto(out *GroupVersionKind) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupVersionKind. +func (in *GroupVersionKind) DeepCopy() *GroupVersionKind { + if in == nil { + return nil + } + out := new(GroupVersionKind) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GroupVersionResource) DeepCopyInto(out *GroupVersionResource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupVersionResource. +func (in *GroupVersionResource) DeepCopy() *GroupVersionResource { + if in == nil { + return nil + } + out := new(GroupVersionResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InternalEvent) DeepCopyInto(out *InternalEvent) { + *out = *in + if in.Object != nil { + out.Object = in.Object.DeepCopyObject() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalEvent. +func (in *InternalEvent) DeepCopy() *InternalEvent { + if in == nil { + return nil + } + out := new(InternalEvent) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LabelSelector) DeepCopyInto(out *LabelSelector) { + *out = *in + if in.MatchLabels != nil { + in, out := &in.MatchLabels, &out.MatchLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.MatchExpressions != nil { + in, out := &in.MatchExpressions, &out.MatchExpressions + *out = make([]LabelSelectorRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelSelector. +func (in *LabelSelector) DeepCopy() *LabelSelector { + if in == nil { + return nil + } + out := new(LabelSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LabelSelectorRequirement) DeepCopyInto(out *LabelSelectorRequirement) { + *out = *in + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelSelectorRequirement. +func (in *LabelSelectorRequirement) DeepCopy() *LabelSelectorRequirement { + if in == nil { + return nil + } + out := new(LabelSelectorRequirement) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *List) DeepCopyInto(out *List) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.RawExtension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List. +func (in *List) DeepCopy() *List { + if in == nil { + return nil + } + out := new(List) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *List) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ListMeta) DeepCopyInto(out *ListMeta) { + *out = *in + if in.RemainingItemCount != nil { + in, out := &in.RemainingItemCount, &out.RemainingItemCount + *out = new(int64) + **out = **in + } + if in.ShardInfo != nil { + in, out := &in.ShardInfo, &out.ShardInfo + *out = new(ShardInfo) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListMeta. +func (in *ListMeta) DeepCopy() *ListMeta { + if in == nil { + return nil + } + out := new(ListMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ListOptions) DeepCopyInto(out *ListOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int64) + **out = **in + } + if in.SendInitialEvents != nil { + in, out := &in.SendInitialEvents, &out.SendInitialEvents + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListOptions. +func (in *ListOptions) DeepCopy() *ListOptions { + if in == nil { + return nil + } + out := new(ListOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ListOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagedFieldsEntry) DeepCopyInto(out *ManagedFieldsEntry) { + *out = *in + if in.Time != nil { + in, out := &in.Time, &out.Time + *out = (*in).DeepCopy() + } + if in.FieldsV1 != nil { + in, out := &in.FieldsV1, &out.FieldsV1 + *out = (*in).DeepCopy() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedFieldsEntry. +func (in *ManagedFieldsEntry) DeepCopy() *ManagedFieldsEntry { + if in == nil { + return nil + } + out := new(ManagedFieldsEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroTime. +func (in *MicroTime) DeepCopy() *MicroTime { + if in == nil { + return nil + } + out := new(MicroTime) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) { + *out = *in + in.CreationTimestamp.DeepCopyInto(&out.CreationTimestamp) + if in.DeletionTimestamp != nil { + in, out := &in.DeletionTimestamp, &out.DeletionTimestamp + *out = (*in).DeepCopy() + } + if in.DeletionGracePeriodSeconds != nil { + in, out := &in.DeletionGracePeriodSeconds, &out.DeletionGracePeriodSeconds + *out = new(int64) + **out = **in + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.OwnerReferences != nil { + in, out := &in.OwnerReferences, &out.OwnerReferences + *out = make([]OwnerReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Finalizers != nil { + in, out := &in.Finalizers, &out.Finalizers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ManagedFields != nil { + in, out := &in.ManagedFields, &out.ManagedFields + *out = make([]ManagedFieldsEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMeta. +func (in *ObjectMeta) DeepCopy() *ObjectMeta { + if in == nil { + return nil + } + out := new(ObjectMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OwnerReference) DeepCopyInto(out *OwnerReference) { + *out = *in + if in.Controller != nil { + in, out := &in.Controller, &out.Controller + *out = new(bool) + **out = **in + } + if in.BlockOwnerDeletion != nil { + in, out := &in.BlockOwnerDeletion, &out.BlockOwnerDeletion + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerReference. +func (in *OwnerReference) DeepCopy() *OwnerReference { + if in == nil { + return nil + } + out := new(OwnerReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PartialObjectMetadata) DeepCopyInto(out *PartialObjectMetadata) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartialObjectMetadata. +func (in *PartialObjectMetadata) DeepCopy() *PartialObjectMetadata { + if in == nil { + return nil + } + out := new(PartialObjectMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PartialObjectMetadata) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PartialObjectMetadataList) DeepCopyInto(out *PartialObjectMetadataList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PartialObjectMetadata, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartialObjectMetadataList. +func (in *PartialObjectMetadataList) DeepCopy() *PartialObjectMetadataList { + if in == nil { + return nil + } + out := new(PartialObjectMetadataList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PartialObjectMetadataList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Patch) DeepCopyInto(out *Patch) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Patch. +func (in *Patch) DeepCopy() *Patch { + if in == nil { + return nil + } + out := new(Patch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchOptions) DeepCopyInto(out *PatchOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Force != nil { + in, out := &in.Force, &out.Force + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchOptions. +func (in *PatchOptions) DeepCopy() *PatchOptions { + if in == nil { + return nil + } + out := new(PatchOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PatchOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Preconditions) DeepCopyInto(out *Preconditions) { + *out = *in + if in.UID != nil { + in, out := &in.UID, &out.UID + *out = new(types.UID) + **out = **in + } + if in.ResourceVersion != nil { + in, out := &in.ResourceVersion, &out.ResourceVersion + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preconditions. +func (in *Preconditions) DeepCopy() *Preconditions { + if in == nil { + return nil + } + out := new(Preconditions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RootPaths) DeepCopyInto(out *RootPaths) { + *out = *in + if in.Paths != nil { + in, out := &in.Paths, &out.Paths + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RootPaths. +func (in *RootPaths) DeepCopy() *RootPaths { + if in == nil { + return nil + } + out := new(RootPaths) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServerAddressByClientCIDR) DeepCopyInto(out *ServerAddressByClientCIDR) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAddressByClientCIDR. +func (in *ServerAddressByClientCIDR) DeepCopy() *ServerAddressByClientCIDR { + if in == nil { + return nil + } + out := new(ServerAddressByClientCIDR) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShardInfo) DeepCopyInto(out *ShardInfo) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShardInfo. +func (in *ShardInfo) DeepCopy() *ShardInfo { + if in == nil { + return nil + } + out := new(ShardInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Status) DeepCopyInto(out *Status) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Details != nil { + in, out := &in.Details, &out.Details + *out = new(StatusDetails) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. +func (in *Status) DeepCopy() *Status { + if in == nil { + return nil + } + out := new(Status) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Status) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatusCause) DeepCopyInto(out *StatusCause) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusCause. +func (in *StatusCause) DeepCopy() *StatusCause { + if in == nil { + return nil + } + out := new(StatusCause) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StatusDetails) DeepCopyInto(out *StatusDetails) { + *out = *in + if in.Causes != nil { + in, out := &in.Causes, &out.Causes + *out = make([]StatusCause, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusDetails. +func (in *StatusDetails) DeepCopy() *StatusDetails { + if in == nil { + return nil + } + out := new(StatusDetails) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Table) DeepCopyInto(out *Table) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.ColumnDefinitions != nil { + in, out := &in.ColumnDefinitions, &out.ColumnDefinitions + *out = make([]TableColumnDefinition, len(*in)) + copy(*out, *in) + } + if in.Rows != nil { + in, out := &in.Rows, &out.Rows + *out = make([]TableRow, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Table. +func (in *Table) DeepCopy() *Table { + if in == nil { + return nil + } + out := new(Table) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Table) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TableColumnDefinition) DeepCopyInto(out *TableColumnDefinition) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TableColumnDefinition. +func (in *TableColumnDefinition) DeepCopy() *TableColumnDefinition { + if in == nil { + return nil + } + out := new(TableColumnDefinition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TableOptions) DeepCopyInto(out *TableOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TableOptions. +func (in *TableOptions) DeepCopy() *TableOptions { + if in == nil { + return nil + } + out := new(TableOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TableOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TableRow) DeepCopyInto(out *TableRow) { + clone := in.DeepCopy() + *out = *clone + return +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TableRowCondition) DeepCopyInto(out *TableRowCondition) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TableRowCondition. +func (in *TableRowCondition) DeepCopy() *TableRowCondition { + if in == nil { + return nil + } + out := new(TableRowCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Time. +func (in *Time) DeepCopy() *Time { + if in == nil { + return nil + } + out := new(Time) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Timestamp) DeepCopyInto(out *Timestamp) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Timestamp. +func (in *Timestamp) DeepCopy() *Timestamp { + if in == nil { + return nil + } + out := new(Timestamp) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpdateOptions) DeepCopyInto(out *UpdateOptions) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpdateOptions. +func (in *UpdateOptions) DeepCopy() *UpdateOptions { + if in == nil { + return nil + } + out := new(UpdateOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UpdateOptions) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Verbs) DeepCopyInto(out *Verbs) { + { + in := &in + *out = make(Verbs, len(*in)) + copy(*out, *in) + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Verbs. +func (in Verbs) DeepCopy() Verbs { + if in == nil { + return nil + } + out := new(Verbs) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WatchEvent) DeepCopyInto(out *WatchEvent) { + *out = *in + in.Object.DeepCopyInto(&out.Object) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WatchEvent. +func (in *WatchEvent) DeepCopy() *WatchEvent { + if in == nil { + return nil + } + out := new(WatchEvent) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WatchEvent) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.defaults.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.defaults.go new file mode 100644 index 0000000000..dac177e93b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.defaults.go @@ -0,0 +1,33 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.model_name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.model_name.go new file mode 100644 index 0000000000..3df687707c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1/zz_generated.model_name.go @@ -0,0 +1,272 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIGroup) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIGroupList) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.APIGroupList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIResource) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIResourceList) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in APIVersions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.APIVersions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ApplyOptions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.ApplyOptions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Condition) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.Condition" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CreateOptions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.CreateOptions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in DeleteOptions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Duration) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.Duration" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in FieldSelectorRequirement) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.FieldSelectorRequirement" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in FieldsV1) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GetOptions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.GetOptions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GroupKind) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.GroupKind" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GroupResource) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.GroupResource" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GroupVersion) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersion" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GroupVersionForDiscovery) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GroupVersionKind) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionKind" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in GroupVersionResource) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionResource" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in InternalEvent) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.InternalEvent" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LabelSelector) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in LabelSelectorRequirement) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in List) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.List" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ListMeta) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ListOptions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.ListOptions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ManagedFieldsEntry) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in MicroTime) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ObjectMeta) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in OwnerReference) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PartialObjectMetadata) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.PartialObjectMetadata" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PartialObjectMetadataList) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.PartialObjectMetadataList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Patch) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.Patch" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PatchOptions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.PatchOptions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Preconditions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in RootPaths) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.RootPaths" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ServerAddressByClientCIDR) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ShardInfo) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.ShardInfo" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Status) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.Status" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in StatusCause) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in StatusDetails) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Table) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.Table" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in TableColumnDefinition) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.TableColumnDefinition" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in TableOptions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.TableOptions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in TableRow) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.TableRow" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in TableRowCondition) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.TableRowCondition" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Time) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.Time" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Timestamp) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.Timestamp" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in TypeMeta) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.TypeMeta" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in UpdateOptions) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.UpdateOptions" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in WatchEvent) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/conversion.go new file mode 100644 index 0000000000..5cac6fba5a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/conversion.go @@ -0,0 +1,46 @@ +/* +Copyright 2017 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 v1beta1 + +import ( + "unsafe" + + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" +) + +// Convert_Slice_string_To_v1beta1_IncludeObjectPolicy allows converting a URL query parameter value +func Convert_Slice_string_To_v1beta1_IncludeObjectPolicy(in *[]string, out *IncludeObjectPolicy, s conversion.Scope) error { + if len(*in) > 0 { + *out = IncludeObjectPolicy((*in)[0]) + } + return nil +} + +// Convert_v1beta1_PartialObjectMetadataList_To_v1_PartialObjectMetadataList allows converting PartialObjectMetadataList between versions +func Convert_v1beta1_PartialObjectMetadataList_To_v1_PartialObjectMetadataList(in *PartialObjectMetadataList, out *v1.PartialObjectMetadataList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.PartialObjectMetadata)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1_PartialObjectMetadataList_To_v1beta1_PartialObjectMetadataList allows converting PartialObjectMetadataList between versions +func Convert_v1_PartialObjectMetadataList_To_v1beta1_PartialObjectMetadataList(in *v1.PartialObjectMetadataList, out *PartialObjectMetadataList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1.PartialObjectMetadata)(unsafe.Pointer(&in.Items)) + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/deepcopy.go new file mode 100644 index 0000000000..2b7e8ca0bf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/deepcopy.go @@ -0,0 +1,17 @@ +/* +Copyright 2017 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 v1beta1 diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/doc.go new file mode 100644 index 0000000000..159ca0573f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:openapi-gen=true +// +k8s:defaulter-gen=TypeMeta +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.apis.meta.v1beta1 + +// +groupName=meta.k8s.io + +package v1beta1 diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.pb.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.pb.go new file mode 100644 index 0000000000..3c763898e7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.pb.go @@ -0,0 +1,341 @@ +/* +Copyright 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto + +package v1beta1 + +import ( + fmt "fmt" + + io "io" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + math_bits "math/bits" + reflect "reflect" + strings "strings" +) + +func (m *PartialObjectMetadataList) Reset() { *m = PartialObjectMetadataList{} } + +func (m *PartialObjectMetadataList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PartialObjectMetadataList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PartialObjectMetadataList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *PartialObjectMetadataList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *PartialObjectMetadataList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]PartialObjectMetadata{" + for _, f := range this.Items { + repeatedStringForItems += fmt.Sprintf("%v", f) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&PartialObjectMetadataList{`, + `Items:` + repeatedStringForItems + `,`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *PartialObjectMetadataList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PartialObjectMetadataList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PartialObjectMetadataList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, v1.PartialObjectMetadata{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenerated + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenerated + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto new file mode 100644 index 0000000000..fcec553542 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/generated.proto @@ -0,0 +1,41 @@ +/* +Copyright 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.apimachinery.pkg.apis.meta.v1beta1; + +import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/apimachinery/pkg/apis/meta/v1beta1"; + +// PartialObjectMetadataList contains a list of objects containing only their metadata. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +message PartialObjectMetadataList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 2; + + // items contains each of the included items. + repeated .k8s.io.apimachinery.pkg.apis.meta.v1.PartialObjectMetadata items = 1; +} + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/register.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/register.go new file mode 100644 index 0000000000..a4a412e335 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/register.go @@ -0,0 +1,62 @@ +/* +Copyright 2017 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 v1beta1 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name for this API. +const GroupName = "meta.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// AddMetaToScheme registers base meta types into schemas. +func AddMetaToScheme(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Table{}, + &TableOptions{}, + &PartialObjectMetadata{}, + &PartialObjectMetadataList{}, + ) + + return nil +} + +// RegisterConversions adds conversion functions to the given scheme. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*PartialObjectMetadataList)(nil), (*v1.PartialObjectMetadataList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_PartialObjectMetadataList_To_v1_PartialObjectMetadataList(a.(*PartialObjectMetadataList), b.(*v1.PartialObjectMetadataList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1.PartialObjectMetadataList)(nil), (*PartialObjectMetadataList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_PartialObjectMetadataList_To_v1beta1_PartialObjectMetadataList(a.(*v1.PartialObjectMetadataList), b.(*PartialObjectMetadataList), scope) + }); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/types.go new file mode 100644 index 0000000000..1cda0840ac --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/types.go @@ -0,0 +1,85 @@ +/* +Copyright 2017 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 v1beta1 is alpha objects from meta that will be introduced. +package v1beta1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Table is a tabular representation of a set of API resources. The server transforms the +// object into a set of preferred columns for quickly reviewing the objects. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +protobuf=false +type Table = v1.Table + +// TableColumnDefinition contains information about a column returned in the Table. +// +protobuf=false +type TableColumnDefinition = v1.TableColumnDefinition + +// TableRow is an individual row in a table. +// +protobuf=false +type TableRow = v1.TableRow + +// TableRowCondition allows a row to be marked with additional information. +// +protobuf=false +type TableRowCondition = v1.TableRowCondition + +type RowConditionType = v1.RowConditionType + +type ConditionStatus = v1.ConditionStatus + +type IncludeObjectPolicy = v1.IncludeObjectPolicy + +// TableOptions are used when a Table is requested by the caller. +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TableOptions = v1.TableOptions + +// PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients +// to get access to a particular ObjectMeta schema without knowing the details of the version. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type PartialObjectMetadata = v1.PartialObjectMetadata + +// IMPORTANT: PartialObjectMetadataList has different protobuf field ids in v1beta1 than +// v1 because ListMeta was accidentally omitted prior to 1.15. Therefore this type must +// remain independent of v1.PartialObjectMetadataList to preserve mappings. + +// PartialObjectMetadataList contains a list of objects containing only their metadata. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type PartialObjectMetadataList struct { + v1.TypeMeta `json:""` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + v1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,2,opt,name=metadata"` + + // items contains each of the included items. + Items []v1.PartialObjectMetadata `json:"items" protobuf:"bytes,1,rep,name=items"` +} + +const ( + RowCompleted = v1.RowCompleted + + ConditionTrue = v1.ConditionTrue + ConditionFalse = v1.ConditionFalse + ConditionUnknown = v1.ConditionUnknown + + IncludeNone = v1.IncludeNone + IncludeMetadata = v1.IncludeMetadata + IncludeObject = v1.IncludeObject +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/types_swagger_doc_generated.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/types_swagger_doc_generated.go new file mode 100644 index 0000000000..dff735dcf3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/types_swagger_doc_generated.go @@ -0,0 +1,40 @@ +/* +Copyright 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 v1beta1 + +// This file contains a collection of methods that can be used from go-restful to +// generate Swagger API documentation for its models. Please read this PR for more +// information on the implementation: https://github.com/emicklei/go-restful/pull/215 +// +// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if +// they are on one line! For multiple line or blocks that you want to ignore use ---. +// Any context after a --- is ignored. +// +// Those methods can be generated by using hack/update-codegen.sh + +// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. +var map_PartialObjectMetadataList = map[string]string{ + "": "PartialObjectMetadataList contains a list of objects containing only their metadata.", + "metadata": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "items": "items contains each of the included items.", +} + +func (PartialObjectMetadataList) SwaggerDoc() map[string]string { + return map_PartialObjectMetadataList +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation/validation.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation/validation.go new file mode 100644 index 0000000000..563b62efa5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation/validation.go @@ -0,0 +1,33 @@ +/* +Copyright 2018 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 validation + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateTableOptions returns any invalid flags on TableOptions. +func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList { + var allErrs field.ErrorList + switch opts.IncludeObject { + case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "": + default: + allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty")) + } + return allErrs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..972b7f03ea --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,60 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1beta1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PartialObjectMetadataList) DeepCopyInto(out *PartialObjectMetadataList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1.PartialObjectMetadata, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PartialObjectMetadataList. +func (in *PartialObjectMetadataList) DeepCopy() *PartialObjectMetadataList { + if in == nil { + return nil + } + out := new(PartialObjectMetadataList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PartialObjectMetadataList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.defaults.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.defaults.go new file mode 100644 index 0000000000..198b5be4af --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.defaults.go @@ -0,0 +1,33 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.model_name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.model_name.go new file mode 100644 index 0000000000..9c3600119a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/meta/v1beta1/zz_generated.model_name.go @@ -0,0 +1,27 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1beta1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in PartialObjectMetadataList) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.meta.v1beta1.PartialObjectMetadataList" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/doc.go new file mode 100644 index 0000000000..4307afaf35 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:deepcopy-gen=package +// +groupName=testapigroup.apimachinery.k8s.io +// +// package testapigroup contains an testapigroup API used to demonstrate how to create api groups. Moreover, this is +// used within tests. +package testapigroup diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/fuzzer/fuzzer.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/fuzzer/fuzzer.go new file mode 100644 index 0000000000..ff98239713 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/fuzzer/fuzzer.go @@ -0,0 +1,99 @@ +/* +Copyright 2015 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 fuzzer + +import ( + "fmt" + + "sigs.k8s.io/randfill" + + apitesting "k8s.io/apimachinery/pkg/api/apitesting" + "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" + "k8s.io/apimachinery/pkg/apis/testapigroup" + v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" +) + +// overrideMetaFuncs override some generic fuzzer funcs from k8s.io/apimachinery in order to have more realistic +// values in a Kubernetes context. +func overrideMetaFuncs(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(j *runtime.Object, c randfill.Continue) { + // TODO: uncomment when round trip starts from a versioned object + if true { // c.Bool() { + *j = &runtime.Unknown{ + // We do not set TypeMeta here because it is not carried through a round trip + Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`), + ContentType: runtime.ContentTypeJSON, + } + } else { + types := []runtime.Object{&testapigroup.Carp{}} + t := types[c.Rand.Intn(len(types))] + c.Fill(t) + *j = t + } + }, + func(r *runtime.RawExtension, c randfill.Continue) { + // Pick an arbitrary type and fuzz it + types := []runtime.Object{&testapigroup.Carp{}} + obj := types[c.Rand.Intn(len(types))] + c.Fill(obj) + + // Convert the object to raw bytes + bytes, err := runtime.Encode(apitesting.TestCodec(codecs, v1.SchemeGroupVersion), obj) + if err != nil { + panic(fmt.Sprintf("Failed to encode object: %v", err)) + } + + // Set the bytes field on the RawExtension + r.Raw = bytes + }, + } +} + +func testapigroupFuncs(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(s *testapigroup.CarpSpec, c randfill.Continue) { + c.FillNoCustom(s) + // has a default value + ttl := int64(30) + if c.Bool() { + ttl = int64(c.Uint32()) + } + s.TerminationGracePeriodSeconds = &ttl + + if s.SchedulerName == "" { + s.SchedulerName = "default-scheduler" + } + }, + func(j *testapigroup.CarpPhase, c randfill.Continue) { + statuses := []testapigroup.CarpPhase{"Pending", "Running", "Succeeded", "Failed", "Unknown"} + *j = statuses[c.Rand.Intn(len(statuses))] + }, + func(rp *testapigroup.RestartPolicy, c randfill.Continue) { + policies := []testapigroup.RestartPolicy{"Always", "Never", "OnFailure"} + *rp = policies[c.Rand.Intn(len(policies))] + }, + } +} + +// Funcs returns the fuzzer functions for the testapigroup. +var Funcs = fuzzer.MergeFuzzerFuncs( + overrideMetaFuncs, + testapigroupFuncs, +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/install/install.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/install/install.go new file mode 100644 index 0000000000..6fc079f51b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/install/install.go @@ -0,0 +1,33 @@ +/* +Copyright 2016 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 install installs the certificates API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/apis/testapigroup" + "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +// Install registers the API group and adds types to a scheme +func Install(scheme *runtime.Scheme) { + utilruntime.Must(testapigroup.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) + utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion)) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/install/roundtrip_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/install/roundtrip_test.go new file mode 100644 index 0000000000..7e9b427a87 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/install/roundtrip_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2017 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 install + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" + testapigroupfuzzer "k8s.io/apimachinery/pkg/apis/testapigroup/fuzzer" +) + +func TestRoundTrip(t *testing.T) { + roundtrip.RoundTripTestForAPIGroup(t, Install, testapigroupfuzzer.Funcs) + roundtrip.RoundTripProtobufTestForAPIGroup(t, Install, testapigroupfuzzer.Funcs) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/register.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/register.go new file mode 100644 index 0000000000..f70c5a83b4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/register.go @@ -0,0 +1,52 @@ +/* +Copyright 2017 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 testapigroup + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// GroupName is the group name use in this package +const GroupName = "testapigroup.apimachinery.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Carp{}, + &CarpList{}, + ) + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/types.go new file mode 100644 index 0000000000..884963fede --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/types.go @@ -0,0 +1,161 @@ +/* +Copyright 2017 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 testapigroup + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ( + ConditionStatus string + CarpConditionType string + CarpPhase string + RestartPolicy string +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Carp is a collection of containers, used as either input (create, update) or as output (list, get). +type Carp struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the behavior of a carp. + // +optional + Spec CarpSpec + + // Status represents the current information about a carp. This data may not be up + // to date. + // +optional + Status CarpStatus +} + +// CarpStatus represents information about the status of a carp. Status may trail the actual +// state of a system. +type CarpStatus struct { + // +optional + Phase CarpPhase + // +optional + Conditions []CarpCondition + // A human readable message indicating details about why the carp is in this state. + // +optional + Message string + // A brief CamelCase message indicating details about why the carp is in this state. e.g. 'DiskPressure' + // +optional + Reason string + + // +optional + HostIP string + // +optional + CarpIP string + + // Date and time at which the object was acknowledged by the Kubelet. + // This is before the Kubelet pulled the container image(s) for the carp. + // +optional + StartTime *metav1.Time + + // Carp infos are provided by different clients, hence the map type. + // + // +listType=map + // +listKey=a + // +listKey=b + // +listKey=c + Infos []CarpInfo +} + +type CarpCondition struct { + Type CarpConditionType + Status ConditionStatus + // +optional + LastProbeTime metav1.Time + // +optional + LastTransitionTime metav1.Time + // +optional + Reason string + // +optional + Message string +} + +type CarpInfo struct { + // A is the first map key. + // +required + A int64 + // B is the second map key. + // +required + B string + // C is the third, optional map key + // +optional + C *string + + // Some data for each pair of A and B. + Data string +} + +// CarpSpec is a description of a carp +type CarpSpec struct { + // +optional + RestartPolicy RestartPolicy + // Optional duration in seconds the carp needs to terminate gracefully. May be decreased in delete request. + // Value must be non-negative integer. The value zero indicates delete immediately. + // If this value is nil, the default grace period will be used instead. + // The grace period is the duration in seconds after the processes running in the carp are sent + // a termination signal and the time when the processes are forcibly halted with a kill signal. + // Set this value longer than the expected cleanup time for your process. + // +optional + TerminationGracePeriodSeconds *int64 + // Optional duration in seconds relative to the StartTime that the carp may be active on a node + // before the system actively tries to terminate the carp; value must be positive integer + // +optional + ActiveDeadlineSeconds *int64 + // NodeSelector is a selector which must be true for the carp to fit on a node + // +optional + NodeSelector map[string]string + + // ServiceAccountName is the name of the ServiceAccount to use to run this carp + // The carp will be allowed to use secrets referenced by the ServiceAccount + ServiceAccountName string + + // NodeName is a request to schedule this carp onto a specific node. If it is non-empty, + // the scheduler simply schedules this carp onto that node, assuming that it fits resource + // requirements. + // +optional + NodeName string + // Specifies the hostname of the Carp. + // If not specified, the carp's hostname will be set to a system-defined value. + // +optional + Hostname string + // If specified, the fully qualified Carp hostname will be "...svc.". + // If not specified, the carp will not have a domainname at all. + // +optional + Subdomain string + // If specified, the carp will be dispatched by specified scheduler. + // If not specified, the carp will be dispatched by default scheduler. + // +optional + SchedulerName string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CarpList is a list of Carps. +type CarpList struct { + metav1.TypeMeta + // +optional + metav1.ListMeta + + Items []Carp +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/conversion.go new file mode 100644 index 0000000000..3e8448f4c5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/conversion.go @@ -0,0 +1,26 @@ +/* +Copyright 2017 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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +func addConversionFuncs(scheme *runtime.Scheme) error { + // Add non-generated conversion functions here. Currently there are none. + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/defaults.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/defaults.go new file mode 100644 index 0000000000..436ccde296 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/defaults.go @@ -0,0 +1,26 @@ +/* +Copyright 2017 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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + // return RegisterDefaults(scheme) + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/doc.go new file mode 100644 index 0000000000..35bdf7c7cf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/doc.go @@ -0,0 +1,27 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package +// +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/testapigroup +// +k8s:openapi-gen=false +// +k8s:defaulter-gen=TypeMeta +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.apis.testapigroup.v1 + +// +k8s:prerelease-lifecycle-gen=true +// +groupName=testapigroup.apimachinery.k8s.io + +package v1 diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.pb.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.pb.go new file mode 100644 index 0000000000..b1af782b02 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.pb.go @@ -0,0 +1,2323 @@ +/* +Copyright 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto + +package v1 + +import ( + fmt "fmt" + + io "io" + "sort" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + math_bits "math/bits" + reflect "reflect" + strings "strings" +) + +func (m *Carp) Reset() { *m = Carp{} } + +func (m *CarpCondition) Reset() { *m = CarpCondition{} } + +func (m *CarpInfo) Reset() { *m = CarpInfo{} } + +func (m *CarpList) Reset() { *m = CarpList{} } + +func (m *CarpSpec) Reset() { *m = CarpSpec{} } + +func (m *CarpStatus) Reset() { *m = CarpStatus{} } + +func (m *Carp) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Carp) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Carp) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *CarpCondition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CarpCondition) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CarpCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x32 + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x2a + { + size, err := m.LastTransitionTime.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size, err := m.LastProbeTime.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + i -= len(m.Status) + copy(dAtA[i:], m.Status) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i-- + dAtA[i] = 0x12 + i -= len(m.Type) + copy(dAtA[i:], m.Type) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Type))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *CarpInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CarpInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CarpInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.C != nil { + i -= len(*m.C) + copy(dAtA[i:], *m.C) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.C))) + i-- + dAtA[i] = 0x22 + } + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x1a + i -= len(m.B) + copy(dAtA[i:], m.B) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.B))) + i-- + dAtA[i] = 0x12 + i = encodeVarintGenerated(dAtA, i, uint64(m.A)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} + +func (m *CarpList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CarpList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CarpList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *CarpSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CarpSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CarpSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.SchedulerName) + copy(dAtA[i:], m.SchedulerName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.SchedulerName))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x9a + i -= len(m.Subdomain) + copy(dAtA[i:], m.Subdomain) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Subdomain))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x8a + i -= len(m.Hostname) + copy(dAtA[i:], m.Hostname) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Hostname))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x82 + i-- + if m.HostIPC { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x68 + i-- + if m.HostPID { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x60 + i-- + if m.HostNetwork { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x58 + i -= len(m.NodeName) + copy(dAtA[i:], m.NodeName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.NodeName))) + i-- + dAtA[i] = 0x52 + i -= len(m.DeprecatedServiceAccount) + copy(dAtA[i:], m.DeprecatedServiceAccount) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DeprecatedServiceAccount))) + i-- + dAtA[i] = 0x4a + i -= len(m.ServiceAccountName) + copy(dAtA[i:], m.ServiceAccountName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ServiceAccountName))) + i-- + dAtA[i] = 0x42 + if len(m.NodeSelector) > 0 { + keysForNodeSelector := make([]string, 0, len(m.NodeSelector)) + for k := range m.NodeSelector { + keysForNodeSelector = append(keysForNodeSelector, string(k)) + } + sort.Strings(keysForNodeSelector) + for iNdEx := len(keysForNodeSelector) - 1; iNdEx >= 0; iNdEx-- { + v := m.NodeSelector[string(keysForNodeSelector[iNdEx])] + baseI := i + i -= len(v) + copy(dAtA[i:], v) + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i-- + dAtA[i] = 0x12 + i -= len(keysForNodeSelector[iNdEx]) + copy(dAtA[i:], keysForNodeSelector[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForNodeSelector[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x3a + } + } + if m.ActiveDeadlineSeconds != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.ActiveDeadlineSeconds)) + i-- + dAtA[i] = 0x28 + } + if m.TerminationGracePeriodSeconds != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.TerminationGracePeriodSeconds)) + i-- + dAtA[i] = 0x20 + } + i -= len(m.RestartPolicy) + copy(dAtA[i:], m.RestartPolicy) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.RestartPolicy))) + i-- + dAtA[i] = 0x1a + return len(dAtA) - i, nil +} + +func (m *CarpStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CarpStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CarpStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Infos) > 0 { + for iNdEx := len(m.Infos) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Infos[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } + if m.StartTime != nil { + { + size, err := m.StartTime.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + i -= len(m.CarpIP) + copy(dAtA[i:], m.CarpIP) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CarpIP))) + i-- + dAtA[i] = 0x32 + i -= len(m.HostIP) + copy(dAtA[i:], m.HostIP) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.HostIP))) + i-- + dAtA[i] = 0x2a + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x22 + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x1a + if len(m.Conditions) > 0 { + for iNdEx := len(m.Conditions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Conditions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + i -= len(m.Phase) + copy(dAtA[i:], m.Phase) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Phase))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Carp) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *CarpCondition) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Type) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Status) + n += 1 + l + sovGenerated(uint64(l)) + l = m.LastProbeTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.LastTransitionTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Reason) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *CarpInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovGenerated(uint64(m.A)) + l = len(m.B) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Data) + n += 1 + l + sovGenerated(uint64(l)) + if m.C != nil { + l = len(*m.C) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *CarpList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *CarpSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RestartPolicy) + n += 1 + l + sovGenerated(uint64(l)) + if m.TerminationGracePeriodSeconds != nil { + n += 1 + sovGenerated(uint64(*m.TerminationGracePeriodSeconds)) + } + if m.ActiveDeadlineSeconds != nil { + n += 1 + sovGenerated(uint64(*m.ActiveDeadlineSeconds)) + } + if len(m.NodeSelector) > 0 { + for k, v := range m.NodeSelector { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + l = len(m.ServiceAccountName) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.DeprecatedServiceAccount) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.NodeName) + n += 1 + l + sovGenerated(uint64(l)) + n += 2 + n += 2 + n += 2 + l = len(m.Hostname) + n += 2 + l + sovGenerated(uint64(l)) + l = len(m.Subdomain) + n += 2 + l + sovGenerated(uint64(l)) + l = len(m.SchedulerName) + n += 2 + l + sovGenerated(uint64(l)) + return n +} + +func (m *CarpStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Phase) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Conditions) > 0 { + for _, e := range m.Conditions { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Reason) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.HostIP) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.CarpIP) + n += 1 + l + sovGenerated(uint64(l)) + if m.StartTime != nil { + l = m.StartTime.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.Infos) > 0 { + for _, e := range m.Infos { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *Carp) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Carp{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "CarpSpec", "CarpSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "CarpStatus", "CarpStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *CarpCondition) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CarpCondition{`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `Status:` + fmt.Sprintf("%v", this.Status) + `,`, + `LastProbeTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastProbeTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, + `LastTransitionTime:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastTransitionTime), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `}`, + }, "") + return s +} +func (this *CarpInfo) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CarpInfo{`, + `A:` + fmt.Sprintf("%v", this.A) + `,`, + `B:` + fmt.Sprintf("%v", this.B) + `,`, + `Data:` + fmt.Sprintf("%v", this.Data) + `,`, + `C:` + valueToStringGenerated(this.C) + `,`, + `}`, + }, "") + return s +} +func (this *CarpList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]Carp{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "Carp", "Carp", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&CarpList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *CarpSpec) String() string { + if this == nil { + return "nil" + } + keysForNodeSelector := make([]string, 0, len(this.NodeSelector)) + for k := range this.NodeSelector { + keysForNodeSelector = append(keysForNodeSelector, k) + } + sort.Strings(keysForNodeSelector) + mapStringForNodeSelector := "map[string]string{" + for _, k := range keysForNodeSelector { + mapStringForNodeSelector += fmt.Sprintf("%v: %v,", k, this.NodeSelector[k]) + } + mapStringForNodeSelector += "}" + s := strings.Join([]string{`&CarpSpec{`, + `RestartPolicy:` + fmt.Sprintf("%v", this.RestartPolicy) + `,`, + `TerminationGracePeriodSeconds:` + valueToStringGenerated(this.TerminationGracePeriodSeconds) + `,`, + `ActiveDeadlineSeconds:` + valueToStringGenerated(this.ActiveDeadlineSeconds) + `,`, + `NodeSelector:` + mapStringForNodeSelector + `,`, + `ServiceAccountName:` + fmt.Sprintf("%v", this.ServiceAccountName) + `,`, + `DeprecatedServiceAccount:` + fmt.Sprintf("%v", this.DeprecatedServiceAccount) + `,`, + `NodeName:` + fmt.Sprintf("%v", this.NodeName) + `,`, + `HostNetwork:` + fmt.Sprintf("%v", this.HostNetwork) + `,`, + `HostPID:` + fmt.Sprintf("%v", this.HostPID) + `,`, + `HostIPC:` + fmt.Sprintf("%v", this.HostIPC) + `,`, + `Hostname:` + fmt.Sprintf("%v", this.Hostname) + `,`, + `Subdomain:` + fmt.Sprintf("%v", this.Subdomain) + `,`, + `SchedulerName:` + fmt.Sprintf("%v", this.SchedulerName) + `,`, + `}`, + }, "") + return s +} +func (this *CarpStatus) String() string { + if this == nil { + return "nil" + } + repeatedStringForConditions := "[]CarpCondition{" + for _, f := range this.Conditions { + repeatedStringForConditions += strings.Replace(strings.Replace(f.String(), "CarpCondition", "CarpCondition", 1), `&`, ``, 1) + "," + } + repeatedStringForConditions += "}" + repeatedStringForInfos := "[]CarpInfo{" + for _, f := range this.Infos { + repeatedStringForInfos += strings.Replace(strings.Replace(f.String(), "CarpInfo", "CarpInfo", 1), `&`, ``, 1) + "," + } + repeatedStringForInfos += "}" + s := strings.Join([]string{`&CarpStatus{`, + `Phase:` + fmt.Sprintf("%v", this.Phase) + `,`, + `Conditions:` + repeatedStringForConditions + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, + `HostIP:` + fmt.Sprintf("%v", this.HostIP) + `,`, + `CarpIP:` + fmt.Sprintf("%v", this.CarpIP) + `,`, + `StartTime:` + strings.Replace(fmt.Sprintf("%v", this.StartTime), "Time", "v1.Time", 1) + `,`, + `Infos:` + repeatedStringForInfos + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *Carp) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Carp: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Carp: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CarpCondition) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CarpCondition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CarpCondition: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Type = CarpConditionType(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Status = ConditionStatus(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastProbeTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LastProbeTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastTransitionTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LastTransitionTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CarpInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CarpInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CarpInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field A", wireType) + } + m.A = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.A |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field B", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.B = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field C", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.C = &s + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CarpList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CarpList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CarpList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, Carp{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CarpSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CarpSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CarpSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RestartPolicy", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RestartPolicy = RestartPolicy(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TerminationGracePeriodSeconds", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TerminationGracePeriodSeconds = &v + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ActiveDeadlineSeconds", wireType) + } + var v int64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ActiveDeadlineSeconds = &v + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NodeSelector == nil { + m.NodeSelector = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.NodeSelector[mapkey] = mapvalue + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceAccountName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceAccountName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeprecatedServiceAccount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeprecatedServiceAccount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NodeName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NodeName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HostNetwork", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.HostNetwork = bool(v != 0) + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HostPID", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.HostPID = bool(v != 0) + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HostIPC", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.HostIPC = bool(v != 0) + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hostname", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hostname = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Subdomain", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Subdomain = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 19: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SchedulerName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SchedulerName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CarpStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CarpStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CarpStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Phase", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Phase = CarpPhase(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Conditions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Conditions = append(m.Conditions, CarpCondition{}) + if err := m.Conditions[len(m.Conditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HostIP", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HostIP = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CarpIP", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CarpIP = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StartTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.StartTime == nil { + m.StartTime = &v1.Time{} + } + if err := m.StartTime.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Infos", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Infos = append(m.Infos, CarpInfo{}) + if err := m.Infos[len(m.Infos)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenerated + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenerated + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto new file mode 100644 index 0000000000..c533cae9aa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/generated.proto @@ -0,0 +1,239 @@ +/* +Copyright 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.apimachinery.pkg.apis.testapigroup.v1; + +import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/apimachinery/pkg/apis/testapigroup/v1"; + +// Carp is a collection of containers, used as either input (create, update) or as output (list, get). +message Carp { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Specification of the desired behavior of the carp. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + optional CarpSpec spec = 2; + + // Most recently observed status of the carp. + // This data may not be up to date. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + optional CarpStatus status = 3; +} + +message CarpCondition { + // Type is the type of the condition. + // Currently only Ready. + // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions + optional string type = 1; + + // Status is the status of the condition. + // Can be True, False, Unknown. + // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions + optional string status = 2; + + // Last time we probed the condition. + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastProbeTime = 3; + + // Last time the condition transitioned from one status to another. + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 4; + + // Unique, one-word, CamelCase reason for the condition's last transition. + // +optional + optional string reason = 5; + + // Human-readable message indicating details about last transition. + // +optional + optional string message = 6; +} + +message CarpInfo { + // A is the first map key. + // +required + optional int64 a = 1; + + // B is the second map key. + // +required + optional string b = 2; + + // C is the third, optional map key + // +optional + optional string c = 4; + + // Some data for each pair of A and B. + optional string data = 3; +} + +// CarpList is a list of Carps. +message CarpList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // List of carps. + // More info: http://kubernetes.io/docs/user-guide/carps + repeated Carp items = 2; +} + +// CarpSpec is a description of a carp +message CarpSpec { + // Restart policy for all containers within the carp. + // One of Always, OnFailure, Never. + // Default to Always. + // More info: http://kubernetes.io/docs/user-guide/carp-states#restartpolicy + // +optional + optional string restartPolicy = 3; + + // Optional duration in seconds the carp needs to terminate gracefully. May be decreased in delete request. + // Value must be non-negative integer. The value zero indicates delete immediately. + // If this value is nil, the default grace period will be used instead. + // The grace period is the duration in seconds after the processes running in the carp are sent + // a termination signal and the time when the processes are forcibly halted with a kill signal. + // Set this value longer than the expected cleanup time for your process. + // Defaults to 30 seconds. + // +optional + optional int64 terminationGracePeriodSeconds = 4; + + // Optional duration in seconds the carp may be active on the node relative to + // StartTime before the system will actively try to mark it failed and kill associated containers. + // Value must be a positive integer. + // +optional + optional int64 activeDeadlineSeconds = 5; + + // NodeSelector is a selector which must be true for the carp to fit on a node. + // Selector which must match a node's labels for the carp to be scheduled on that node. + // More info: http://kubernetes.io/docs/user-guide/node-selection/README + // +optional + map nodeSelector = 7; + + // ServiceAccountName is the name of the ServiceAccount to use to run this carp. + // More info: https://kubernetes.io/docs/concepts/security/service-accounts/ + // +optional + optional string serviceAccountName = 8; + + // DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. + // Deprecated: Use serviceAccountName instead. + // +k8s:conversion-gen=false + // +optional + optional string deprecatedServiceAccount = 9; + + // NodeName is a request to schedule this carp onto a specific node. If it is non-empty, + // the scheduler simply schedules this carp onto that node, assuming that it fits resource + // requirements. + // +optional + optional string nodeName = 10; + + // Host networking requested for this carp. Use the host's network namespace. + // Default to false. + // +k8s:conversion-gen=false + // +optional + optional bool hostNetwork = 11; + + // Use the host's pid namespace. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + optional bool hostPID = 12; + + // Use the host's ipc namespace. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + optional bool hostIPC = 13; + + // Specifies the hostname of the Carp + // If not specified, the carp's hostname will be set to a system-defined value. + // +optional + optional string hostname = 16; + + // If specified, the fully qualified Carp hostname will be "...svc.". + // If not specified, the carp will not have a domainname at all. + // +optional + optional string subdomain = 17; + + // If specified, the carp will be dispatched by specified scheduler. + // If not specified, the carp will be dispatched by default scheduler. + // +optional + optional string schedulerName = 19; +} + +// CarpStatus represents information about the status of a carp. Status may trail the actual +// state of a system. +message CarpStatus { + // Current condition of the carp. + // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-phase + // +optional + optional string phase = 1; + + // Current service state of carp. + // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions + // +patchStrategy=merge + // +patchMergeKey=type + // +listType=map + // +listMapKey=type + // +optional + repeated CarpCondition conditions = 2; + + // A human readable message indicating details about why the carp is in this condition. + // +optional + optional string message = 3; + + // A brief CamelCase message indicating details about why the carp is in this state. + // e.g. 'DiskPressure' + // +optional + optional string reason = 4; + + // IP address of the host to which the carp is assigned. Empty if not yet scheduled. + // +optional + optional string hostIP = 5; + + // IP address allocated to the carp. Routable at least within the cluster. + // Empty if not yet allocated. + // +optional + optional string carpIP = 6; + + // RFC 3339 date and time at which the object was acknowledged by the Kubelet. + // This is before the Kubelet pulled the container image(s) for the carp. + // +optional + optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time startTime = 7; + + // Carp infos are provided by different clients, hence the map type. + // + // +listType=map + // +listMapKey=a + // +listMapKey=b + // +listMapKey=c + repeated CarpInfo infos = 8; +} + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/register.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/register.go new file mode 100644 index 0000000000..afd908c7fe --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/register.go @@ -0,0 +1,64 @@ +/* +Copyright 2017 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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "testapigroup.apimachinery.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes, addConversionFuncs, addDefaultingFuncs) +} + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Carp{}, + &CarpList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/types.go new file mode 100644 index 0000000000..8512a514c6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/types.go @@ -0,0 +1,224 @@ +/* +Copyright 2017 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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ( + ConditionStatus string + CarpConditionType string + CarpPhase string + RestartPolicy string +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.1 + +// Carp is a collection of containers, used as either input (create, update) or as output (list, get). +type Carp struct { + metav1.TypeMeta `json:""` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Specification of the desired behavior of the carp. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec CarpSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + + // Most recently observed status of the carp. + // This data may not be up to date. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status CarpStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// CarpStatus represents information about the status of a carp. Status may trail the actual +// state of a system. +type CarpStatus struct { + // Current condition of the carp. + // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-phase + // +optional + Phase CarpPhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=CarpPhase"` + // Current service state of carp. + // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions + // +patchStrategy=merge + // +patchMergeKey=type + // +listType=map + // +listMapKey=type + // +optional + Conditions []CarpCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,2,opt,name=conditions"` + // A human readable message indicating details about why the carp is in this condition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"` + // A brief CamelCase message indicating details about why the carp is in this state. + // e.g. 'DiskPressure' + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + + // IP address of the host to which the carp is assigned. Empty if not yet scheduled. + // +optional + HostIP string `json:"hostIP,omitempty" protobuf:"bytes,5,opt,name=hostIP"` + // IP address allocated to the carp. Routable at least within the cluster. + // Empty if not yet allocated. + // +optional + CarpIP string `json:"carpIP,omitempty" protobuf:"bytes,6,opt,name=carpIP"` + + // RFC 3339 date and time at which the object was acknowledged by the Kubelet. + // This is before the Kubelet pulled the container image(s) for the carp. + // +optional + StartTime *metav1.Time `json:"startTime,omitempty" protobuf:"bytes,7,opt,name=startTime"` + + // Carp infos are provided by different clients, hence the map type. + // + // +listType=map + // +listMapKey=a + // +listMapKey=b + // +listMapKey=c + Infos []CarpInfo `json:"infos,omitempty" protobuf:"bytes,8,rep,name=infos"` +} + +type CarpCondition struct { + // Type is the type of the condition. + // Currently only Ready. + // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions + Type CarpConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=CarpConditionType"` + // Status is the status of the condition. + // Can be True, False, Unknown. + // More info: http://kubernetes.io/docs/user-guide/carp-states#carp-conditions + Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"` + // Last time we probed the condition. + // +optional + LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"` + // Unique, one-word, CamelCase reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"` + // Human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` +} + +type CarpInfo struct { + // A is the first map key. + // +required + A int64 `json:"a" protobuf:"bytes,1,name=a"` + // B is the second map key. + // +required + B string `json:"b" protobuf:"bytes,2,name=b"` + // C is the third, optional map key + // +optional + C *string `json:"c,omitempty" protobuf:"bytes,4,opt,name=c"` + + // Some data for each pair of A and B. + Data string `json:"data" protobuf:"bytes,3,name=data"` +} + +// CarpSpec is a description of a carp +type CarpSpec struct { + // Restart policy for all containers within the carp. + // One of Always, OnFailure, Never. + // Default to Always. + // More info: http://kubernetes.io/docs/user-guide/carp-states#restartpolicy + // +optional + RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" protobuf:"bytes,3,opt,name=restartPolicy,casttype=RestartPolicy"` + // Optional duration in seconds the carp needs to terminate gracefully. May be decreased in delete request. + // Value must be non-negative integer. The value zero indicates delete immediately. + // If this value is nil, the default grace period will be used instead. + // The grace period is the duration in seconds after the processes running in the carp are sent + // a termination signal and the time when the processes are forcibly halted with a kill signal. + // Set this value longer than the expected cleanup time for your process. + // Defaults to 30 seconds. + // +optional + TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty" protobuf:"varint,4,opt,name=terminationGracePeriodSeconds"` + // Optional duration in seconds the carp may be active on the node relative to + // StartTime before the system will actively try to mark it failed and kill associated containers. + // Value must be a positive integer. + // +optional + ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty" protobuf:"varint,5,opt,name=activeDeadlineSeconds"` + // NodeSelector is a selector which must be true for the carp to fit on a node. + // Selector which must match a node's labels for the carp to be scheduled on that node. + // More info: http://kubernetes.io/docs/user-guide/node-selection/README + // +optional + NodeSelector map[string]string `json:"nodeSelector,omitempty" protobuf:"bytes,7,rep,name=nodeSelector"` + + // ServiceAccountName is the name of the ServiceAccount to use to run this carp. + // More info: https://kubernetes.io/docs/concepts/security/service-accounts/ + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty" protobuf:"bytes,8,opt,name=serviceAccountName"` + // DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. + // Deprecated: Use serviceAccountName instead. + // +k8s:conversion-gen=false + // +optional + DeprecatedServiceAccount string `json:"deprecatedServiceAccount,omitempty" protobuf:"bytes,9,opt,name=deprecatedServiceAccount"` + + // NodeName is a request to schedule this carp onto a specific node. If it is non-empty, + // the scheduler simply schedules this carp onto that node, assuming that it fits resource + // requirements. + // +optional + NodeName string `json:"nodeName,omitempty" protobuf:"bytes,10,opt,name=nodeName"` + // Host networking requested for this carp. Use the host's network namespace. + // Default to false. + // +k8s:conversion-gen=false + // +optional + HostNetwork bool `json:"hostNetwork,omitempty" protobuf:"varint,11,opt,name=hostNetwork"` + // Use the host's pid namespace. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + HostPID bool `json:"hostPID,omitempty" protobuf:"varint,12,opt,name=hostPID"` + // Use the host's ipc namespace. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + HostIPC bool `json:"hostIPC,omitempty" protobuf:"varint,13,opt,name=hostIPC"` + // Specifies the hostname of the Carp + // If not specified, the carp's hostname will be set to a system-defined value. + // +optional + Hostname string `json:"hostname,omitempty" protobuf:"bytes,16,opt,name=hostname"` + // If specified, the fully qualified Carp hostname will be "...svc.". + // If not specified, the carp will not have a domainname at all. + // +optional + Subdomain string `json:"subdomain,omitempty" protobuf:"bytes,17,opt,name=subdomain"` + // If specified, the carp will be dispatched by specified scheduler. + // If not specified, the carp will be dispatched by default scheduler. + // +optional + SchedulerName string `json:"schedulerName,omitempty" protobuf:"bytes,19,opt,name=schedulerName"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:prerelease-lifecycle-gen:introduced=1.1 + +// CarpList is a list of Carps. +type CarpList struct { + metav1.TypeMeta `json:""` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // List of carps. + // More info: http://kubernetes.io/docs/user-guide/carps + Items []Carp `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.conversion.go new file mode 100644 index 0000000000..92056d520b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.conversion.go @@ -0,0 +1,274 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1 + +import ( + unsafe "unsafe" + + testapigroup "k8s.io/apimachinery/pkg/apis/testapigroup" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*Carp)(nil), (*testapigroup.Carp)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Carp_To_testapigroup_Carp(a.(*Carp), b.(*testapigroup.Carp), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*testapigroup.Carp)(nil), (*Carp)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_testapigroup_Carp_To_v1_Carp(a.(*testapigroup.Carp), b.(*Carp), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*CarpCondition)(nil), (*testapigroup.CarpCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CarpCondition_To_testapigroup_CarpCondition(a.(*CarpCondition), b.(*testapigroup.CarpCondition), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*testapigroup.CarpCondition)(nil), (*CarpCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_testapigroup_CarpCondition_To_v1_CarpCondition(a.(*testapigroup.CarpCondition), b.(*CarpCondition), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*CarpInfo)(nil), (*testapigroup.CarpInfo)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CarpInfo_To_testapigroup_CarpInfo(a.(*CarpInfo), b.(*testapigroup.CarpInfo), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*testapigroup.CarpInfo)(nil), (*CarpInfo)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_testapigroup_CarpInfo_To_v1_CarpInfo(a.(*testapigroup.CarpInfo), b.(*CarpInfo), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*CarpList)(nil), (*testapigroup.CarpList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CarpList_To_testapigroup_CarpList(a.(*CarpList), b.(*testapigroup.CarpList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*testapigroup.CarpList)(nil), (*CarpList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_testapigroup_CarpList_To_v1_CarpList(a.(*testapigroup.CarpList), b.(*CarpList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*CarpSpec)(nil), (*testapigroup.CarpSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CarpSpec_To_testapigroup_CarpSpec(a.(*CarpSpec), b.(*testapigroup.CarpSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*testapigroup.CarpSpec)(nil), (*CarpSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_testapigroup_CarpSpec_To_v1_CarpSpec(a.(*testapigroup.CarpSpec), b.(*CarpSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*CarpStatus)(nil), (*testapigroup.CarpStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CarpStatus_To_testapigroup_CarpStatus(a.(*CarpStatus), b.(*testapigroup.CarpStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*testapigroup.CarpStatus)(nil), (*CarpStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_testapigroup_CarpStatus_To_v1_CarpStatus(a.(*testapigroup.CarpStatus), b.(*CarpStatus), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1_Carp_To_testapigroup_Carp(in *Carp, out *testapigroup.Carp, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_CarpSpec_To_testapigroup_CarpSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_CarpStatus_To_testapigroup_CarpStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_Carp_To_testapigroup_Carp is an autogenerated conversion function. +func Convert_v1_Carp_To_testapigroup_Carp(in *Carp, out *testapigroup.Carp, s conversion.Scope) error { + return autoConvert_v1_Carp_To_testapigroup_Carp(in, out, s) +} + +func autoConvert_testapigroup_Carp_To_v1_Carp(in *testapigroup.Carp, out *Carp, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_testapigroup_CarpSpec_To_v1_CarpSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_testapigroup_CarpStatus_To_v1_CarpStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_testapigroup_Carp_To_v1_Carp is an autogenerated conversion function. +func Convert_testapigroup_Carp_To_v1_Carp(in *testapigroup.Carp, out *Carp, s conversion.Scope) error { + return autoConvert_testapigroup_Carp_To_v1_Carp(in, out, s) +} + +func autoConvert_v1_CarpCondition_To_testapigroup_CarpCondition(in *CarpCondition, out *testapigroup.CarpCondition, s conversion.Scope) error { + *out = *(*testapigroup.CarpCondition)(unsafe.Pointer(in)) + return nil +} + +// Convert_v1_CarpCondition_To_testapigroup_CarpCondition is an autogenerated conversion function. +func Convert_v1_CarpCondition_To_testapigroup_CarpCondition(in *CarpCondition, out *testapigroup.CarpCondition, s conversion.Scope) error { + return autoConvert_v1_CarpCondition_To_testapigroup_CarpCondition(in, out, s) +} + +func autoConvert_testapigroup_CarpCondition_To_v1_CarpCondition(in *testapigroup.CarpCondition, out *CarpCondition, s conversion.Scope) error { + *out = *(*CarpCondition)(unsafe.Pointer(in)) + return nil +} + +// Convert_testapigroup_CarpCondition_To_v1_CarpCondition is an autogenerated conversion function. +func Convert_testapigroup_CarpCondition_To_v1_CarpCondition(in *testapigroup.CarpCondition, out *CarpCondition, s conversion.Scope) error { + return autoConvert_testapigroup_CarpCondition_To_v1_CarpCondition(in, out, s) +} + +func autoConvert_v1_CarpInfo_To_testapigroup_CarpInfo(in *CarpInfo, out *testapigroup.CarpInfo, s conversion.Scope) error { + *out = *(*testapigroup.CarpInfo)(unsafe.Pointer(in)) + return nil +} + +// Convert_v1_CarpInfo_To_testapigroup_CarpInfo is an autogenerated conversion function. +func Convert_v1_CarpInfo_To_testapigroup_CarpInfo(in *CarpInfo, out *testapigroup.CarpInfo, s conversion.Scope) error { + return autoConvert_v1_CarpInfo_To_testapigroup_CarpInfo(in, out, s) +} + +func autoConvert_testapigroup_CarpInfo_To_v1_CarpInfo(in *testapigroup.CarpInfo, out *CarpInfo, s conversion.Scope) error { + *out = *(*CarpInfo)(unsafe.Pointer(in)) + return nil +} + +// Convert_testapigroup_CarpInfo_To_v1_CarpInfo is an autogenerated conversion function. +func Convert_testapigroup_CarpInfo_To_v1_CarpInfo(in *testapigroup.CarpInfo, out *CarpInfo, s conversion.Scope) error { + return autoConvert_testapigroup_CarpInfo_To_v1_CarpInfo(in, out, s) +} + +func autoConvert_v1_CarpList_To_testapigroup_CarpList(in *CarpList, out *testapigroup.CarpList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]testapigroup.Carp, len(*in)) + for i := range *in { + if err := Convert_v1_Carp_To_testapigroup_Carp(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1_CarpList_To_testapigroup_CarpList is an autogenerated conversion function. +func Convert_v1_CarpList_To_testapigroup_CarpList(in *CarpList, out *testapigroup.CarpList, s conversion.Scope) error { + return autoConvert_v1_CarpList_To_testapigroup_CarpList(in, out, s) +} + +func autoConvert_testapigroup_CarpList_To_v1_CarpList(in *testapigroup.CarpList, out *CarpList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Carp, len(*in)) + for i := range *in { + if err := Convert_testapigroup_Carp_To_v1_Carp(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_testapigroup_CarpList_To_v1_CarpList is an autogenerated conversion function. +func Convert_testapigroup_CarpList_To_v1_CarpList(in *testapigroup.CarpList, out *CarpList, s conversion.Scope) error { + return autoConvert_testapigroup_CarpList_To_v1_CarpList(in, out, s) +} + +func autoConvert_v1_CarpSpec_To_testapigroup_CarpSpec(in *CarpSpec, out *testapigroup.CarpSpec, s conversion.Scope) error { + out.RestartPolicy = testapigroup.RestartPolicy(in.RestartPolicy) + out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds)) + out.ActiveDeadlineSeconds = (*int64)(unsafe.Pointer(in.ActiveDeadlineSeconds)) + out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) + out.ServiceAccountName = in.ServiceAccountName + // INFO: in.DeprecatedServiceAccount opted out of conversion generation + out.NodeName = in.NodeName + // INFO: in.HostNetwork opted out of conversion generation + // INFO: in.HostPID opted out of conversion generation + // INFO: in.HostIPC opted out of conversion generation + out.Hostname = in.Hostname + out.Subdomain = in.Subdomain + out.SchedulerName = in.SchedulerName + return nil +} + +// Convert_v1_CarpSpec_To_testapigroup_CarpSpec is an autogenerated conversion function. +func Convert_v1_CarpSpec_To_testapigroup_CarpSpec(in *CarpSpec, out *testapigroup.CarpSpec, s conversion.Scope) error { + return autoConvert_v1_CarpSpec_To_testapigroup_CarpSpec(in, out, s) +} + +func autoConvert_testapigroup_CarpSpec_To_v1_CarpSpec(in *testapigroup.CarpSpec, out *CarpSpec, s conversion.Scope) error { + out.RestartPolicy = RestartPolicy(in.RestartPolicy) + out.TerminationGracePeriodSeconds = (*int64)(unsafe.Pointer(in.TerminationGracePeriodSeconds)) + out.ActiveDeadlineSeconds = (*int64)(unsafe.Pointer(in.ActiveDeadlineSeconds)) + out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) + out.ServiceAccountName = in.ServiceAccountName + out.NodeName = in.NodeName + out.Hostname = in.Hostname + out.Subdomain = in.Subdomain + out.SchedulerName = in.SchedulerName + return nil +} + +// Convert_testapigroup_CarpSpec_To_v1_CarpSpec is an autogenerated conversion function. +func Convert_testapigroup_CarpSpec_To_v1_CarpSpec(in *testapigroup.CarpSpec, out *CarpSpec, s conversion.Scope) error { + return autoConvert_testapigroup_CarpSpec_To_v1_CarpSpec(in, out, s) +} + +func autoConvert_v1_CarpStatus_To_testapigroup_CarpStatus(in *CarpStatus, out *testapigroup.CarpStatus, s conversion.Scope) error { + *out = *(*testapigroup.CarpStatus)(unsafe.Pointer(in)) + return nil +} + +// Convert_v1_CarpStatus_To_testapigroup_CarpStatus is an autogenerated conversion function. +func Convert_v1_CarpStatus_To_testapigroup_CarpStatus(in *CarpStatus, out *testapigroup.CarpStatus, s conversion.Scope) error { + return autoConvert_v1_CarpStatus_To_testapigroup_CarpStatus(in, out, s) +} + +func autoConvert_testapigroup_CarpStatus_To_v1_CarpStatus(in *testapigroup.CarpStatus, out *CarpStatus, s conversion.Scope) error { + *out = *(*CarpStatus)(unsafe.Pointer(in)) + return nil +} + +// Convert_testapigroup_CarpStatus_To_v1_CarpStatus is an autogenerated conversion function. +func Convert_testapigroup_CarpStatus_To_v1_CarpStatus(in *testapigroup.CarpStatus, out *CarpStatus, s conversion.Scope) error { + return autoConvert_testapigroup_CarpStatus_To_v1_CarpStatus(in, out, s) +} diff --git a/vendor/k8s.io/api/apidiscovery/v2beta1/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.deepcopy.go similarity index 52% rename from vendor/k8s.io/api/apidiscovery/v2beta1/zz_generated.deepcopy.go rename to hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.deepcopy.go index cf8f98c6fb..d5969b09fc 100644 --- a/vendor/k8s.io/api/apidiscovery/v2beta1/zz_generated.deepcopy.go +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.deepcopy.go @@ -19,40 +19,34 @@ limitations under the License. // Code generated by deepcopy-gen. DO NOT EDIT. -package v2beta1 +package v1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIGroupDiscovery) DeepCopyInto(out *APIGroupDiscovery) { +func (in *Carp) DeepCopyInto(out *Carp) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Versions != nil { - in, out := &in.Versions, &out.Versions - *out = make([]APIVersionDiscovery, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGroupDiscovery. -func (in *APIGroupDiscovery) DeepCopy() *APIGroupDiscovery { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Carp. +func (in *Carp) DeepCopy() *Carp { if in == nil { return nil } - out := new(APIGroupDiscovery) + out := new(Carp) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *APIGroupDiscovery) DeepCopyObject() runtime.Object { +func (in *Carp) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -60,64 +54,52 @@ func (in *APIGroupDiscovery) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIGroupDiscoveryList) DeepCopyInto(out *APIGroupDiscoveryList) { +func (in *CarpCondition) DeepCopyInto(out *CarpCondition) { *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]APIGroupDiscovery, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } + in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIGroupDiscoveryList. -func (in *APIGroupDiscoveryList) DeepCopy() *APIGroupDiscoveryList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpCondition. +func (in *CarpCondition) DeepCopy() *CarpCondition { if in == nil { return nil } - out := new(APIGroupDiscoveryList) + out := new(CarpCondition) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *APIGroupDiscoveryList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIResourceDiscovery) DeepCopyInto(out *APIResourceDiscovery) { +func (in *CarpInfo) DeepCopyInto(out *CarpInfo) { *out = *in - if in.ResponseKind != nil { - in, out := &in.ResponseKind, &out.ResponseKind - *out = new(v1.GroupVersionKind) + if in.C != nil { + in, out := &in.C, &out.C + *out = new(string) **out = **in } - if in.Verbs != nil { - in, out := &in.Verbs, &out.Verbs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ShortNames != nil { - in, out := &in.ShortNames, &out.ShortNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Categories != nil { - in, out := &in.Categories, &out.Categories - *out = make([]string, len(*in)) - copy(*out, *in) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpInfo. +func (in *CarpInfo) DeepCopy() *CarpInfo { + if in == nil { + return nil } - if in.Subresources != nil { - in, out := &in.Subresources, &out.Subresources - *out = make([]APISubresourceDiscovery, len(*in)) + out := new(CarpInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CarpList) DeepCopyInto(out *CarpList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Carp, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -125,53 +107,74 @@ func (in *APIResourceDiscovery) DeepCopyInto(out *APIResourceDiscovery) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceDiscovery. -func (in *APIResourceDiscovery) DeepCopy() *APIResourceDiscovery { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpList. +func (in *CarpList) DeepCopy() *CarpList { if in == nil { return nil } - out := new(APIResourceDiscovery) + out := new(CarpList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CarpList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APISubresourceDiscovery) DeepCopyInto(out *APISubresourceDiscovery) { +func (in *CarpSpec) DeepCopyInto(out *CarpSpec) { *out = *in - if in.ResponseKind != nil { - in, out := &in.ResponseKind, &out.ResponseKind - *out = new(v1.GroupVersionKind) + if in.TerminationGracePeriodSeconds != nil { + in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds + *out = new(int64) **out = **in } - if in.AcceptedTypes != nil { - in, out := &in.AcceptedTypes, &out.AcceptedTypes - *out = make([]v1.GroupVersionKind, len(*in)) - copy(*out, *in) + if in.ActiveDeadlineSeconds != nil { + in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds + *out = new(int64) + **out = **in } - if in.Verbs != nil { - in, out := &in.Verbs, &out.Verbs - *out = make([]string, len(*in)) - copy(*out, *in) + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APISubresourceDiscovery. -func (in *APISubresourceDiscovery) DeepCopy() *APISubresourceDiscovery { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpSpec. +func (in *CarpSpec) DeepCopy() *CarpSpec { if in == nil { return nil } - out := new(APISubresourceDiscovery) + out := new(CarpSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIVersionDiscovery) DeepCopyInto(out *APIVersionDiscovery) { +func (in *CarpStatus) DeepCopyInto(out *CarpStatus) { *out = *in - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = make([]APIResourceDiscovery, len(*in)) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]CarpCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + *out = (*in).DeepCopy() + } + if in.Infos != nil { + in, out := &in.Infos, &out.Infos + *out = make([]CarpInfo, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -179,12 +182,12 @@ func (in *APIVersionDiscovery) DeepCopyInto(out *APIVersionDiscovery) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersionDiscovery. -func (in *APIVersionDiscovery) DeepCopy() *APIVersionDiscovery { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpStatus. +func (in *CarpStatus) DeepCopy() *CarpStatus { if in == nil { return nil } - out := new(APIVersionDiscovery) + out := new(CarpStatus) in.DeepCopyInto(out) return out } diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.defaults.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.defaults.go new file mode 100644 index 0000000000..dac177e93b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.defaults.go @@ -0,0 +1,33 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.model_name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.model_name.go new file mode 100644 index 0000000000..38ee899cb9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.model_name.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Carp) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.testapigroup.v1.Carp" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CarpCondition) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.testapigroup.v1.CarpCondition" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CarpInfo) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.testapigroup.v1.CarpInfo" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CarpList) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.testapigroup.v1.CarpList" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CarpSpec) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.testapigroup.v1.CarpSpec" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in CarpStatus) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.apis.testapigroup.v1.CarpStatus" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.prerelease-lifecycle.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.prerelease-lifecycle.go new file mode 100644 index 0000000000..7cd1cf2592 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/v1/zz_generated.prerelease-lifecycle.go @@ -0,0 +1,34 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by prerelease-lifecycle-gen. DO NOT EDIT. + +package v1 + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *Carp) APILifecycleIntroduced() (major, minor int) { + return 1, 1 +} + +// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison. +// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go. +func (in *CarpList) APILifecycleIntroduced() (major, minor int) { + return 1, 1 +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/zz_generated.deepcopy.go new file mode 100644 index 0000000000..a44dcfe14e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/apis/testapigroup/zz_generated.deepcopy.go @@ -0,0 +1,193 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package testapigroup + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Carp) DeepCopyInto(out *Carp) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Carp. +func (in *Carp) DeepCopy() *Carp { + if in == nil { + return nil + } + out := new(Carp) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Carp) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CarpCondition) DeepCopyInto(out *CarpCondition) { + *out = *in + in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpCondition. +func (in *CarpCondition) DeepCopy() *CarpCondition { + if in == nil { + return nil + } + out := new(CarpCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CarpInfo) DeepCopyInto(out *CarpInfo) { + *out = *in + if in.C != nil { + in, out := &in.C, &out.C + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpInfo. +func (in *CarpInfo) DeepCopy() *CarpInfo { + if in == nil { + return nil + } + out := new(CarpInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CarpList) DeepCopyInto(out *CarpList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Carp, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpList. +func (in *CarpList) DeepCopy() *CarpList { + if in == nil { + return nil + } + out := new(CarpList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CarpList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CarpSpec) DeepCopyInto(out *CarpSpec) { + *out = *in + if in.TerminationGracePeriodSeconds != nil { + in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds + *out = new(int64) + **out = **in + } + if in.ActiveDeadlineSeconds != nil { + in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds + *out = new(int64) + **out = **in + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpSpec. +func (in *CarpSpec) DeepCopy() *CarpSpec { + if in == nil { + return nil + } + out := new(CarpSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CarpStatus) DeepCopyInto(out *CarpStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]CarpCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + *out = (*in).DeepCopy() + } + if in.Infos != nil { + in, out := &in.Infos, &out.Infos + *out = make([]CarpInfo, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarpStatus. +func (in *CarpStatus) DeepCopy() *CarpStatus { + if in == nil { + return nil + } + out := new(CarpStatus) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/converter.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/converter.go new file mode 100644 index 0000000000..76b76247c7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/converter.go @@ -0,0 +1,225 @@ +/* +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 conversion + +import ( + "fmt" + "reflect" +) + +type typePair struct { + source reflect.Type + dest reflect.Type +} + +type NameFunc func(t reflect.Type) string + +var DefaultNameFunc = func(t reflect.Type) string { return t.Name() } + +// ConversionFunc converts the object a into the object b, reusing arrays or objects +// or pointers if necessary. It should return an error if the object cannot be converted +// or if some data is invalid. If you do not wish a and b to share fields or nested +// objects, you must copy a before calling this function. +type ConversionFunc func(a, b interface{}, scope Scope) error + +// Converter knows how to convert one type to another. +type Converter struct { + // Map from the conversion pair to a function which can + // do the conversion. + conversionFuncs ConversionFuncs + generatedConversionFuncs ConversionFuncs + + // Set of conversions that should be treated as a no-op + ignoredUntypedConversions map[typePair]struct{} +} + +// NewConverter creates a new Converter object. +// Arg NameFunc is just for backward compatibility. +func NewConverter(NameFunc) *Converter { + c := &Converter{ + conversionFuncs: NewConversionFuncs(), + generatedConversionFuncs: NewConversionFuncs(), + ignoredUntypedConversions: make(map[typePair]struct{}), + } + c.RegisterUntypedConversionFunc( + (*[]byte)(nil), (*[]byte)(nil), + func(a, b interface{}, s Scope) error { + return Convert_Slice_byte_To_Slice_byte(a.(*[]byte), b.(*[]byte), s) + }, + ) + return c +} + +// WithConversions returns a Converter that is a copy of c but with the additional +// fns merged on top. +func (c *Converter) WithConversions(fns ConversionFuncs) *Converter { + copied := *c + copied.conversionFuncs = c.conversionFuncs.Merge(fns) + return &copied +} + +// DefaultMeta returns meta for a given type. +func (c *Converter) DefaultMeta(t reflect.Type) *Meta { + return &Meta{} +} + +// Convert_Slice_byte_To_Slice_byte prevents recursing into every byte +func Convert_Slice_byte_To_Slice_byte(in *[]byte, out *[]byte, s Scope) error { + if *in == nil { + *out = nil + return nil + } + *out = make([]byte, len(*in)) + copy(*out, *in) + return nil +} + +// Scope is passed to conversion funcs to allow them to continue an ongoing conversion. +// If multiple converters exist in the system, Scope will allow you to use the correct one +// from a conversion function--that is, the one your conversion function was called by. +type Scope interface { + // Call Convert to convert sub-objects. Note that if you call it with your own exact + // parameters, you'll run out of stack space before anything useful happens. + Convert(src, dest interface{}) error + + // Meta returns any information originally passed to Convert. + Meta() *Meta +} + +func NewConversionFuncs() ConversionFuncs { + return ConversionFuncs{ + untyped: make(map[typePair]ConversionFunc), + } +} + +type ConversionFuncs struct { + untyped map[typePair]ConversionFunc +} + +// AddUntyped adds the provided conversion function to the lookup table for the types that are +// supplied as a and b. a and b must be pointers or an error is returned. This method overwrites +// previously defined functions. +func (c ConversionFuncs) AddUntyped(a, b interface{}, fn ConversionFunc) error { + tA, tB := reflect.TypeOf(a), reflect.TypeOf(b) + if tA.Kind() != reflect.Pointer { + return fmt.Errorf("the type %T must be a pointer to register as an untyped conversion", a) + } + if tB.Kind() != reflect.Pointer { + return fmt.Errorf("the type %T must be a pointer to register as an untyped conversion", b) + } + c.untyped[typePair{tA, tB}] = fn + return nil +} + +// Merge returns a new ConversionFuncs that contains all conversions from +// both other and c, with other conversions taking precedence. +func (c ConversionFuncs) Merge(other ConversionFuncs) ConversionFuncs { + merged := NewConversionFuncs() + for k, v := range c.untyped { + merged.untyped[k] = v + } + for k, v := range other.untyped { + merged.untyped[k] = v + } + return merged +} + +// Meta is supplied by Scheme, when it calls Convert. +type Meta struct { + // Context is an optional field that callers may use to pass info to conversion functions. + Context interface{} +} + +// scope contains information about an ongoing conversion. +type scope struct { + converter *Converter + meta *Meta +} + +// Convert continues a conversion. +func (s *scope) Convert(src, dest interface{}) error { + return s.converter.Convert(src, dest, s.meta) +} + +// Meta returns the meta object that was originally passed to Convert. +func (s *scope) Meta() *Meta { + return s.meta +} + +// RegisterUntypedConversionFunc registers a function that converts between a and b by passing objects of those +// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce +// any other guarantee. +func (c *Converter) RegisterUntypedConversionFunc(a, b interface{}, fn ConversionFunc) error { + return c.conversionFuncs.AddUntyped(a, b, fn) +} + +// RegisterGeneratedUntypedConversionFunc registers a function that converts between a and b by passing objects of those +// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce +// any other guarantee. +func (c *Converter) RegisterGeneratedUntypedConversionFunc(a, b interface{}, fn ConversionFunc) error { + return c.generatedConversionFuncs.AddUntyped(a, b, fn) +} + +// RegisterIgnoredConversion registers a "no-op" for conversion, where any requested +// conversion between from and to is ignored. +func (c *Converter) RegisterIgnoredConversion(from, to interface{}) error { + typeFrom := reflect.TypeOf(from) + typeTo := reflect.TypeOf(to) + if typeFrom.Kind() != reflect.Pointer { + return fmt.Errorf("expected pointer arg for 'from' param 0, got: %v", typeFrom) + } + if typeTo.Kind() != reflect.Pointer { + return fmt.Errorf("expected pointer arg for 'to' param 1, got: %v", typeTo) + } + c.ignoredUntypedConversions[typePair{typeFrom, typeTo}] = struct{}{} + return nil +} + +// Convert will translate src to dest if it knows how. Both must be pointers. +// If no conversion func is registered and the default copying mechanism +// doesn't work on this type pair, an error will be returned. +// 'meta' is given to allow you to pass information to conversion functions, +// it is not used by Convert() other than storing it in the scope. +// Not safe for objects with cyclic references! +func (c *Converter) Convert(src, dest interface{}, meta *Meta) error { + pair := typePair{reflect.TypeOf(src), reflect.TypeOf(dest)} + scope := &scope{ + converter: c, + meta: meta, + } + + // ignore conversions of this type + if _, ok := c.ignoredUntypedConversions[pair]; ok { + return nil + } + if fn, ok := c.conversionFuncs.untyped[pair]; ok { + return fn(src, dest, scope) + } + if fn, ok := c.generatedConversionFuncs.untyped[pair]; ok { + return fn(src, dest, scope) + } + + dv, err := EnforcePtr(dest) + if err != nil { + return err + } + sv, err := EnforcePtr(src) + if err != nil { + return err + } + return fmt.Errorf("converting (%s) to (%s): unknown conversion", sv.Type(), dv.Type()) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/converter_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/converter_test.go new file mode 100644 index 0000000000..584068772f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/converter_test.go @@ -0,0 +1,287 @@ +/* +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 conversion + +import ( + "fmt" + "reflect" + "strconv" + "testing" +) + +func TestConverter_byteSlice(t *testing.T) { + c := NewConverter(nil) + src := []byte{1, 2, 3} + dest := []byte{} + err := c.Convert(&src, &dest, nil) + if err != nil { + t.Fatalf("expected no error") + } + if e, a := src, dest; !reflect.DeepEqual(e, a) { + t.Errorf("expected %#v, got %#v", e, a) + } +} + +func TestConverter_MismatchedTypes(t *testing.T) { + c := NewConverter(nil) + + convertFn := func(in *[]string, out *int, s Scope) error { + if str, err := strconv.Atoi((*in)[0]); err != nil { + return err + } else { + *out = str + return nil + } + } + if err := c.RegisterUntypedConversionFunc( + (*[]string)(nil), (*int)(nil), + func(a, b interface{}, s Scope) error { + return convertFn(a.(*[]string), b.(*int), s) + }, + ); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + src := []string{"5"} + var dest int + if err := c.Convert(&src, &dest, nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if e, a := 5, dest; e != a { + t.Errorf("expected %#v, got %#v", e, a) + } +} + +func TestConverter_CallsRegisteredFunctions(t *testing.T) { + type A struct { + Foo string + Baz int + } + type B struct { + Bar string + Baz int + } + type C struct{} + c := NewConverter(nil) + convertFn1 := func(in *A, out *B, s Scope) error { + out.Bar = in.Foo + out.Baz = in.Baz + return nil + } + if err := c.RegisterUntypedConversionFunc( + (*A)(nil), (*B)(nil), + func(a, b interface{}, s Scope) error { + return convertFn1(a.(*A), b.(*B), s) + }, + ); err != nil { + t.Fatalf("unexpected error %v", err) + } + convertFn2 := func(in *B, out *A, s Scope) error { + out.Foo = in.Bar + out.Baz = in.Baz + return nil + } + if err := c.RegisterUntypedConversionFunc( + (*B)(nil), (*A)(nil), + func(a, b interface{}, s Scope) error { + return convertFn2(a.(*B), b.(*A), s) + }, + ); err != nil { + t.Fatalf("unexpected error %v", err) + } + + x := A{"hello, intrepid test reader!", 3} + y := B{} + + if err := c.Convert(&x, &y, nil); err != nil { + t.Fatalf("unexpected error %v", err) + } + if e, a := x.Foo, y.Bar; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := x.Baz, y.Baz; e != a { + t.Errorf("expected %v, got %v", e, a) + } + + z := B{"all your test are belong to us", 42} + w := A{} + + if err := c.Convert(&z, &w, nil); err != nil { + t.Fatalf("unexpected error %v", err) + } + if e, a := z.Bar, w.Foo; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := z.Baz, w.Baz; e != a { + t.Errorf("expected %v, got %v", e, a) + } + + convertFn3 := func(in *A, out *C, s Scope) error { + return fmt.Errorf("C can't store an A, silly") + } + if err := c.RegisterUntypedConversionFunc( + (*A)(nil), (*C)(nil), + func(a, b interface{}, s Scope) error { + return convertFn3(a.(*A), b.(*C), s) + }, + ); err != nil { + t.Fatalf("unexpected error %v", err) + } + if err := c.Convert(&A{}, &C{}, nil); err == nil { + t.Errorf("unexpected non-error") + } +} + +func TestConverter_IgnoredConversion(t *testing.T) { + type A struct{} + type B struct{} + + count := 0 + c := NewConverter(nil) + convertFn := func(in *A, out *B, s Scope) error { + count++ + return nil + } + if err := c.RegisterUntypedConversionFunc( + (*A)(nil), (*B)(nil), + func(a, b interface{}, s Scope) error { + return convertFn(a.(*A), b.(*B), s) + }, + ); err != nil { + t.Fatalf("unexpected error %v", err) + } + if err := c.RegisterIgnoredConversion(&A{}, &B{}); err != nil { + t.Fatal(err) + } + a := A{} + b := B{} + if err := c.Convert(&a, &b, nil); err != nil { + t.Errorf("%v", err) + } + if count != 0 { + t.Errorf("unexpected number of conversion invocations") + } +} + +func TestConverter_GeneratedConversionOverridden(t *testing.T) { + type A struct{} + type B struct{} + c := NewConverter(nil) + convertFn1 := func(in *A, out *B, s Scope) error { + return nil + } + if err := c.RegisterUntypedConversionFunc( + (*A)(nil), (*B)(nil), + func(a, b interface{}, s Scope) error { + return convertFn1(a.(*A), b.(*B), s) + }, + ); err != nil { + t.Fatalf("unexpected error %v", err) + } + convertFn2 := func(in *A, out *B, s Scope) error { + return fmt.Errorf("generated function should be overridden") + } + if err := c.RegisterGeneratedUntypedConversionFunc( + (*A)(nil), (*B)(nil), + func(a, b interface{}, s Scope) error { + return convertFn2(a.(*A), b.(*B), s) + }, + ); err != nil { + t.Fatalf("unexpected error %v", err) + } + + a := A{} + b := B{} + if err := c.Convert(&a, &b, nil); err != nil { + t.Errorf("%v", err) + } +} + +func TestConverter_WithConversionOverridden(t *testing.T) { + type A struct{} + type B struct{} + c := NewConverter(nil) + convertFn1 := func(in *A, out *B, s Scope) error { + return fmt.Errorf("conversion function should be overridden") + } + if err := c.RegisterUntypedConversionFunc( + (*A)(nil), (*B)(nil), + func(a, b interface{}, s Scope) error { + return convertFn1(a.(*A), b.(*B), s) + }, + ); err != nil { + t.Fatalf("unexpected error %v", err) + } + convertFn2 := func(in *A, out *B, s Scope) error { + return fmt.Errorf("generated function should be overridden") + } + if err := c.RegisterGeneratedUntypedConversionFunc( + (*A)(nil), (*B)(nil), + func(a, b interface{}, s Scope) error { + return convertFn2(a.(*A), b.(*B), s) + }, + ); err != nil { + t.Fatalf("unexpected error %v", err) + } + + ext := NewConversionFuncs() + ext.AddUntyped( + (*A)(nil), (*B)(nil), + func(a, b interface{}, s Scope) error { + return nil + }, + ) + newc := c.WithConversions(ext) + + a := A{} + b := B{} + if err := c.Convert(&a, &b, nil); err == nil || err.Error() != "conversion function should be overridden" { + t.Errorf("unexpected error: %v", err) + } + if err := newc.Convert(&a, &b, nil); err != nil { + t.Errorf("%v", err) + } +} + +func TestConverter_meta(t *testing.T) { + type Foo struct{ A string } + type Bar struct{ A string } + c := NewConverter(nil) + checks := 0 + convertFn1 := func(in *Foo, out *Bar, s Scope) error { + if s.Meta() == nil { + t.Errorf("Meta did not get passed!") + } + checks++ + out.A = in.A + return nil + } + if err := c.RegisterUntypedConversionFunc( + (*Foo)(nil), (*Bar)(nil), + func(a, b interface{}, s Scope) error { + return convertFn1(a.(*Foo), b.(*Bar), s) + }, + ); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if err := c.Convert(&Foo{}, &Bar{}, &Meta{}); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if checks != 1 { + t.Errorf("Registered functions did not get called.") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/deep_equal.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/deep_equal.go new file mode 100644 index 0000000000..25b2923f22 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/deep_equal.go @@ -0,0 +1,47 @@ +/* +Copyright 2015 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 conversion + +import ( + "k8s.io/apimachinery/third_party/forked/golang/reflect" +) + +// The code for this type must be located in third_party, since it forks from +// go std lib. But for convenience, we expose the type here, too. +type Equalities struct { + reflect.Equalities +} + +// For convenience, panics on errors +func EqualitiesOrDie(funcs ...interface{}) Equalities { + e := Equalities{reflect.Equalities{}} + if err := e.AddFuncs(funcs...); err != nil { + panic(err) + } + return e +} + +// Performs a shallow copy of the equalities map +func (e Equalities) Copy() Equalities { + result := Equalities{reflect.Equalities{}} + + for key, value := range e.Equalities { + result.Equalities[key] = value + } + + return result +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/doc.go new file mode 100644 index 0000000000..0c46ef2d16 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/doc.go @@ -0,0 +1,24 @@ +/* +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 conversion provides go object versioning. +// +// Specifically, conversion provides a way for you to define multiple versions +// of the same object. You may write functions which implement conversion logic, +// but for the fields which did not change, copying is automated. This makes it +// easy to modify the structures you use in memory without affecting the format +// you store on disk or respond to in your external API calls. +package conversion diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/helper.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/helper.go new file mode 100644 index 0000000000..7fadd27a46 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/helper.go @@ -0,0 +1,39 @@ +/* +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 conversion + +import ( + "fmt" + "reflect" +) + +// EnforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value +// of the dereferenced pointer, ensuring that it is settable/addressable. +// Returns an error if this is not possible. +func EnforcePtr(obj interface{}) (reflect.Value, error) { + v := reflect.ValueOf(obj) + if v.Kind() != reflect.Pointer { + if v.Kind() == reflect.Invalid { + return reflect.Value{}, fmt.Errorf("expected pointer, but got invalid kind") + } + return reflect.Value{}, fmt.Errorf("expected pointer, but got %v type", v.Type()) + } + if v.IsNil() { + return reflect.Value{}, fmt.Errorf("expected pointer, but got nil") + } + return v.Elem(), nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/helper_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/helper_test.go new file mode 100644 index 0000000000..8c61a30a88 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/helper_test.go @@ -0,0 +1,38 @@ +/* +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 conversion + +import "testing" + +func TestInvalidPtrValueKind(t *testing.T) { + var simple interface{} + switch obj := simple.(type) { + default: + _, err := EnforcePtr(obj) + if err == nil { + t.Errorf("Expected error on invalid kind") + } + } +} + +func TestEnforceNilPtr(t *testing.T) { + var nilPtr *struct{} + _, err := EnforcePtr(nilPtr) + if err == nil { + t.Errorf("Expected error on nil pointer") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/convert.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/convert.go new file mode 100644 index 0000000000..5eb25e2e80 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/convert.go @@ -0,0 +1,194 @@ +/* +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 queryparams + +import ( + "fmt" + "net/url" + "reflect" + "strings" +) + +// Marshaler converts an object to a query parameter string representation +type Marshaler interface { + MarshalQueryParameter() (string, error) +} + +// Unmarshaler converts a string representation to an object +type Unmarshaler interface { + UnmarshalQueryParameter(string) error +} + +func jsonTag(field reflect.StructField) (string, bool) { + structTag, exists := field.Tag.Lookup("json") + if !exists || len(structTag) == 0 { + return "", false + } + parts := strings.Split(structTag, ",") + tag := parts[0] + if tag == "-" { + tag = "" + } + omitempty := false + parts = parts[1:] + for _, part := range parts { + if part == "omitempty" { + omitempty = true + break + } + } + return tag, omitempty +} + +func isPointerKind(kind reflect.Kind) bool { + return kind == reflect.Pointer +} + +func isStructKind(kind reflect.Kind) bool { + return kind == reflect.Struct +} + +func isValueKind(kind reflect.Kind) bool { + switch kind { + case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, + reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, + reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, + reflect.Float64, reflect.Complex64, reflect.Complex128: + return true + default: + return false + } +} + +func zeroValue(value reflect.Value) bool { + return reflect.DeepEqual(reflect.Zero(value.Type()).Interface(), value.Interface()) +} + +func customMarshalValue(value reflect.Value) (reflect.Value, bool) { + // Return unless we implement a custom query marshaler + if !value.CanInterface() { + return reflect.Value{}, false + } + + marshaler, ok := value.Interface().(Marshaler) + if !ok { + if !isPointerKind(value.Kind()) && value.CanAddr() { + marshaler, ok = value.Addr().Interface().(Marshaler) + if !ok { + return reflect.Value{}, false + } + } else { + return reflect.Value{}, false + } + } + + // Don't invoke functions on nil pointers + // If the type implements MarshalQueryParameter, AND the tag is not omitempty, AND the value is a nil pointer, "" seems like a reasonable response + if isPointerKind(value.Kind()) && zeroValue(value) { + return reflect.ValueOf(""), true + } + + // Get the custom marshalled value + v, err := marshaler.MarshalQueryParameter() + if err != nil { + return reflect.Value{}, false + } + return reflect.ValueOf(v), true +} + +func addParam(values url.Values, tag string, omitempty bool, value reflect.Value) { + if omitempty && zeroValue(value) { + return + } + val := "" + iValue := fmt.Sprintf("%v", value.Interface()) + + if iValue != "" { + val = iValue + } + values.Add(tag, val) +} + +func addListOfParams(values url.Values, tag string, omitempty bool, list reflect.Value) { + for i := 0; i < list.Len(); i++ { + addParam(values, tag, omitempty, list.Index(i)) + } +} + +// Convert takes an object and converts it to a url.Values object using JSON tags as +// parameter names. Only top-level simple values, arrays, and slices are serialized. +// Embedded structs, maps, etc. will not be serialized. +func Convert(obj interface{}) (url.Values, error) { + result := url.Values{} + if obj == nil { + return result, nil + } + var sv reflect.Value + switch reflect.TypeOf(obj).Kind() { + case reflect.Pointer, reflect.Interface: + sv = reflect.ValueOf(obj).Elem() + default: + return nil, fmt.Errorf("expecting a pointer or interface") + } + st := sv.Type() + if !isStructKind(st.Kind()) { + return nil, fmt.Errorf("expecting a pointer to a struct") + } + + // Check all object fields + convertStruct(result, st, sv) + + return result, nil +} + +func convertStruct(result url.Values, st reflect.Type, sv reflect.Value) { + for i := 0; i < st.NumField(); i++ { + field := sv.Field(i) + tag, omitempty := jsonTag(st.Field(i)) + if len(tag) == 0 { + continue + } + ft := field.Type() + + kind := ft.Kind() + if isPointerKind(kind) { + ft = ft.Elem() + kind = ft.Kind() + if !field.IsNil() { + field = reflect.Indirect(field) + // If the field is non-nil, it should be added to params + // and the omitempty should be overwite to false + omitempty = false + } + } + + switch { + case isValueKind(kind): + addParam(result, tag, omitempty, field) + case kind == reflect.Array || kind == reflect.Slice: + if isValueKind(ft.Elem().Kind()) { + addListOfParams(result, tag, omitempty, field) + } + case isStructKind(kind) && !(zeroValue(field) && omitempty): + if marshalValue, ok := customMarshalValue(field); ok { + addParam(result, tag, omitempty, marshalValue) + } else { + convertStruct(result, ft, field) + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/convert_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/convert_test.go new file mode 100644 index 0000000000..a9b9385ced --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/convert_test.go @@ -0,0 +1,216 @@ +/* +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 queryparams_test + +import ( + "net/url" + "reflect" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion/queryparams" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" +) + +type namedString string +type namedBool bool + +type bar struct { + Float1 float32 `json:"float1"` + Float2 float64 `json:"float2"` + Int1 int64 `json:"int1,omitempty"` + Int2 int32 `json:"int2,omitempty"` + Int3 int16 `json:"int3,omitempty"` + Str1 string `json:"str1,omitempty"` + Ignored int + Ignored2 string +} + +func (obj *bar) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +type foo struct { + Str string `json:"str"` + Integer int `json:"integer,omitempty"` + Slice []string `json:"slice,omitempty"` + Boolean bool `json:"boolean,omitempty"` + NamedStr namedString `json:"namedStr,omitempty"` + NamedBool namedBool `json:"namedBool,omitempty"` + Foobar bar `json:"foobar,omitempty"` + Testmap map[string]string `json:"testmap,omitempty"` +} + +func (obj *foo) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +type baz struct { + Ptr *int `json:"ptr"` + Bptr *bool `json:"bptr,omitempty"` +} + +func (obj *baz) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +// childStructs tests some of the types we serialize to query params for log API calls +// notably, the nested time struct +type childStructs struct { + Container string `json:"container,omitempty"` + Follow bool `json:"follow,omitempty"` + Previous bool `json:"previous,omitempty"` + SinceSeconds *int64 `json:"sinceSeconds,omitempty"` + TailLines *int64 `json:"tailLines,omitempty"` + SinceTime *metav1.Time `json:"sinceTime,omitempty"` + EmptyTime *metav1.Time `json:"emptyTime"` + NonPointerTime metav1.Time `json:"nonPointerTime"` +} + +func (obj *childStructs) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +func validateResult(t *testing.T, input interface{}, actual, expected url.Values) { + local := url.Values{} + for k, v := range expected { + local[k] = v + } + for k, v := range actual { + if ev, ok := local[k]; !ok || !reflect.DeepEqual(ev, v) { + if !ok { + t.Errorf("%#v: actual value key %s not found in expected map", input, k) + } else { + t.Errorf("%#v: values don't match: actual: %#v, expected: %#v", input, v, ev) + } + } + delete(local, k) + } + if len(local) > 0 { + t.Errorf("%#v: expected map has keys that were not found in actual map: %#v", input, local) + } +} + +func TestConvert(t *testing.T) { + sinceSeconds := int64(123) + tailLines := int64(0) + sinceTime := metav1.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC) + + tests := []struct { + input interface{} + expected url.Values + }{ + { + input: &foo{ + Str: "hello", + }, + expected: url.Values{"str": {"hello"}}, + }, + { + input: &foo{ + Str: "test string", + Slice: []string{"one", "two", "three"}, + Integer: 234, + Boolean: true, + }, + expected: url.Values{"str": {"test string"}, "slice": {"one", "two", "three"}, "integer": {"234"}, "boolean": {"true"}}, + }, + { + input: &foo{ + Str: "named types", + NamedStr: "value1", + NamedBool: true, + }, + expected: url.Values{"str": {"named types"}, "namedStr": {"value1"}, "namedBool": {"true"}}, + }, + { + input: &foo{ + Str: "don't ignore embedded struct", + Foobar: bar{ + Float1: 5.0, + }, + }, + expected: url.Values{"str": {"don't ignore embedded struct"}, "float1": {"5"}, "float2": {"0"}}, + }, + { + // Ignore untagged fields + input: &bar{ + Float1: 23.5, + Float2: 100.7, + Int1: 1, + Int2: 2, + Int3: 3, + Ignored: 1, + Ignored2: "ignored", + }, + expected: url.Values{"float1": {"23.5"}, "float2": {"100.7"}, "int1": {"1"}, "int2": {"2"}, "int3": {"3"}}, + }, + { + // include fields that are not tagged omitempty + input: &foo{ + NamedStr: "named str", + }, + expected: url.Values{"str": {""}, "namedStr": {"named str"}}, + }, + { + input: &baz{ + Ptr: ptr.To(5), + Bptr: ptr.To(true), + }, + expected: url.Values{"ptr": {"5"}, "bptr": {"true"}}, + }, + { + input: &baz{ + Bptr: ptr.To(true), + }, + expected: url.Values{"ptr": {""}, "bptr": {"true"}}, + }, + { + input: &baz{ + Ptr: ptr.To(5), + }, + expected: url.Values{"ptr": {"5"}}, + }, + { + input: &childStructs{ + Container: "mycontainer", + Follow: true, + Previous: true, + SinceSeconds: &sinceSeconds, + TailLines: nil, + SinceTime: &sinceTime, // test a custom marshaller + EmptyTime: nil, // test a nil custom marshaller without omitempty + NonPointerTime: sinceTime, + }, + expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "sinceTime": {"2000-01-01T12:34:56Z"}, "emptyTime": {""}, "nonPointerTime": {"2000-01-01T12:34:56Z"}}, + }, + { + input: &childStructs{ + Container: "mycontainer", + Follow: true, + Previous: true, + SinceSeconds: &sinceSeconds, + TailLines: &tailLines, + SinceTime: nil, // test a nil custom marshaller with omitempty + NonPointerTime: sinceTime, + }, + expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "tailLines": {"0"}, "emptyTime": {""}, "nonPointerTime": {"2000-01-01T12:34:56Z"}}, + }, + } + + for _, test := range tests { + result, err := queryparams.Convert(test.input) + if err != nil { + t.Errorf("Unexpected error while converting %#v: %v", test.input, err) + } + validateResult(t, test.input, result, test.expected) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/doc.go new file mode 100644 index 0000000000..4c1002a4c1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/conversion/queryparams/doc.go @@ -0,0 +1,19 @@ +/* +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 queryparams provides conversion from versioned +// runtime objects to URL query values +package queryparams diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/doc.go new file mode 100644 index 0000000000..49059e2635 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 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 fields implements a simple field system, parsing and matching +// selectors with sets of fields. +package fields diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/fields.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/fields.go new file mode 100644 index 0000000000..623b27e957 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/fields.go @@ -0,0 +1,62 @@ +/* +Copyright 2015 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 fields + +import ( + "sort" + "strings" +) + +// Fields allows you to present fields independently from their storage. +type Fields interface { + // Has returns whether the provided field exists. + Has(field string) (exists bool) + + // Get returns the value for the provided field. + Get(field string) (value string) +} + +// Set is a map of field:value. It implements Fields. +type Set map[string]string + +// String returns all fields listed as a human readable string. +// Conveniently, exactly the format that ParseSelector takes. +func (ls Set) String() string { + selector := make([]string, 0, len(ls)) + for key, value := range ls { + selector = append(selector, key+"="+value) + } + // Sort for determinism. + sort.StringSlice(selector).Sort() + return strings.Join(selector, ",") +} + +// Has returns whether the provided field exists in the map. +func (ls Set) Has(field string) bool { + _, exists := ls[field] + return exists +} + +// Get returns the value in the map for the provided field. +func (ls Set) Get(field string) string { + return ls[field] +} + +// AsSelector converts fields into a selectors. +func (ls Set) AsSelector() Selector { + return SelectorFromSet(ls) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/fields_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/fields_test.go new file mode 100644 index 0000000000..6965be6870 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/fields_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2015 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 fields + +import ( + "testing" +) + +func matches(t *testing.T, ls Set, want string) { + if ls.String() != want { + t.Errorf("Expected '%s', but got '%s'", want, ls.String()) + } +} + +func TestSetString(t *testing.T) { + matches(t, Set{"x": "y"}, "x=y") + matches(t, Set{"foo": "bar"}, "foo=bar") + matches(t, Set{"foo": "bar", "baz": "qup"}, "baz=qup,foo=bar") +} + +func TestFieldHas(t *testing.T) { + fieldHasTests := []struct { + Ls Fields + Key string + Has bool + }{ + {Set{"x": "y"}, "x", true}, + {Set{"x": ""}, "x", true}, + {Set{"x": "y"}, "foo", false}, + } + for _, lh := range fieldHasTests { + if has := lh.Ls.Has(lh.Key); has != lh.Has { + t.Errorf("%#v.Has(%#v) => %v, expected %v", lh.Ls, lh.Key, has, lh.Has) + } + } +} + +func TestFieldGet(t *testing.T) { + ls := Set{"x": "y"} + if ls.Get("x") != "y" { + t.Errorf("Set.Get is broken") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/requirements.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/requirements.go new file mode 100644 index 0000000000..70d94ded88 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/requirements.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 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 fields + +import "k8s.io/apimachinery/pkg/selection" + +// Requirements is AND of all requirements. +type Requirements []Requirement + +// Requirement contains a field, a value, and an operator that relates the field and value. +// This is currently for reading internal selection information of field selector. +type Requirement struct { + Operator selection.Operator + Field string + Value string +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/selector.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/selector.go new file mode 100644 index 0000000000..a9e204976a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/selector.go @@ -0,0 +1,478 @@ +/* +Copyright 2015 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 fields + +import ( + "bytes" + "fmt" + "sort" + "strings" + + "k8s.io/apimachinery/pkg/selection" +) + +// Selector represents a field selector. +type Selector interface { + // Matches returns true if this selector matches the given set of fields. + Matches(Fields) bool + + // Empty returns true if this selector does not restrict the selection space. + Empty() bool + + // RequiresExactMatch allows a caller to introspect whether a given selector + // requires a single specific field to be set, and if so returns the value it + // requires. + RequiresExactMatch(field string) (value string, found bool) + + // Transform returns a new copy of the selector after TransformFunc has been + // applied to the entire selector, or an error if fn returns an error. + // If for a given requirement both field and value are transformed to empty + // string, the requirement is skipped. + Transform(fn TransformFunc) (Selector, error) + + // Requirements converts this interface to Requirements to expose + // more detailed selection information. + Requirements() Requirements + + // String returns a human readable string that represents this selector. + String() string + + // Make a deep copy of the selector. + DeepCopySelector() Selector +} + +type nothingSelector struct{} + +func (n nothingSelector) Matches(_ Fields) bool { return false } +func (n nothingSelector) Empty() bool { return false } +func (n nothingSelector) String() string { return "" } +func (n nothingSelector) Requirements() Requirements { return nil } +func (n nothingSelector) DeepCopySelector() Selector { return n } +func (n nothingSelector) RequiresExactMatch(field string) (value string, found bool) { + return "", false +} +func (n nothingSelector) Transform(fn TransformFunc) (Selector, error) { return n, nil } + +// Nothing returns a selector that matches no fields +func Nothing() Selector { + return nothingSelector{} +} + +// Everything returns a selector that matches all fields. +func Everything() Selector { + return andTerm{} +} + +type hasTerm struct { + field, value string +} + +func (t *hasTerm) Matches(ls Fields) bool { + return ls.Get(t.field) == t.value +} + +func (t *hasTerm) Empty() bool { + return false +} + +func (t *hasTerm) RequiresExactMatch(field string) (value string, found bool) { + if t.field == field { + return t.value, true + } + return "", false +} + +func (t *hasTerm) Transform(fn TransformFunc) (Selector, error) { + field, value, err := fn(t.field, t.value) + if err != nil { + return nil, err + } + if len(field) == 0 && len(value) == 0 { + return Everything(), nil + } + return &hasTerm{field, value}, nil +} + +func (t *hasTerm) Requirements() Requirements { + return []Requirement{{ + Field: t.field, + Operator: selection.Equals, + Value: t.value, + }} +} + +func (t *hasTerm) String() string { + return fmt.Sprintf("%v=%v", t.field, EscapeValue(t.value)) +} + +func (t *hasTerm) DeepCopySelector() Selector { + if t == nil { + return nil + } + out := new(hasTerm) + *out = *t + return out +} + +type notHasTerm struct { + field, value string +} + +func (t *notHasTerm) Matches(ls Fields) bool { + return ls.Get(t.field) != t.value +} + +func (t *notHasTerm) Empty() bool { + return false +} + +func (t *notHasTerm) RequiresExactMatch(field string) (value string, found bool) { + return "", false +} + +func (t *notHasTerm) Transform(fn TransformFunc) (Selector, error) { + field, value, err := fn(t.field, t.value) + if err != nil { + return nil, err + } + if len(field) == 0 && len(value) == 0 { + return Everything(), nil + } + return ¬HasTerm{field, value}, nil +} + +func (t *notHasTerm) Requirements() Requirements { + return []Requirement{{ + Field: t.field, + Operator: selection.NotEquals, + Value: t.value, + }} +} + +func (t *notHasTerm) String() string { + return fmt.Sprintf("%v!=%v", t.field, EscapeValue(t.value)) +} + +func (t *notHasTerm) DeepCopySelector() Selector { + if t == nil { + return nil + } + out := new(notHasTerm) + *out = *t + return out +} + +type andTerm []Selector + +func (t andTerm) Matches(ls Fields) bool { + for _, q := range t { + if !q.Matches(ls) { + return false + } + } + return true +} + +func (t andTerm) Empty() bool { + if t == nil { + return true + } + if len([]Selector(t)) == 0 { + return true + } + for i := range t { + if !t[i].Empty() { + return false + } + } + return true +} + +func (t andTerm) RequiresExactMatch(field string) (string, bool) { + if t == nil || len([]Selector(t)) == 0 { + return "", false + } + for i := range t { + if value, found := t[i].RequiresExactMatch(field); found { + return value, found + } + } + return "", false +} + +func (t andTerm) Transform(fn TransformFunc) (Selector, error) { + next := make([]Selector, 0, len([]Selector(t))) + for _, s := range []Selector(t) { + n, err := s.Transform(fn) + if err != nil { + return nil, err + } + if !n.Empty() { + next = append(next, n) + } + } + return andTerm(next), nil +} + +func (t andTerm) Requirements() Requirements { + reqs := make([]Requirement, 0, len(t)) + for _, s := range []Selector(t) { + rs := s.Requirements() + reqs = append(reqs, rs...) + } + return reqs +} + +func (t andTerm) String() string { + var terms []string + for _, q := range t { + terms = append(terms, q.String()) + } + return strings.Join(terms, ",") +} + +func (t andTerm) DeepCopySelector() Selector { + if t == nil { + return nil + } + out := make([]Selector, len(t)) + for i := range t { + out[i] = t[i].DeepCopySelector() + } + return andTerm(out) +} + +// SelectorFromSet returns a Selector which will match exactly the given Set. A +// nil Set is considered equivalent to Everything(). +func SelectorFromSet(ls Set) Selector { + if ls == nil { + return Everything() + } + items := make([]Selector, 0, len(ls)) + for field, value := range ls { + items = append(items, &hasTerm{field: field, value: value}) + } + if len(items) == 1 { + return items[0] + } + return andTerm(items) +} + +// valueEscaper prefixes \,= characters with a backslash +var valueEscaper = strings.NewReplacer( + // escape \ characters + `\`, `\\`, + // then escape , and = characters to allow unambiguous parsing of the value in a fieldSelector + `,`, `\,`, + `=`, `\=`, +) + +// EscapeValue escapes an arbitrary literal string for use as a fieldSelector value +func EscapeValue(s string) string { + return valueEscaper.Replace(s) +} + +// InvalidEscapeSequence indicates an error occurred unescaping a field selector +type InvalidEscapeSequence struct { + sequence string +} + +func (i InvalidEscapeSequence) Error() string { + return fmt.Sprintf("invalid field selector: invalid escape sequence: %s", i.sequence) +} + +// UnescapedRune indicates an error occurred unescaping a field selector +type UnescapedRune struct { + r rune +} + +func (i UnescapedRune) Error() string { + return fmt.Sprintf("invalid field selector: unescaped character in value: %v", i.r) +} + +// UnescapeValue unescapes a fieldSelector value and returns the original literal value. +// May return the original string if it contains no escaped or special characters. +func UnescapeValue(s string) (string, error) { + // if there's no escaping or special characters, just return to avoid allocation + if !strings.ContainsAny(s, `\,=`) { + return s, nil + } + + v := bytes.NewBuffer(make([]byte, 0, len(s))) + inSlash := false + for _, c := range s { + if inSlash { + switch c { + case '\\', ',', '=': + // omit the \ for recognized escape sequences + v.WriteRune(c) + default: + // error on unrecognized escape sequences + return "", InvalidEscapeSequence{sequence: string([]rune{'\\', c})} + } + inSlash = false + continue + } + + switch c { + case '\\': + inSlash = true + case ',', '=': + // unescaped , and = characters are not allowed in field selector values + return "", UnescapedRune{r: c} + default: + v.WriteRune(c) + } + } + + // Ending with a single backslash is an invalid sequence + if inSlash { + return "", InvalidEscapeSequence{sequence: "\\"} + } + + return v.String(), nil +} + +// ParseSelectorOrDie takes a string representing a selector and returns an +// object suitable for matching, or panic when an error occur. +func ParseSelectorOrDie(s string) Selector { + selector, err := ParseSelector(s) + if err != nil { + panic(err) + } + return selector +} + +// ParseSelector takes a string representing a selector and returns an +// object suitable for matching, or an error. +func ParseSelector(selector string) (Selector, error) { + return parseSelector(selector, + func(lhs, rhs string) (newLhs, newRhs string, err error) { + return lhs, rhs, nil + }) +} + +// ParseAndTransformSelector parses the selector and runs them through the given TransformFunc. +func ParseAndTransformSelector(selector string, fn TransformFunc) (Selector, error) { + return parseSelector(selector, fn) +} + +// TransformFunc transforms selectors. +type TransformFunc func(field, value string) (newField, newValue string, err error) + +// splitTerms returns the comma-separated terms contained in the given fieldSelector. +// Backslash-escaped commas are treated as data instead of delimiters, and are included in the returned terms, with the leading backslash preserved. +func splitTerms(fieldSelector string) []string { + if len(fieldSelector) == 0 { + return nil + } + + terms := make([]string, 0, 1) + startIndex := 0 + inSlash := false + for i, c := range fieldSelector { + switch { + case inSlash: + inSlash = false + case c == '\\': + inSlash = true + case c == ',': + terms = append(terms, fieldSelector[startIndex:i]) + startIndex = i + 1 + } + } + + terms = append(terms, fieldSelector[startIndex:]) + + return terms +} + +const ( + notEqualOperator = "!=" + doubleEqualOperator = "==" + equalOperator = "=" +) + +// termOperators holds the recognized operators supported in fieldSelectors. +// doubleEqualOperator and equal are equivalent, but doubleEqualOperator is checked first +// to avoid leaving a leading = character on the rhs value. +var termOperators = []string{notEqualOperator, doubleEqualOperator, equalOperator} + +// splitTerm returns the lhs, operator, and rhs parsed from the given term, along with an indicator of whether the parse was successful. +// no escaping of special characters is supported in the lhs value, so the first occurrence of a recognized operator is used as the split point. +// the literal rhs is returned, and the caller is responsible for applying any desired unescaping. +func splitTerm(term string) (lhs, op, rhs string, ok bool) { + for i := range term { + remaining := term[i:] + for _, op := range termOperators { + if strings.HasPrefix(remaining, op) { + return term[0:i], op, term[i+len(op):], true + } + } + } + return "", "", "", false +} + +func parseSelector(selector string, fn TransformFunc) (Selector, error) { + parts := splitTerms(selector) + sort.StringSlice(parts).Sort() + var items []Selector + for _, part := range parts { + if part == "" { + continue + } + lhs, op, rhs, ok := splitTerm(part) + if !ok { + return nil, fmt.Errorf("invalid selector: '%s'; can't understand '%s'", selector, part) + } + unescapedRHS, err := UnescapeValue(rhs) + if err != nil { + return nil, err + } + switch op { + case notEqualOperator: + items = append(items, ¬HasTerm{field: lhs, value: unescapedRHS}) + case doubleEqualOperator: + items = append(items, &hasTerm{field: lhs, value: unescapedRHS}) + case equalOperator: + items = append(items, &hasTerm{field: lhs, value: unescapedRHS}) + default: + return nil, fmt.Errorf("invalid selector: '%s'; can't understand '%s'", selector, part) + } + } + if len(items) == 1 { + return items[0].Transform(fn) + } + return andTerm(items).Transform(fn) +} + +// OneTermEqualSelector returns an object that matches objects where one field/field equals one value. +// Cannot return an error. +func OneTermEqualSelector(k, v string) Selector { + return &hasTerm{field: k, value: v} +} + +// OneTermNotEqualSelector returns an object that matches objects where one field/field does not equal one value. +// Cannot return an error. +func OneTermNotEqualSelector(k, v string) Selector { + return ¬HasTerm{field: k, value: v} +} + +// AndSelectors creates a selector that is the logical AND of all the given selectors +func AndSelectors(selectors ...Selector) Selector { + return andTerm(selectors) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/selector_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/selector_test.go new file mode 100644 index 0000000000..0aa66935cc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/fields/selector_test.go @@ -0,0 +1,397 @@ +/* +Copyright 2015 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 fields + +import ( + "reflect" + "testing" +) + +func TestSplitTerms(t *testing.T) { + testcases := map[string][]string{ + // Simple selectors + `a`: {`a`}, + `a=avalue`: {`a=avalue`}, + `a=avalue,b=bvalue`: {`a=avalue`, `b=bvalue`}, + `a=avalue,b==bvalue,c!=cvalue`: {`a=avalue`, `b==bvalue`, `c!=cvalue`}, + + // Empty terms + ``: nil, + `a=a,`: {`a=a`, ``}, + `,a=a`: {``, `a=a`}, + + // Escaped values + `k=\,,k2=v2`: {`k=\,`, `k2=v2`}, // escaped comma in value + `k=\\,k2=v2`: {`k=\\`, `k2=v2`}, // escaped backslash, unescaped comma + `k=\\\,,k2=v2`: {`k=\\\,`, `k2=v2`}, // escaped backslash and comma + `k=\a\b\`: {`k=\a\b\`}, // non-escape sequences + `k=\`: {`k=\`}, // orphan backslash + + // Multi-byte + `함=수,목=록`: {`함=수`, `목=록`}, + } + + for selector, expectedTerms := range testcases { + if terms := splitTerms(selector); !reflect.DeepEqual(terms, expectedTerms) { + t.Errorf("splitSelectors(`%s`): Expected\n%#v\ngot\n%#v", selector, expectedTerms, terms) + } + } +} + +func TestSplitTerm(t *testing.T) { + testcases := map[string]struct { + lhs string + op string + rhs string + ok bool + }{ + // Simple terms + `a=value`: {lhs: `a`, op: `=`, rhs: `value`, ok: true}, + `b==value`: {lhs: `b`, op: `==`, rhs: `value`, ok: true}, + `c!=value`: {lhs: `c`, op: `!=`, rhs: `value`, ok: true}, + + // Empty or invalid terms + ``: {lhs: ``, op: ``, rhs: ``, ok: false}, + `a`: {lhs: ``, op: ``, rhs: ``, ok: false}, + + // Escaped values + `k=\,`: {lhs: `k`, op: `=`, rhs: `\,`, ok: true}, + `k=\=`: {lhs: `k`, op: `=`, rhs: `\=`, ok: true}, + `k=\\\a\b\=\,\`: {lhs: `k`, op: `=`, rhs: `\\\a\b\=\,\`, ok: true}, + + // Multi-byte + `함=수`: {lhs: `함`, op: `=`, rhs: `수`, ok: true}, + } + + for term, expected := range testcases { + lhs, op, rhs, ok := splitTerm(term) + if lhs != expected.lhs || op != expected.op || rhs != expected.rhs || ok != expected.ok { + t.Errorf( + "splitTerm(`%s`): Expected\n%s,%s,%s,%v\nGot\n%s,%s,%s,%v", + term, + expected.lhs, expected.op, expected.rhs, expected.ok, + lhs, op, rhs, ok, + ) + } + } +} + +func TestEscapeValue(t *testing.T) { + // map values to their normalized escaped values + testcases := map[string]string{ + ``: ``, + `a`: `a`, + `=`: `\=`, + `,`: `\,`, + `\`: `\\`, + `\=\,\`: `\\\=\\\,\\`, + } + + for unescapedValue, escapedValue := range testcases { + actualEscaped := EscapeValue(unescapedValue) + if actualEscaped != escapedValue { + t.Errorf("EscapeValue(%s): expected %s, got %s", unescapedValue, escapedValue, actualEscaped) + } + + actualUnescaped, err := UnescapeValue(escapedValue) + if err != nil { + t.Errorf("UnescapeValue(%s): unexpected error %v", escapedValue, err) + } + if actualUnescaped != unescapedValue { + t.Errorf("UnescapeValue(%s): expected %s, got %s", escapedValue, unescapedValue, actualUnescaped) + } + } + + // test invalid escape sequences + invalidTestcases := []string{ + `\`, // orphan slash is invalid + `\\\`, // orphan slash is invalid + `\a`, // unrecognized escape sequence is invalid + } + for _, invalidValue := range invalidTestcases { + _, err := UnescapeValue(invalidValue) + if _, ok := err.(InvalidEscapeSequence); !ok || err == nil { + t.Errorf("UnescapeValue(%s): expected invalid escape sequence error, got %#v", invalidValue, err) + } + } +} + +func TestSelectorParse(t *testing.T) { + testGoodStrings := []string{ + "x=a,y=b,z=c", + "", + "x!=a,y=b", + `x=a||y\=b`, + `x=a\=\=b`, + } + testBadStrings := []string{ + "x=a||y=b", + "x==a==b", + "x=a,b", + "x in (a)", + "x in (a,b,c)", + "x", + } + for _, test := range testGoodStrings { + lq, err := ParseSelector(test) + if err != nil { + t.Errorf("%v: error %v (%#v)\n", test, err, err) + } + if test != lq.String() { + t.Errorf("%v restring gave: %v\n", test, lq.String()) + } + } + for _, test := range testBadStrings { + _, err := ParseSelector(test) + if err == nil { + t.Errorf("%v: did not get expected error\n", test) + } + } +} + +func TestDeterministicParse(t *testing.T) { + s1, err := ParseSelector("x=a,a=x") + s2, err2 := ParseSelector("a=x,x=a") + if err != nil || err2 != nil { + t.Errorf("Unexpected parse error") + } + if s1.String() != s2.String() { + t.Errorf("Non-deterministic parse") + } +} + +func expectMatch(t *testing.T, selector string, ls Set) { + lq, err := ParseSelector(selector) + if err != nil { + t.Errorf("Unable to parse %v as a selector\n", selector) + return + } + if !lq.Matches(ls) { + t.Errorf("Wanted %s to match '%s', but it did not.\n", selector, ls) + } +} + +func expectNoMatch(t *testing.T, selector string, ls Set) { + lq, err := ParseSelector(selector) + if err != nil { + t.Errorf("Unable to parse %v as a selector\n", selector) + return + } + if lq.Matches(ls) { + t.Errorf("Wanted '%s' to not match '%s', but it did.", selector, ls) + } +} + +func TestEverything(t *testing.T) { + if !Everything().Matches(Set{"x": "y"}) { + t.Errorf("Nil selector didn't match") + } + if !Everything().Empty() { + t.Errorf("Everything was not empty") + } +} + +func TestSelectorMatches(t *testing.T) { + expectMatch(t, "", Set{"x": "y"}) + expectMatch(t, "x=y", Set{"x": "y"}) + expectMatch(t, "x=y,z=w", Set{"x": "y", "z": "w"}) + expectMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "a"}) + expectMatch(t, "notin=in", Set{"notin": "in"}) // in and notin in exactMatch + expectNoMatch(t, "x=y", Set{"x": "z"}) + expectNoMatch(t, "x=y,z=w", Set{"x": "w", "z": "w"}) + expectNoMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "w"}) + + fieldset := Set{ + "foo": "bar", + "baz": "blah", + "complex": `=value\,\`, + } + expectMatch(t, "foo=bar", fieldset) + expectMatch(t, "baz=blah", fieldset) + expectMatch(t, "foo=bar,baz=blah", fieldset) + expectMatch(t, `foo=bar,baz=blah,complex=\=value\\\,\\`, fieldset) + expectNoMatch(t, "foo=blah", fieldset) + expectNoMatch(t, "baz=bar", fieldset) + expectNoMatch(t, "foo=bar,foobar=bar,baz=blah", fieldset) +} + +func TestOneTermEqualSelector(t *testing.T) { + if !OneTermEqualSelector("x", "y").Matches(Set{"x": "y"}) { + t.Errorf("No match when match expected.") + } + if OneTermEqualSelector("x", "y").Matches(Set{"x": "z"}) { + t.Errorf("Match when none expected.") + } +} + +func expectMatchDirect(t *testing.T, selector, ls Set) { + if !SelectorFromSet(selector).Matches(ls) { + t.Errorf("Wanted %s to match '%s', but it did not.\n", selector, ls) + } +} + +func expectNoMatchDirect(t *testing.T, selector, ls Set) { + if SelectorFromSet(selector).Matches(ls) { + t.Errorf("Wanted '%s' to not match '%s', but it did.", selector, ls) + } +} + +func TestSetMatches(t *testing.T) { + labelset := Set{ + "foo": "bar", + "baz": "blah", + } + expectMatchDirect(t, Set{}, labelset) + expectMatchDirect(t, Set{"foo": "bar"}, labelset) + expectMatchDirect(t, Set{"baz": "blah"}, labelset) + expectMatchDirect(t, Set{"foo": "bar", "baz": "blah"}, labelset) + expectNoMatchDirect(t, Set{"foo": "=blah"}, labelset) + expectNoMatchDirect(t, Set{"baz": "=bar"}, labelset) + expectNoMatchDirect(t, Set{"foo": "=bar", "foobar": "bar", "baz": "blah"}, labelset) +} + +func TestNilMapIsValid(t *testing.T) { + selector := Set(nil).AsSelector() + if selector == nil { + t.Errorf("Selector for nil set should be Everything") + } + if !selector.Empty() { + t.Errorf("Selector for nil set should be Empty") + } +} + +func TestSetIsEmpty(t *testing.T) { + if !(Set{}).AsSelector().Empty() { + t.Errorf("Empty set should be empty") + } + if !(andTerm(nil)).Empty() { + t.Errorf("Nil andTerm should be empty") + } + if (&hasTerm{}).Empty() { + t.Errorf("hasTerm should not be empty") + } + if (¬HasTerm{}).Empty() { + t.Errorf("notHasTerm should not be empty") + } + if !(andTerm{andTerm{}}).Empty() { + t.Errorf("Nested andTerm should be empty") + } + if (andTerm{&hasTerm{"a", "b"}}).Empty() { + t.Errorf("Nested andTerm should not be empty") + } +} + +func TestRequiresExactMatch(t *testing.T) { + testCases := map[string]struct { + S Selector + Label string + Value string + Found bool + }{ + "empty set": {Set{}.AsSelector(), "test", "", false}, + "empty hasTerm": {&hasTerm{}, "test", "", false}, + "skipped hasTerm": {&hasTerm{"a", "b"}, "test", "", false}, + "valid hasTerm": {&hasTerm{"test", "b"}, "test", "b", true}, + "valid hasTerm no value": {&hasTerm{"test", ""}, "test", "", true}, + "valid notHasTerm": {¬HasTerm{"test", "b"}, "test", "", false}, + "valid notHasTerm no value": {¬HasTerm{"test", ""}, "test", "", false}, + "nil andTerm": {andTerm(nil), "test", "", false}, + "empty andTerm": {andTerm{}, "test", "", false}, + "nested andTerm": {andTerm{andTerm{}}, "test", "", false}, + "nested andTerm matches": {andTerm{&hasTerm{"test", "b"}}, "test", "b", true}, + "andTerm with non-match": {andTerm{&hasTerm{}, &hasTerm{"test", "b"}}, "test", "b", true}, + } + for k, v := range testCases { + value, found := v.S.RequiresExactMatch(v.Label) + if value != v.Value { + t.Errorf("%s: expected value %s, got %s", k, v.Value, value) + } + if found != v.Found { + t.Errorf("%s: expected found %t, got %t", k, v.Found, found) + } + } +} + +func TestTransform(t *testing.T) { + testCases := []struct { + name string + selector string + transform func(field, value string) (string, string, error) + result string + isEmpty bool + }{ + { + name: "empty selector", + selector: "", + transform: func(field, value string) (string, string, error) { return field, value, nil }, + result: "", + isEmpty: true, + }, + { + name: "no-op transform", + selector: "a=b,c=d", + transform: func(field, value string) (string, string, error) { return field, value, nil }, + result: "a=b,c=d", + isEmpty: false, + }, + { + name: "transform one field", + selector: "a=b,c=d", + transform: func(field, value string) (string, string, error) { + if field == "a" { + return "e", "f", nil + } + return field, value, nil + }, + result: "e=f,c=d", + isEmpty: false, + }, + { + name: "remove field to make empty", + selector: "a=b", + transform: func(field, value string) (string, string, error) { return "", "", nil }, + result: "", + isEmpty: true, + }, + { + name: "remove only one field", + selector: "a=b,c=d,e=f", + transform: func(field, value string) (string, string, error) { + if field == "c" { + return "", "", nil + } + return field, value, nil + }, + result: "a=b,e=f", + isEmpty: false, + }, + } + + for i, tc := range testCases { + result, err := ParseAndTransformSelector(tc.selector, tc.transform) + if err != nil { + t.Errorf("[%d] unexpected error during Transform: %v", i, err) + } + if result.Empty() != tc.isEmpty { + t.Errorf("[%d] expected empty: %t, got: %t", i, tc.isEmpty, result.Empty()) + } + if result.String() != tc.result { + t.Errorf("[%d] unexpected result: %s", i, result.String()) + } + } + +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/doc.go new file mode 100644 index 0000000000..35ba788094 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/doc.go @@ -0,0 +1,19 @@ +/* +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 labels implements a simple label system, parsing and matching +// selectors with sets of labels. +package labels diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/labels.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/labels.go new file mode 100644 index 0000000000..670b010ba5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/labels.go @@ -0,0 +1,183 @@ +/* +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 labels + +import ( + "fmt" + "sort" + "strings" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// Labels allows you to present labels independently from their storage. +type Labels interface { + // Has returns whether the provided label exists. + Has(label string) (exists bool) + + // Get returns the value for the provided label. + Get(label string) (value string) + + // Lookup returns the value for the provided label if it exists and whether the provided label exist + Lookup(label string) (value string, exists bool) +} + +// Set is a map of label:value. It implements Labels. +type Set map[string]string + +// String returns all labels listed as a human readable string. +// Conveniently, exactly the format that ParseSelector takes. +func (ls Set) String() string { + selector := make([]string, 0, len(ls)) + for key, value := range ls { + selector = append(selector, key+"="+value) + } + // Sort for determinism. + sort.StringSlice(selector).Sort() + return strings.Join(selector, ",") +} + +// Has returns whether the provided label exists in the map. +func (ls Set) Has(label string) bool { + _, exists := ls[label] + return exists +} + +// Get returns the value in the map for the provided label. +func (ls Set) Get(label string) string { + return ls[label] +} + +// Lookup returns the value for the provided label if it exists and whether the provided label exist +func (ls Set) Lookup(label string) (string, bool) { + val, exists := ls[label] + return val, exists +} + +// AsSelector converts labels into a selectors. It does not +// perform any validation, which means the server will reject +// the request if the Set contains invalid values. +func (ls Set) AsSelector() Selector { + return SelectorFromSet(ls) +} + +// AsValidatedSelector converts labels into a selectors. +// The Set is validated client-side, which allows to catch errors early. +func (ls Set) AsValidatedSelector() (Selector, error) { + return ValidatedSelectorFromSet(ls) +} + +// AsSelectorPreValidated converts labels into a selector, but +// assumes that labels are already validated and thus doesn't +// perform any validation. +// According to our measurements this is significantly faster +// in codepaths that matter at high scale. +// Note: this method copies the Set; if the Set is immutable, consider wrapping it with ValidatedSetSelector +// instead, which does not copy. +func (ls Set) AsSelectorPreValidated() Selector { + return SelectorFromValidatedSet(ls) +} + +// FormatLabels converts label map into plain string +func FormatLabels(labelMap map[string]string) string { + l := Set(labelMap).String() + if l == "" { + l = "" + } + return l +} + +// Conflicts takes 2 maps and returns true if there a key match between +// the maps but the value doesn't match, and returns false in other cases +func Conflicts(labels1, labels2 Set) bool { + small := labels1 + big := labels2 + if len(labels2) < len(labels1) { + small = labels2 + big = labels1 + } + + for k, v := range small { + if val, match := big[k]; match { + if val != v { + return true + } + } + } + + return false +} + +// Merge combines given maps, and does not check for any conflicts +// between the maps. In case of conflicts, second map (labels2) wins +func Merge(labels1, labels2 Set) Set { + mergedMap := Set{} + + for k, v := range labels1 { + mergedMap[k] = v + } + for k, v := range labels2 { + mergedMap[k] = v + } + return mergedMap +} + +// Equals returns true if the given maps are equal +func Equals(labels1, labels2 Set) bool { + if len(labels1) != len(labels2) { + return false + } + + for k, v := range labels1 { + value, ok := labels2[k] + if !ok { + return false + } + if value != v { + return false + } + } + return true +} + +// ConvertSelectorToLabelsMap converts selector string to labels map +// and validates keys and values +func ConvertSelectorToLabelsMap(selector string, opts ...field.PathOption) (Set, error) { + labelsMap := Set{} + + if len(selector) == 0 { + return labelsMap, nil + } + + labels := strings.Split(selector, ",") + for _, label := range labels { + l := strings.Split(label, "=") + if len(l) != 2 { + return labelsMap, fmt.Errorf("invalid selector: %s", l) + } + key := strings.TrimSpace(l[0]) + if err := validateLabelKey(key, field.ToPath(opts...)); err != nil { + return labelsMap, err + } + value := strings.TrimSpace(l[1]) + if err := validateLabelValue(key, value, field.ToPath(opts...)); err != nil { + return labelsMap, err + } + labelsMap[key] = value + } + return labelsMap, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/labels_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/labels_test.go new file mode 100644 index 0000000000..2d4d761bc2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/labels_test.go @@ -0,0 +1,231 @@ +/* +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 labels + +import ( + "testing" +) + +func matches(t *testing.T, ls Set, want string) { + if ls.String() != want { + t.Errorf("Expected '%s', but got '%s'", want, ls.String()) + } +} + +func TestSetString(t *testing.T) { + matches(t, Set{"x": "y"}, "x=y") + matches(t, Set{"foo": "bar"}, "foo=bar") + matches(t, Set{"foo": "bar", "baz": "qup"}, "baz=qup,foo=bar") + + // TODO: Make our label representation robust enough to handle labels + // with ",=!" characters in their names. +} + +func TestLabelHas(t *testing.T) { + labelHasTests := []struct { + Ls Labels + Key string + Has bool + }{ + {Set{"x": "y"}, "x", true}, + {Set{"x": ""}, "x", true}, + {Set{"x": "y"}, "foo", false}, + } + for _, lh := range labelHasTests { + if has := lh.Ls.Has(lh.Key); has != lh.Has { + t.Errorf("%#v.Has(%#v) => %v, expected %v", lh.Ls, lh.Key, has, lh.Has) + } + } +} + +func TestLabelGet(t *testing.T) { + ls := Set{"x": "y"} + if ls.Get("x") != "y" { + t.Errorf("Set.Get is broken") + } +} + +func TestLabelConflict(t *testing.T) { + tests := []struct { + labels1 map[string]string + labels2 map[string]string + conflict bool + }{ + { + labels1: map[string]string{}, + labels2: map[string]string{}, + conflict: false, + }, + { + labels1: map[string]string{"env": "test"}, + labels2: map[string]string{"infra": "true"}, + conflict: false, + }, + { + labels1: map[string]string{"env": "test"}, + labels2: map[string]string{"infra": "true", "env": "test"}, + conflict: false, + }, + { + labels1: map[string]string{"env": "test"}, + labels2: map[string]string{"env": "dev"}, + conflict: true, + }, + { + labels1: map[string]string{"env": "test", "infra": "false"}, + labels2: map[string]string{"infra": "true", "color": "blue"}, + conflict: true, + }, + } + for _, test := range tests { + conflict := Conflicts(Set(test.labels1), Set(test.labels2)) + if conflict != test.conflict { + t.Errorf("expected: %v but got: %v", test.conflict, conflict) + } + } +} + +func TestLabelMerge(t *testing.T) { + tests := []struct { + labels1 map[string]string + labels2 map[string]string + mergedLabels map[string]string + }{ + { + labels1: map[string]string{}, + labels2: map[string]string{}, + mergedLabels: map[string]string{}, + }, + { + labels1: map[string]string{"infra": "true"}, + labels2: map[string]string{}, + mergedLabels: map[string]string{"infra": "true"}, + }, + { + labels1: map[string]string{"infra": "true"}, + labels2: map[string]string{"env": "test", "color": "blue"}, + mergedLabels: map[string]string{"infra": "true", "env": "test", "color": "blue"}, + }, + } + for _, test := range tests { + mergedLabels := Merge(Set(test.labels1), Set(test.labels2)) + if !Equals(mergedLabels, test.mergedLabels) { + t.Errorf("expected: %v but got: %v", test.mergedLabels, mergedLabels) + } + } +} + +func TestLabelSelectorParse(t *testing.T) { + tests := []struct { + selector string + labels map[string]string + valid bool + }{ + { + selector: "", + labels: map[string]string{}, + valid: true, + }, + { + selector: "x=a", + labels: map[string]string{"x": "a"}, + valid: true, + }, + { + selector: "x=a,y=b,z=c", + labels: map[string]string{"x": "a", "y": "b", "z": "c"}, + valid: true, + }, + { + selector: " x = a , y = b , z = c ", + labels: map[string]string{"x": "a", "y": "b", "z": "c"}, + valid: true, + }, + { + selector: "color=green,env=test,service=front", + labels: map[string]string{"color": "green", "env": "test", "service": "front"}, + valid: true, + }, + { + selector: "color=green, env=test, service=front", + labels: map[string]string{"color": "green", "env": "test", "service": "front"}, + valid: true, + }, + { + selector: ",", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x,y", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x=$y", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x!=y", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x==y", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x=a||y=b", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x in (y)", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x notin (y)", + labels: map[string]string{}, + valid: false, + }, + { + selector: "x y", + labels: map[string]string{}, + valid: false, + }, + } + for _, test := range tests { + labels, err := ConvertSelectorToLabelsMap(test.selector) + if test.valid && err != nil { + t.Errorf("selector: %s, expected no error but got: %s", test.selector, err) + } else if !test.valid && err == nil { + t.Errorf("selector: %s, expected an error", test.selector) + } + + if !Equals(Set(labels), test.labels) { + t.Errorf("expected: %s but got: %s", test.labels, labels) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/selector.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/selector.go new file mode 100644 index 0000000000..f31a890f2b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/selector.go @@ -0,0 +1,1073 @@ +/* +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 labels + +import ( + "fmt" + "slices" + "sort" + "strconv" + "strings" + + "k8s.io/klog/v2" + + "k8s.io/apimachinery/pkg/api/validate/content" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +var ( + unaryOperators = []string{ + string(selection.Exists), string(selection.DoesNotExist), + } + binaryOperators = []string{ + string(selection.In), string(selection.NotIn), + string(selection.Equals), string(selection.DoubleEquals), string(selection.NotEquals), + string(selection.GreaterThan), string(selection.LessThan), + } + validRequirementOperators = append(binaryOperators, unaryOperators...) +) + +// Requirements is AND of all requirements. +type Requirements []Requirement + +func (r Requirements) String() string { + var sb strings.Builder + + for i, requirement := range r { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(requirement.String()) + } + + return sb.String() +} + +// Selector represents a label selector. +type Selector interface { + // Matches returns true if this selector matches the given set of labels. + Matches(Labels) bool + + // Empty returns true if this selector does not restrict the selection space. + Empty() bool + + // String returns a human readable string that represents this selector. + String() string + + // Add adds requirements to the Selector + Add(r ...Requirement) Selector + + // Requirements converts this interface into Requirements to expose + // more detailed selection information. + // If there are querying parameters, it will return converted requirements and selectable=true. + // If this selector doesn't want to select anything, it will return selectable=false. + Requirements() (requirements Requirements, selectable bool) + + // Make a deep copy of the selector. + DeepCopySelector() Selector + + // RequiresExactMatch allows a caller to introspect whether a given selector + // requires a single specific label to be set, and if so returns the value it + // requires. + RequiresExactMatch(label string) (value string, found bool) +} + +// Sharing this saves 1 alloc per use; this is safe because it's immutable. +var sharedEverythingSelector Selector = internalSelector{} + +// Everything returns a selector that matches all labels. +func Everything() Selector { + return sharedEverythingSelector +} + +type nothingSelector struct{} + +func (n nothingSelector) Matches(_ Labels) bool { return false } +func (n nothingSelector) Empty() bool { return false } +func (n nothingSelector) String() string { return "" } +func (n nothingSelector) Add(_ ...Requirement) Selector { return n } +func (n nothingSelector) Requirements() (Requirements, bool) { return nil, false } +func (n nothingSelector) DeepCopySelector() Selector { return n } +func (n nothingSelector) RequiresExactMatch(label string) (value string, found bool) { + return "", false +} + +// Sharing this saves 1 alloc per use; this is safe because it's immutable. +var sharedNothingSelector Selector = nothingSelector{} + +// Nothing returns a selector that matches no labels +func Nothing() Selector { + return sharedNothingSelector +} + +// MatchesNothing only returns true for selectors which are definitively determined to match no objects. +// This currently only detects the `labels.Nothing()` selector, but may change over time to detect more selectors that match no objects. +// +// Note: The current implementation does not check for selector conflict scenarios (e.g., a=a,a!=a). +// Support for detecting such cases can be added in the future. +func MatchesNothing(selector Selector) bool { + return selector == sharedNothingSelector +} + +// NewSelector returns a nil selector +func NewSelector() Selector { + return internalSelector(nil) +} + +type internalSelector []Requirement + +func (s internalSelector) DeepCopy() internalSelector { + if s == nil { + return nil + } + result := make([]Requirement, len(s)) + for i := range s { + s[i].DeepCopyInto(&result[i]) + } + return result +} + +func (s internalSelector) DeepCopySelector() Selector { + return s.DeepCopy() +} + +// ByKey sorts requirements by key to obtain deterministic parser +type ByKey []Requirement + +func (a ByKey) Len() int { return len(a) } + +func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func (a ByKey) Less(i, j int) bool { return a[i].key < a[j].key } + +// Requirement contains values, a key, and an operator that relates the key and values. +// The zero value of Requirement is invalid. +// Requirement implements both set based match and exact match +// Requirement should be initialized via NewRequirement constructor for creating a valid Requirement. +// +k8s:deepcopy-gen=true +type Requirement struct { + key string + operator selection.Operator + // In huge majority of cases we have at most one value here. + // It is generally faster to operate on a single-element slice + // than on a single-element map, so we have a slice here. + strValues []string +} + +// NewRequirement is the constructor for a Requirement. +// If any of these rules is violated, an error is returned: +// 1. The operator can only be In, NotIn, Equals, DoubleEquals, Gt, Lt, NotEquals, Exists, or DoesNotExist. +// 2. If the operator is In or NotIn, the values set must be non-empty. +// 3. If the operator is Equals, DoubleEquals, or NotEquals, the values set must contain one value. +// 4. If the operator is Exists or DoesNotExist, the value set must be empty. +// 5. If the operator is Gt or Lt, the values set must contain only one value, which will be interpreted as an integer. +// 6. The key is invalid due to its length, or sequence of characters. See validateLabelKey for more details. +// +// The empty string is a valid value in the input values set. +// Returned error, if not nil, is guaranteed to be an aggregated field.ErrorList +func NewRequirement(key string, op selection.Operator, vals []string, opts ...field.PathOption) (*Requirement, error) { + var allErrs field.ErrorList + path := field.ToPath(opts...) + if err := validateLabelKey(key, path.Child("key")); err != nil { + allErrs = append(allErrs, err) + } + + valuePath := path.Child("values") + switch op { + case selection.In, selection.NotIn: + if len(vals) == 0 { + allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'in', 'notin' operators, values set can't be empty")) + } + case selection.Equals, selection.DoubleEquals, selection.NotEquals: + if len(vals) != 1 { + allErrs = append(allErrs, field.Invalid(valuePath, vals, "exact-match compatibility requires one single value")) + } + case selection.Exists, selection.DoesNotExist: + if len(vals) != 0 { + allErrs = append(allErrs, field.Invalid(valuePath, vals, "values set must be empty for exists and does not exist")) + } + case selection.GreaterThan, selection.LessThan: + if len(vals) != 1 { + allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'Gt', 'Lt' operators, exactly one value is required")) + } + for i := range vals { + if _, err := strconv.ParseInt(vals[i], 10, 64); err != nil { + allErrs = append(allErrs, field.Invalid(valuePath.Index(i), vals[i], "for 'Gt', 'Lt' operators, the value must be an integer")) + } + } + default: + allErrs = append(allErrs, field.NotSupported(path.Child("operator"), op, validRequirementOperators)) + } + + for i := range vals { + if err := validateLabelValue(key, vals[i], valuePath.Index(i)); err != nil { + allErrs = append(allErrs, err) + } + } + return &Requirement{key: key, operator: op, strValues: vals}, allErrs.ToAggregate() +} + +func (r *Requirement) hasValue(value string) bool { + for i := range r.strValues { + if r.strValues[i] == value { + return true + } + } + return false +} + +// Matches returns true if the Requirement matches the input Labels. +// There is a match in the following cases: +// 1. The operator is Exists and Labels has the Requirement's key. +// 2. The operator is In, Labels has the Requirement's key and Labels' +// value for that key is in Requirement's value set. +// 3. The operator is NotIn, Labels has the Requirement's key and +// Labels' value for that key is not in Requirement's value set. +// 4. The operator is DoesNotExist or NotIn and Labels does not have the +// Requirement's key. +// 5. The operator is GreaterThanOperator or LessThanOperator, and Labels has +// the Requirement's key and the corresponding value satisfies mathematical inequality. +func (r *Requirement) Matches(ls Labels) bool { + switch r.operator { + case selection.In, selection.Equals, selection.DoubleEquals: + val, exists := ls.Lookup(r.key) + if !exists { + return false + } + return r.hasValue(val) + case selection.NotIn, selection.NotEquals: + val, exists := ls.Lookup(r.key) + if !exists { + return true + } + return !r.hasValue(val) + case selection.Exists: + return ls.Has(r.key) + case selection.DoesNotExist: + return !ls.Has(r.key) + case selection.GreaterThan, selection.LessThan: + val, exists := ls.Lookup(r.key) + if !exists { + return false + } + lsValue, err := strconv.ParseInt(val, 10, 64) + if err != nil { + //nolint:logcheck // Extending the API is not worth it for contextual, structured logging of this. + klog.V(10).InfoS("ParseInt failed", "value", val, "label", ls, "err", err) + return false + } + + // There should be only one strValue in r.strValues, and can be converted to an integer. + if len(r.strValues) != 1 { + //nolint:logcheck // Extending the API is not worth it for contextual, structured logging of this. + klog.V(10).InfoS("Invalid values count: for 'Gt', 'Lt' operators, exactly one value is required", "count", len(r.strValues), "requirement", r) + return false + } + + var rValue int64 + for i := range r.strValues { + rValue, err = strconv.ParseInt(r.strValues[i], 10, 64) + if err != nil { + //nolint:logcheck // Extending the API is not worth it for contextual, structured logging of this. + klog.V(10).InfoS("ParseInt failed: for 'Gt', 'Lt' operators, the value must be an integer", "value", r.strValues[i], "requirement", r, "err", err) + return false + } + } + return (r.operator == selection.GreaterThan && lsValue > rValue) || (r.operator == selection.LessThan && lsValue < rValue) + default: + return false + } +} + +// Key returns requirement key +func (r *Requirement) Key() string { + return r.key +} + +// Operator returns requirement operator +func (r *Requirement) Operator() selection.Operator { + return r.operator +} + +// Values returns requirement values +func (r *Requirement) Values() sets.String { + ret := sets.String{} + for i := range r.strValues { + ret.Insert(r.strValues[i]) + } + return ret +} + +// ValuesUnsorted returns a copy of requirement values as passed to NewRequirement without sorting. +func (r *Requirement) ValuesUnsorted() []string { + ret := make([]string, 0, len(r.strValues)) + ret = append(ret, r.strValues...) + return ret +} + +// Equal checks the equality of requirement. +func (r Requirement) Equal(x Requirement) bool { + if r.key != x.key { + return false + } + if r.operator != x.operator { + return false + } + return slices.Equal(r.strValues, x.strValues) +} + +// Empty returns true if the internalSelector doesn't restrict selection space +func (s internalSelector) Empty() bool { + if s == nil { + return true + } + return len(s) == 0 +} + +// String returns a human-readable string that represents this +// Requirement. If called on an invalid Requirement, an error is +// returned. See NewRequirement for creating a valid Requirement. +func (r *Requirement) String() string { + var sb strings.Builder + sb.Grow( + // length of r.key + len(r.key) + + // length of 'r.operator' + 2 spaces for the worst case ('in' and 'notin') + len(r.operator) + 2 + + // length of 'r.strValues' slice times. Heuristically 5 chars per word + +5*len(r.strValues)) + if r.operator == selection.DoesNotExist { + sb.WriteString("!") + } + sb.WriteString(r.key) + + switch r.operator { + case selection.Equals: + sb.WriteString("=") + case selection.DoubleEquals: + sb.WriteString("==") + case selection.NotEquals: + sb.WriteString("!=") + case selection.In: + sb.WriteString(" in ") + case selection.NotIn: + sb.WriteString(" notin ") + case selection.GreaterThan: + sb.WriteString(">") + case selection.LessThan: + sb.WriteString("<") + case selection.Exists, selection.DoesNotExist: + return sb.String() + } + + switch r.operator { + case selection.In, selection.NotIn: + sb.WriteString("(") + } + if len(r.strValues) == 1 { + sb.WriteString(r.strValues[0]) + } else { // only > 1 since == 0 prohibited by NewRequirement + // normalizes value order on output, without mutating the in-memory selector representation + // also avoids normalization when it is not required, and ensures we do not mutate shared data + sb.WriteString(strings.Join(safeSort(r.strValues), ",")) + } + + switch r.operator { + case selection.In, selection.NotIn: + sb.WriteString(")") + } + return sb.String() +} + +// safeSort sorts input strings without modification +func safeSort(in []string) []string { + if sort.StringsAreSorted(in) { + return in + } + out := make([]string, len(in)) + copy(out, in) + sort.Strings(out) + return out +} + +// Add adds requirements to the selector. It copies the current selector returning a new one +func (s internalSelector) Add(reqs ...Requirement) Selector { + ret := make(internalSelector, 0, len(s)+len(reqs)) + ret = append(ret, s...) + ret = append(ret, reqs...) + sort.Sort(ByKey(ret)) + return ret +} + +// Matches for a internalSelector returns true if all +// its Requirements match the input Labels. If any +// Requirement does not match, false is returned. +func (s internalSelector) Matches(l Labels) bool { + for ix := range s { + if matches := s[ix].Matches(l); !matches { + return false + } + } + return true +} + +func (s internalSelector) Requirements() (Requirements, bool) { return Requirements(s), true } + +// String returns a comma-separated string of all +// the internalSelector Requirements' human-readable strings. +func (s internalSelector) String() string { + var reqs []string + for ix := range s { + reqs = append(reqs, s[ix].String()) + } + return strings.Join(reqs, ",") +} + +// RequiresExactMatch introspects whether a given selector requires a single specific field +// to be set, and if so returns the value it requires. +func (s internalSelector) RequiresExactMatch(label string) (value string, found bool) { + for ix := range s { + if s[ix].key == label { + switch s[ix].operator { + case selection.Equals, selection.DoubleEquals, selection.In: + if len(s[ix].strValues) == 1 { + return s[ix].strValues[0], true + } + } + return "", false + } + } + return "", false +} + +// Token represents constant definition for lexer token +type Token int + +const ( + // ErrorToken represents scan error + ErrorToken Token = iota + // EndOfStringToken represents end of string + EndOfStringToken + // ClosedParToken represents close parenthesis + ClosedParToken + // CommaToken represents the comma + CommaToken + // DoesNotExistToken represents logic not + DoesNotExistToken + // DoubleEqualsToken represents double equals + DoubleEqualsToken + // EqualsToken represents equal + EqualsToken + // GreaterThanToken represents greater than + GreaterThanToken + // IdentifierToken represents identifier, e.g. keys and values + IdentifierToken + // InToken represents in + InToken + // LessThanToken represents less than + LessThanToken + // NotEqualsToken represents not equal + NotEqualsToken + // NotInToken represents not in + NotInToken + // OpenParToken represents open parenthesis + OpenParToken +) + +// string2token contains the mapping between lexer Token and token literal +// (except IdentifierToken, EndOfStringToken and ErrorToken since it makes no sense) +var string2token = map[string]Token{ + ")": ClosedParToken, + ",": CommaToken, + "!": DoesNotExistToken, + "==": DoubleEqualsToken, + "=": EqualsToken, + ">": GreaterThanToken, + "in": InToken, + "<": LessThanToken, + "!=": NotEqualsToken, + "notin": NotInToken, + "(": OpenParToken, +} + +// ScannedItem contains the Token and the literal produced by the lexer. +type ScannedItem struct { + tok Token + literal string +} + +// isWhitespace returns true if the rune is a space, tab, or newline. +func isWhitespace(ch byte) bool { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' +} + +// isSpecialSymbol detects if the character ch can be an operator +func isSpecialSymbol(ch byte) bool { + switch ch { + case '=', '!', '(', ')', ',', '>', '<': + return true + } + return false +} + +// Lexer represents the Lexer struct for label selector. +// It contains necessary informationt to tokenize the input string +type Lexer struct { + // s stores the string to be tokenized + s string + // pos is the position currently tokenized + pos int +} + +// read returns the character currently lexed +// increment the position and check the buffer overflow +func (l *Lexer) read() (b byte) { + b = 0 + if l.pos < len(l.s) { + b = l.s[l.pos] + l.pos++ + } + return b +} + +// unread 'undoes' the last read character +func (l *Lexer) unread() { + l.pos-- +} + +// scanIDOrKeyword scans string to recognize literal token (for example 'in') or an identifier. +func (l *Lexer) scanIDOrKeyword() (tok Token, lit string) { + var buffer []byte +IdentifierLoop: + for { + switch ch := l.read(); { + case ch == 0: + break IdentifierLoop + case isSpecialSymbol(ch) || isWhitespace(ch): + l.unread() + break IdentifierLoop + default: + buffer = append(buffer, ch) + } + } + s := string(buffer) + if val, ok := string2token[s]; ok { // is a literal token? + return val, s + } + return IdentifierToken, s // otherwise is an identifier +} + +// scanSpecialSymbol scans string starting with special symbol. +// special symbol identify non literal operators. "!=", "==", "=" +func (l *Lexer) scanSpecialSymbol() (Token, string) { + lastScannedItem := ScannedItem{} + var buffer []byte +SpecialSymbolLoop: + for { + switch ch := l.read(); { + case ch == 0: + break SpecialSymbolLoop + case isSpecialSymbol(ch): + buffer = append(buffer, ch) + if token, ok := string2token[string(buffer)]; ok { + lastScannedItem = ScannedItem{tok: token, literal: string(buffer)} + } else if lastScannedItem.tok != 0 { + l.unread() + break SpecialSymbolLoop + } + default: + l.unread() + break SpecialSymbolLoop + } + } + if lastScannedItem.tok == 0 { + return ErrorToken, fmt.Sprintf("error expected: keyword found '%s'", buffer) + } + return lastScannedItem.tok, lastScannedItem.literal +} + +// skipWhiteSpaces consumes all blank characters +// returning the first non blank character +func (l *Lexer) skipWhiteSpaces(ch byte) byte { + for { + if !isWhitespace(ch) { + return ch + } + ch = l.read() + } +} + +// Lex returns a pair of Token and the literal +// literal is meaningfull only for IdentifierToken token +func (l *Lexer) Lex() (tok Token, lit string) { + switch ch := l.skipWhiteSpaces(l.read()); { + case ch == 0: + return EndOfStringToken, "" + case isSpecialSymbol(ch): + l.unread() + return l.scanSpecialSymbol() + default: + l.unread() + return l.scanIDOrKeyword() + } +} + +// Parser data structure contains the label selector parser data structure +type Parser struct { + l *Lexer + scannedItems []ScannedItem + position int + path *field.Path +} + +// ParserContext represents context during parsing: +// some literal for example 'in' and 'notin' can be +// recognized as operator for example 'x in (a)' but +// it can be recognized as value for example 'value in (in)' +type ParserContext int + +const ( + // KeyAndOperator represents key and operator + KeyAndOperator ParserContext = iota + // Values represents values + Values +) + +// lookahead func returns the current token and string. No increment of current position +func (p *Parser) lookahead(context ParserContext) (Token, string) { + tok, lit := p.scannedItems[p.position].tok, p.scannedItems[p.position].literal + if context == Values { + switch tok { + case InToken, NotInToken: + tok = IdentifierToken + } + } + return tok, lit +} + +// consume returns current token and string. Increments the position +func (p *Parser) consume(context ParserContext) (Token, string) { + p.position++ + tok, lit := p.scannedItems[p.position-1].tok, p.scannedItems[p.position-1].literal + if context == Values { + switch tok { + case InToken, NotInToken: + tok = IdentifierToken + } + } + return tok, lit +} + +// scan runs through the input string and stores the ScannedItem in an array +// Parser can now lookahead and consume the tokens +func (p *Parser) scan() { + for { + token, literal := p.l.Lex() + p.scannedItems = append(p.scannedItems, ScannedItem{token, literal}) + if token == EndOfStringToken { + break + } + } +} + +// parse runs the left recursive descending algorithm +// on input string. It returns a list of Requirement objects. +func (p *Parser) parse() (internalSelector, error) { + p.scan() // init scannedItems + + var requirements internalSelector + for { + tok, lit := p.lookahead(Values) + switch tok { + case IdentifierToken, DoesNotExistToken: + r, err := p.parseRequirement() + if err != nil { + return nil, fmt.Errorf("unable to parse requirement: %v", err) + } + requirements = append(requirements, *r) + t, l := p.consume(Values) + switch t { + case EndOfStringToken: + return requirements, nil + case CommaToken: + t2, l2 := p.lookahead(Values) + if t2 != IdentifierToken && t2 != DoesNotExistToken { + return nil, fmt.Errorf("found '%s', expected: identifier after ','", l2) + } + default: + return nil, fmt.Errorf("found '%s', expected: ',' or 'end of string'", l) + } + case EndOfStringToken: + return requirements, nil + default: + return nil, fmt.Errorf("found '%s', expected: !, identifier, or 'end of string'", lit) + } + } +} + +func (p *Parser) parseRequirement() (*Requirement, error) { + key, operator, err := p.parseKeyAndInferOperator() + if err != nil { + return nil, err + } + if operator == selection.Exists || operator == selection.DoesNotExist { // operator found lookahead set checked + return NewRequirement(key, operator, []string{}, field.WithPath(p.path)) + } + operator, err = p.parseOperator() + if err != nil { + return nil, err + } + var values sets.String + switch operator { + case selection.In, selection.NotIn: + values, err = p.parseValues() + case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.GreaterThan, selection.LessThan: + values, err = p.parseExactValue() + } + if err != nil { + return nil, err + } + return NewRequirement(key, operator, values.List(), field.WithPath(p.path)) + +} + +// parseKeyAndInferOperator parses literals. +// in case of no operator '!, in, notin, ==, =, !=' are found +// the 'exists' operator is inferred +func (p *Parser) parseKeyAndInferOperator() (string, selection.Operator, error) { + var operator selection.Operator + tok, literal := p.consume(Values) + if tok == DoesNotExistToken { + operator = selection.DoesNotExist + tok, literal = p.consume(Values) + } + if tok != IdentifierToken { + err := fmt.Errorf("found '%s', expected: identifier", literal) + return "", "", err + } + if err := validateLabelKey(literal, p.path); err != nil { + return "", "", err + } + if t, _ := p.lookahead(Values); t == EndOfStringToken || t == CommaToken { + if operator != selection.DoesNotExist { + operator = selection.Exists + } + } + return literal, operator, nil +} + +// parseOperator returns operator and eventually matchType +// matchType can be exact +func (p *Parser) parseOperator() (op selection.Operator, err error) { + tok, lit := p.consume(KeyAndOperator) + switch tok { + // DoesNotExistToken shouldn't be here because it's a unary operator, not a binary operator + case InToken: + op = selection.In + case EqualsToken: + op = selection.Equals + case DoubleEqualsToken: + op = selection.DoubleEquals + case GreaterThanToken: + op = selection.GreaterThan + case LessThanToken: + op = selection.LessThan + case NotInToken: + op = selection.NotIn + case NotEqualsToken: + op = selection.NotEquals + default: + return "", fmt.Errorf("found '%s', expected: %v", lit, strings.Join(binaryOperators, ", ")) + } + return op, nil +} + +// parseValues parses the values for set based matching (x,y,z) +func (p *Parser) parseValues() (sets.String, error) { + tok, lit := p.consume(Values) + if tok != OpenParToken { + return nil, fmt.Errorf("found '%s' expected: '('", lit) + } + tok, lit = p.lookahead(Values) + switch tok { + case IdentifierToken, CommaToken: + s, err := p.parseIdentifiersList() // handles general cases + if err != nil { + return s, err + } + if tok, _ = p.consume(Values); tok != ClosedParToken { + return nil, fmt.Errorf("found '%s', expected: ')'", lit) + } + return s, nil + case ClosedParToken: // handles "()" + p.consume(Values) + return sets.NewString(""), nil + default: + return nil, fmt.Errorf("found '%s', expected: ',', ')' or identifier", lit) + } +} + +// parseIdentifiersList parses a (possibly empty) list of +// of comma separated (possibly empty) identifiers +func (p *Parser) parseIdentifiersList() (sets.String, error) { + s := sets.NewString() + for { + tok, lit := p.consume(Values) + switch tok { + case IdentifierToken: + s.Insert(lit) + tok2, lit2 := p.lookahead(Values) + switch tok2 { + case CommaToken: + continue + case ClosedParToken: + return s, nil + default: + return nil, fmt.Errorf("found '%s', expected: ',' or ')'", lit2) + } + case CommaToken: // handled here since we can have "(," + if s.Len() == 0 { + s.Insert("") // to handle (, + } + tok2, _ := p.lookahead(Values) + if tok2 == ClosedParToken { + s.Insert("") // to handle ,) Double "" removed by StringSet + return s, nil + } + if tok2 == CommaToken { + s.Insert("") // to handle ,, Double "" removed by StringSet + } + default: // it can be operator + return s, fmt.Errorf("found '%s', expected: ',', or identifier", lit) + } + } +} + +// parseExactValue parses the only value for exact match style +func (p *Parser) parseExactValue() (sets.String, error) { + s := sets.NewString() + tok, _ := p.lookahead(Values) + if tok == EndOfStringToken || tok == CommaToken { + s.Insert("") + return s, nil + } + tok, lit := p.consume(Values) + if tok == IdentifierToken { + s.Insert(lit) + return s, nil + } + return nil, fmt.Errorf("found '%s', expected: identifier", lit) +} + +// Parse takes a string representing a selector and returns a selector +// object, or an error. This parsing function differs from ParseSelector +// as they parse different selectors with different syntaxes. +// The input will cause an error if it does not follow this form: +// +// ::= | "," +// ::= [!] KEY [ | ] +// ::= "" | +// ::= | +// ::= "notin" +// ::= "in" +// ::= "(" ")" +// ::= VALUE | VALUE "," +// ::= ["="|"=="|"!="] VALUE +// +// KEY is a sequence of one or more characters following [ DNS_SUBDOMAIN "/" ] DNS_LABEL. Max length is 63 characters. +// VALUE is a sequence of zero or more characters "([A-Za-z0-9_-\.])". Max length is 63 characters. +// Delimiter is white space: (' ', '\t') +// Example of valid syntax: +// +// "x in (foo,,baz),y,z notin ()" +// +// Note: +// 1. Inclusion - " in " - denotes that the KEY exists and is equal to any of the +// VALUEs in its requirement +// 2. Exclusion - " notin " - denotes that the KEY is not equal to any +// of the VALUEs in its requirement or does not exist +// 3. The empty string is a valid VALUE +// 4. A requirement with just a KEY - as in "y" above - denotes that +// the KEY exists and can be any VALUE. +// 5. A requirement with just !KEY requires that the KEY not exist. +func Parse(selector string, opts ...field.PathOption) (Selector, error) { + parsedSelector, err := parse(selector, field.ToPath(opts...)) + if err == nil { + return parsedSelector, nil + } + return nil, err +} + +// parse parses the string representation of the selector and returns the internalSelector struct. +// The callers of this method can then decide how to return the internalSelector struct to their +// callers. This function has two callers now, one returns a Selector interface and the other +// returns a list of requirements. +func parse(selector string, path *field.Path) (internalSelector, error) { + p := &Parser{l: &Lexer{s: selector, pos: 0}, path: path} + items, err := p.parse() + if err != nil { + return nil, err + } + sort.Sort(ByKey(items)) // sort to grant determistic parsing + return internalSelector(items), err +} + +func validateLabelKey(k string, path *field.Path) *field.Error { + if errs := content.IsLabelKey(k); len(errs) != 0 { + return field.Invalid(path, k, strings.Join(errs, "; ")) + } + return nil +} + +func validateLabelValue(k, v string, path *field.Path) *field.Error { + if errs := validation.IsValidLabelValue(v); len(errs) != 0 { + return field.Invalid(path.Key(k), v, strings.Join(errs, "; ")) + } + return nil +} + +// SelectorFromSet returns a Selector which will match exactly the given Set. A +// nil and empty Sets are considered equivalent to Everything(). +// It does not perform any validation, which means the server will reject +// the request if the Set contains invalid values. +func SelectorFromSet(ls Set) Selector { + return SelectorFromValidatedSet(ls) +} + +// ValidatedSelectorFromSet returns a Selector which will match exactly the given Set. A +// nil and empty Sets are considered equivalent to Everything(). +// The Set is validated client-side, which allows to catch errors early. +func ValidatedSelectorFromSet(ls Set) (Selector, error) { + if ls == nil || len(ls) == 0 { + return internalSelector{}, nil + } + requirements := make([]Requirement, 0, len(ls)) + for label, value := range ls { + r, err := NewRequirement(label, selection.Equals, []string{value}) + if err != nil { + return nil, err + } + requirements = append(requirements, *r) + } + // sort to have deterministic string representation + sort.Sort(ByKey(requirements)) + return internalSelector(requirements), nil +} + +// SelectorFromValidatedSet returns a Selector which will match exactly the given Set. +// A nil and empty Sets are considered equivalent to Everything(). +// It assumes that Set is already validated and doesn't do any validation. +// Note: this method copies the Set; if the Set is immutable, consider wrapping it with ValidatedSetSelector +// instead, which does not copy. +func SelectorFromValidatedSet(ls Set) Selector { + if ls == nil || len(ls) == 0 { + return internalSelector{} + } + requirements := make([]Requirement, 0, len(ls)) + for label, value := range ls { + requirements = append(requirements, Requirement{key: label, operator: selection.Equals, strValues: []string{value}}) + } + // sort to have deterministic string representation + sort.Sort(ByKey(requirements)) + return internalSelector(requirements) +} + +// ParseToRequirements takes a string representing a selector and returns a list of +// requirements. This function is suitable for those callers that perform additional +// processing on selector requirements. +// See the documentation for Parse() function for more details. +// TODO: Consider exporting the internalSelector type instead. +func ParseToRequirements(selector string, opts ...field.PathOption) ([]Requirement, error) { + return parse(selector, field.ToPath(opts...)) +} + +// ValidatedSetSelector wraps a Set, allowing it to implement the Selector interface. Unlike +// Set.AsSelectorPreValidated (which copies the input Set), this type simply wraps the underlying +// Set. As a result, it is substantially more efficient. A nil and empty Sets are considered +// equivalent to Everything(). +// +// Callers MUST ensure the underlying Set is not mutated, and that it is already validated. If these +// constraints are not met, Set.AsValidatedSelector should be preferred +// +// None of the Selector methods mutate the underlying Set, but Add() and Requirements() convert to +// the less optimized version. +type ValidatedSetSelector Set + +func (s ValidatedSetSelector) Matches(labels Labels) bool { + for k, v := range s { + val, exists := labels.Lookup(k) + if !exists || v != val { + return false + } + } + return true +} + +func (s ValidatedSetSelector) Empty() bool { + return len(s) == 0 +} + +func (s ValidatedSetSelector) String() string { + keys := make([]string, 0, len(s)) + for k := range s { + keys = append(keys, k) + } + // Ensure deterministic output + sort.Strings(keys) + b := strings.Builder{} + for i, key := range keys { + v := s[key] + b.Grow(len(key) + 2 + len(v)) + if i != 0 { + b.WriteString(",") + } + b.WriteString(key) + b.WriteString("=") + b.WriteString(v) + } + return b.String() +} + +func (s ValidatedSetSelector) Add(r ...Requirement) Selector { + return s.toFullSelector().Add(r...) +} + +func (s ValidatedSetSelector) Requirements() (requirements Requirements, selectable bool) { + return s.toFullSelector().Requirements() +} + +func (s ValidatedSetSelector) DeepCopySelector() Selector { + res := make(ValidatedSetSelector, len(s)) + for k, v := range s { + res[k] = v + } + return res +} + +func (s ValidatedSetSelector) RequiresExactMatch(label string) (value string, found bool) { + v, f := s[label] + return v, f +} + +func (s ValidatedSetSelector) toFullSelector() Selector { + return SelectorFromValidatedSet(Set(s)) +} + +var _ Selector = ValidatedSetSelector{} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/selector_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/selector_test.go new file mode 100644 index 0000000000..abe066154d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/selector_test.go @@ -0,0 +1,1227 @@ +/* +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 labels + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +var ( + ignoreDetail = cmpopts.IgnoreFields(field.Error{}, "Detail") +) + +func TestSelectorParse(t *testing.T) { + testGoodStrings := []string{ + "x=a,y=b,z=c", + "", + "x!=a,y=b", + "x=", + "x= ", + "x=,z= ", + "x= ,z= ", + "!x", + "x>1", + "x>1,z<5", + } + testBadStrings := []string{ + "x=a||y=b", + "x==a==b", + "!x=a", + "x1", Set{"x": "2"}) + expectMatch(t, "x<1", Set{"x": "0"}) + expectNoMatch(t, "x=z", Set{}) + expectNoMatch(t, "x=y", Set{"x": "z"}) + expectNoMatch(t, "x=y,z=w", Set{"x": "w", "z": "w"}) + expectNoMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "w"}) + expectNoMatch(t, "x", Set{"y": "z"}) + expectNoMatch(t, "!x", Set{"x": "z"}) + expectNoMatch(t, "x>1", Set{"x": "0"}) + expectNoMatch(t, "x<1", Set{"x": "2"}) + + labelset := Set{ + "foo": "bar", + "baz": "blah", + } + expectMatch(t, "foo=bar", labelset) + expectMatch(t, "baz=blah", labelset) + expectMatch(t, "foo=bar,baz=blah", labelset) + expectNoMatch(t, "foo=blah", labelset) + expectNoMatch(t, "baz=bar", labelset) + expectNoMatch(t, "foo=bar,foobar=bar,baz=blah", labelset) +} + +func expectMatchDirect(t *testing.T, selector, ls Set) { + if !SelectorFromSet(selector).Matches(ls) { + t.Errorf("Wanted %s to match '%s', but it did not.\n", selector, ls) + } +} + +//nolint:staticcheck,unused //iccheck // U1000 currently commented out in TODO of TestSetMatches +func expectNoMatchDirect(t *testing.T, selector, ls Set) { + if SelectorFromSet(selector).Matches(ls) { + t.Errorf("Wanted '%s' to not match '%s', but it did.", selector, ls) + } +} + +func TestSetMatches(t *testing.T) { + labelset := Set{ + "foo": "bar", + "baz": "blah", + } + expectMatchDirect(t, Set{}, labelset) + expectMatchDirect(t, Set{"foo": "bar"}, labelset) + expectMatchDirect(t, Set{"baz": "blah"}, labelset) + expectMatchDirect(t, Set{"foo": "bar", "baz": "blah"}, labelset) + + //TODO: bad values not handled for the moment in SelectorFromSet + //expectNoMatchDirect(t, Set{"foo": "=blah"}, labelset) + //expectNoMatchDirect(t, Set{"baz": "=bar"}, labelset) + //expectNoMatchDirect(t, Set{"foo": "=bar", "foobar": "bar", "baz": "blah"}, labelset) +} + +func TestNilMapIsValid(t *testing.T) { + selector := Set(nil).AsSelector() + if selector == nil { + t.Errorf("Selector for nil set should be Everything") + } + if !selector.Empty() { + t.Errorf("Selector for nil set should be Empty") + } +} + +func TestSetIsEmpty(t *testing.T) { + if !(Set{}).AsSelector().Empty() { + t.Errorf("Empty set should be empty") + } + if !(NewSelector()).Empty() { + t.Errorf("Nil Selector should be empty") + } +} + +func TestLexer(t *testing.T) { + testcases := []struct { + s string + t Token + }{ + {"", EndOfStringToken}, + {",", CommaToken}, + {"notin", NotInToken}, + {"in", InToken}, + {"=", EqualsToken}, + {"==", DoubleEqualsToken}, + {">", GreaterThanToken}, + {"<", LessThanToken}, + //Note that Lex returns the longest valid token found + {"!", DoesNotExistToken}, + {"!=", NotEqualsToken}, + {"(", OpenParToken}, + {")", ClosedParToken}, + //Non-"special" characters are considered part of an identifier + {"~", IdentifierToken}, + {"||", IdentifierToken}, + } + for _, v := range testcases { + l := &Lexer{s: v.s, pos: 0} + token, lit := l.Lex() + if token != v.t { + t.Errorf("Got %d it should be %d for '%s'", token, v.t, v.s) + } + if v.t != ErrorToken && lit != v.s { + t.Errorf("Got '%s' it should be '%s'", lit, v.s) + } + } +} + +func TestLexerSequence(t *testing.T) { + testcases := []struct { + s string + t []Token + }{ + {"key in ( value )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, ClosedParToken}}, + {"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken}}, + {"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken}}, + {"key", []Token{IdentifierToken}}, + {"!key", []Token{DoesNotExistToken, IdentifierToken}}, + {"()", []Token{OpenParToken, ClosedParToken}}, + {"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken}}, + {"== != (), = notin", []Token{DoubleEqualsToken, NotEqualsToken, OpenParToken, ClosedParToken, CommaToken, EqualsToken, NotInToken}}, + {"key>2", []Token{IdentifierToken, GreaterThanToken, IdentifierToken}}, + {"key<1", []Token{IdentifierToken, LessThanToken, IdentifierToken}}, + } + for _, v := range testcases { + var tokens []Token + l := &Lexer{s: v.s, pos: 0} + for { + token, _ := l.Lex() + if token == EndOfStringToken { + break + } + tokens = append(tokens, token) + } + if len(tokens) != len(v.t) { + t.Errorf("Bad number of tokens for '%s %d, %d", v.s, len(tokens), len(v.t)) + } + for i := 0; i < min(len(tokens), len(v.t)); i++ { + if tokens[i] != v.t[i] { + t.Errorf("Test '%s': Mismatching in token type found '%v' it should be '%v'", v.s, tokens[i], v.t[i]) + } + } + } +} +func TestParserLookahead(t *testing.T) { + testcases := []struct { + s string + t []Token + }{ + {"key in ( value )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, ClosedParToken, EndOfStringToken}}, + {"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken, EndOfStringToken}}, + {"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken, EndOfStringToken}}, + {"key", []Token{IdentifierToken, EndOfStringToken}}, + {"!key", []Token{DoesNotExistToken, IdentifierToken, EndOfStringToken}}, + {"()", []Token{OpenParToken, ClosedParToken, EndOfStringToken}}, + {"", []Token{EndOfStringToken}}, + {"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken, EndOfStringToken}}, + {"== != (), = notin", []Token{DoubleEqualsToken, NotEqualsToken, OpenParToken, ClosedParToken, CommaToken, EqualsToken, NotInToken, EndOfStringToken}}, + {"key>2", []Token{IdentifierToken, GreaterThanToken, IdentifierToken, EndOfStringToken}}, + {"key<1", []Token{IdentifierToken, LessThanToken, IdentifierToken, EndOfStringToken}}, + } + for _, v := range testcases { + p := &Parser{l: &Lexer{s: v.s, pos: 0}, position: 0} + p.scan() + if len(p.scannedItems) != len(v.t) { + t.Errorf("Expected %d items found %d", len(v.t), len(p.scannedItems)) + } + for { + token, lit := p.lookahead(KeyAndOperator) + + token2, lit2 := p.consume(KeyAndOperator) + if token == EndOfStringToken { + break + } + if token != token2 || lit != lit2 { + t.Errorf("Bad values") + } + } + } +} + +func TestParseOperator(t *testing.T) { + testcases := []struct { + token string + expectedError error + }{ + {"in", nil}, + {"=", nil}, + {"==", nil}, + {">", nil}, + {"<", nil}, + {"notin", nil}, + {"!=", nil}, + {"!", fmt.Errorf("found '%s', expected: %v", selection.DoesNotExist, strings.Join(binaryOperators, ", "))}, + {"exists", fmt.Errorf("found '%s', expected: %v", selection.Exists, strings.Join(binaryOperators, ", "))}, + {"(", fmt.Errorf("found '%s', expected: %v", "(", strings.Join(binaryOperators, ", "))}, + } + for _, testcase := range testcases { + p := &Parser{l: &Lexer{s: testcase.token, pos: 0}, position: 0} + p.scan() + + _, err := p.parseOperator() + if ok := reflect.DeepEqual(testcase.expectedError, err); !ok { + t.Errorf("\nexpect err [%v], \nactual err [%v]", testcase.expectedError, err) + } + } +} + +func TestRequirementConstructor(t *testing.T) { + requirementConstructorTests := []struct { + Key string + Op selection.Operator + Vals sets.String + WantErr field.ErrorList + }{ + { + Key: "x1", + Op: selection.In, + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values", + BadValue: []string{}, + }, + }, + }, + { + Key: "x2", + Op: selection.NotIn, + Vals: sets.NewString(), + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values", + BadValue: []string{}, + }, + }, + }, + { + Key: "x3", + Op: selection.In, + Vals: sets.NewString("foo"), + }, + { + Key: "x4", + Op: selection.NotIn, + Vals: sets.NewString("foo"), + }, + { + Key: "x5", + Op: selection.Equals, + Vals: sets.NewString("foo", "bar"), + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values", + BadValue: []string{"bar", "foo"}, + }, + }, + }, + { + Key: "x6", + Op: selection.Exists, + }, + { + Key: "x7", + Op: selection.DoesNotExist, + }, + { + Key: "x8", + Op: selection.Exists, + Vals: sets.NewString("foo"), + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values", + BadValue: []string{"foo"}, + }, + }, + }, + { + Key: "x9", + Op: selection.In, + Vals: sets.NewString("bar"), + }, + { + Key: "x10", + Op: selection.In, + Vals: sets.NewString("bar"), + }, + { + Key: "x11", + Op: selection.GreaterThan, + Vals: sets.NewString("1"), + }, + { + Key: "x12", + Op: selection.LessThan, + Vals: sets.NewString("6"), + }, + { + Key: "x13", + Op: selection.GreaterThan, + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values", + BadValue: []string{}, + }, + }, + }, + { + Key: "x14", + Op: selection.GreaterThan, + Vals: sets.NewString("bar"), + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values[0]", + BadValue: "bar", + }, + }, + }, + { + Key: "x15", + Op: selection.LessThan, + Vals: sets.NewString("bar"), + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values[0]", + BadValue: "bar", + }, + }, + }, + { + Key: strings.Repeat("a", 254), //breaks DNS rule that len(key) <= 253 + Op: selection.Exists, + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "key", + BadValue: strings.Repeat("a", 254), + }, + }, + }, + { + Key: "x16", + Op: selection.Equals, + Vals: sets.NewString(strings.Repeat("a", 254)), + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values[0][x16]", + BadValue: strings.Repeat("a", 254), + }, + }, + }, + { + Key: "x17", + Op: selection.Equals, + Vals: sets.NewString("a b"), + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values[0][x17]", + BadValue: "a b", + }, + }, + }, + { + Key: "x18", + Op: "unsupportedOp", + WantErr: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeNotSupported, + Field: "operator", + BadValue: selection.Operator("unsupportedOp"), + }, + }, + }, + } + for _, rc := range requirementConstructorTests { + _, err := NewRequirement(rc.Key, rc.Op, rc.Vals.List()) + if diff := cmp.Diff(rc.WantErr.ToAggregate(), err, ignoreDetail); diff != "" { + t.Errorf("NewRequirement test %v returned unexpected error (-want,+got):\n%s", rc.Key, diff) + } + } +} + +func TestToString(t *testing.T) { + var req Requirement + toStringTests := []struct { + In *internalSelector + Out string + Valid bool + }{ + + {&internalSelector{ + getRequirement("x", selection.In, sets.NewString("abc", "def"), t), + getRequirement("y", selection.NotIn, sets.NewString("jkl"), t), + getRequirement("z", selection.Exists, nil, t)}, + "x in (abc,def),y notin (jkl),z", true}, + {&internalSelector{ + getRequirement("x", selection.NotIn, sets.NewString("abc", "def"), t), + getRequirement("y", selection.NotEquals, sets.NewString("jkl"), t), + getRequirement("z", selection.DoesNotExist, nil, t)}, + "x notin (abc,def),y!=jkl,!z", true}, + {&internalSelector{ + getRequirement("x", selection.In, sets.NewString("abc", "def"), t), + req}, // adding empty req for the trailing ',' + "x in (abc,def),", false}, + {&internalSelector{ + getRequirement("x", selection.NotIn, sets.NewString("abc"), t), + getRequirement("y", selection.In, sets.NewString("jkl", "mno"), t), + getRequirement("z", selection.NotIn, sets.NewString(""), t)}, + "x notin (abc),y in (jkl,mno),z notin ()", true}, + {&internalSelector{ + getRequirement("x", selection.Equals, sets.NewString("abc"), t), + getRequirement("y", selection.DoubleEquals, sets.NewString("jkl"), t), + getRequirement("z", selection.NotEquals, sets.NewString("a"), t), + getRequirement("z", selection.Exists, nil, t)}, + "x=abc,y==jkl,z!=a,z", true}, + {&internalSelector{ + getRequirement("x", selection.GreaterThan, sets.NewString("2"), t), + getRequirement("y", selection.LessThan, sets.NewString("8"), t), + getRequirement("z", selection.Exists, nil, t)}, + "x>2,y<8,z", true}, + } + for _, ts := range toStringTests { + if out := ts.In.String(); out == "" && ts.Valid { + t.Errorf("%#v.String() => '%v' expected no error", ts.In, out) + } else if out != ts.Out { + t.Errorf("%#v.String() => '%v' want '%v'", ts.In, out, ts.Out) + } + } +} + +func TestRequirementSelectorMatching(t *testing.T) { + var req Requirement + labelSelectorMatchingTests := []struct { + Set Set + Sel Selector + Match bool + }{ + {Set{"x": "foo", "y": "baz"}, &internalSelector{ + req, + }, false}, + {Set{"x": "foo", "y": "baz"}, &internalSelector{ + getRequirement("x", selection.In, sets.NewString("foo"), t), + getRequirement("y", selection.NotIn, sets.NewString("alpha"), t), + }, true}, + {Set{"x": "foo", "y": "baz"}, &internalSelector{ + getRequirement("x", selection.In, sets.NewString("foo"), t), + getRequirement("y", selection.In, sets.NewString("alpha"), t), + }, false}, + {Set{"y": ""}, &internalSelector{ + getRequirement("x", selection.NotIn, sets.NewString(""), t), + getRequirement("y", selection.Exists, nil, t), + }, true}, + {Set{"y": ""}, &internalSelector{ + getRequirement("x", selection.DoesNotExist, nil, t), + getRequirement("y", selection.Exists, nil, t), + }, true}, + {Set{"y": ""}, &internalSelector{ + getRequirement("x", selection.NotIn, sets.NewString(""), t), + getRequirement("y", selection.DoesNotExist, nil, t), + }, false}, + {Set{"y": "baz"}, &internalSelector{ + getRequirement("x", selection.In, sets.NewString(""), t), + }, false}, + {Set{"z": "2"}, &internalSelector{ + getRequirement("z", selection.GreaterThan, sets.NewString("1"), t), + }, true}, + {Set{"z": "v2"}, &internalSelector{ + getRequirement("z", selection.GreaterThan, sets.NewString("1"), t), + }, false}, + } + for _, lsm := range labelSelectorMatchingTests { + if match := lsm.Sel.Matches(lsm.Set); match != lsm.Match { + t.Errorf("%+v.Matches(%#v) => %v, want %v", lsm.Sel, lsm.Set, match, lsm.Match) + } + } +} + +func TestSetSelectorParser(t *testing.T) { + setSelectorParserTests := []struct { + In string + Out Selector + Match bool + Valid bool + }{ + {"", NewSelector(), true, true}, + {"\rx", internalSelector{ + getRequirement("x", selection.Exists, nil, t), + }, true, true}, + {"this-is-a-dns.domain.com/key-with-dash", internalSelector{ + getRequirement("this-is-a-dns.domain.com/key-with-dash", selection.Exists, nil, t), + }, true, true}, + {"this-is-another-dns.domain.com/key-with-dash in (so,what)", internalSelector{ + getRequirement("this-is-another-dns.domain.com/key-with-dash", selection.In, sets.NewString("so", "what"), t), + }, true, true}, + {"0.1.2.domain/99 notin (10.10.100.1, tick.tack.clock)", internalSelector{ + getRequirement("0.1.2.domain/99", selection.NotIn, sets.NewString("10.10.100.1", "tick.tack.clock"), t), + }, true, true}, + {"foo in (abc)", internalSelector{ + getRequirement("foo", selection.In, sets.NewString("abc"), t), + }, true, true}, + {"x notin\n (abc)", internalSelector{ + getRequirement("x", selection.NotIn, sets.NewString("abc"), t), + }, true, true}, + {"x notin \t (abc,def)", internalSelector{ + getRequirement("x", selection.NotIn, sets.NewString("abc", "def"), t), + }, true, true}, + {"x in (abc,def)", internalSelector{ + getRequirement("x", selection.In, sets.NewString("abc", "def"), t), + }, true, true}, + {"x in (abc,)", internalSelector{ + getRequirement("x", selection.In, sets.NewString("abc", ""), t), + }, true, true}, + {"x in (abc,abc)", internalSelector{ + getRequirement("x", selection.In, sets.NewString("abc"), t), + }, true, true}, + {"x in ()", internalSelector{ + getRequirement("x", selection.In, sets.NewString(""), t), + }, true, true}, + {"x in (a,,)", internalSelector{ + getRequirement("x", selection.In, sets.NewString("a", ""), t), + }, true, true}, + {"x in (a,,,)", internalSelector{ + getRequirement("x", selection.In, sets.NewString("a", ""), t), + }, true, true}, + {"x in (a,,,,,,)", internalSelector{ + getRequirement("x", selection.In, sets.NewString("a", ""), t), + }, true, true}, + {"x in (a,,a,,a,,a,,)", internalSelector{ + getRequirement("x", selection.In, sets.NewString("a", ""), t), + }, true, true}, + {"x notin (abc,,def),bar,z in (),w", internalSelector{ + getRequirement("bar", selection.Exists, nil, t), + getRequirement("w", selection.Exists, nil, t), + getRequirement("x", selection.NotIn, sets.NewString("abc", "", "def"), t), + getRequirement("z", selection.In, sets.NewString(""), t), + }, true, true}, + {"x,y in (a)", internalSelector{ + getRequirement("y", selection.In, sets.NewString("a"), t), + getRequirement("x", selection.Exists, nil, t), + }, false, true}, + {"x=a", internalSelector{ + getRequirement("x", selection.Equals, sets.NewString("a"), t), + }, true, true}, + {"x>1", internalSelector{ + getRequirement("x", selection.GreaterThan, sets.NewString("1"), t), + }, true, true}, + {"x<7", internalSelector{ + getRequirement("x", selection.LessThan, sets.NewString("7"), t), + }, true, true}, + {"x=a,y!=b", internalSelector{ + getRequirement("x", selection.Equals, sets.NewString("a"), t), + getRequirement("y", selection.NotEquals, sets.NewString("b"), t), + }, true, true}, + {"x=a,y!=b,z in (h,i,j)", internalSelector{ + getRequirement("x", selection.Equals, sets.NewString("a"), t), + getRequirement("y", selection.NotEquals, sets.NewString("b"), t), + getRequirement("z", selection.In, sets.NewString("h", "i", "j"), t), + }, true, true}, + {"x=a||y=b", internalSelector{}, false, false}, + {"x,,y", nil, true, false}, + {",x,y", nil, true, false}, + {"x nott in (y)", nil, true, false}, + {"x notin ( )", internalSelector{ + getRequirement("x", selection.NotIn, sets.NewString(""), t), + }, true, true}, + {"x notin (, a)", internalSelector{ + getRequirement("x", selection.NotIn, sets.NewString("", "a"), t), + }, true, true}, + {"a in (xyz),", nil, true, false}, + {"a in (xyz)b notin ()", nil, true, false}, + {"a ", internalSelector{ + getRequirement("a", selection.Exists, nil, t), + }, true, true}, + {"a in (x,y,notin, z,in)", internalSelector{ + getRequirement("a", selection.In, sets.NewString("in", "notin", "x", "y", "z"), t), + }, true, true}, // operator 'in' inside list of identifiers + {"a in (xyz abc)", nil, false, false}, // no comma + {"a notin(", nil, true, false}, // bad formed + {"a (", nil, false, false}, // cpar + {"(", nil, false, false}, // opar + } + + for _, ssp := range setSelectorParserTests { + if sel, err := Parse(ssp.In); err != nil && ssp.Valid { + t.Errorf("Parse(%s) => %v expected no error", ssp.In, err) + } else if err == nil && !ssp.Valid { + t.Errorf("Parse(%s) => %+v expected error", ssp.In, sel) + } else if ssp.Match && !reflect.DeepEqual(sel, ssp.Out) { + t.Errorf("Parse(%s) => parse output '%#v' doesn't match '%#v' expected match", ssp.In, sel, ssp.Out) + } + } +} + +func getRequirement(key string, op selection.Operator, vals sets.String, t *testing.T) Requirement { + req, err := NewRequirement(key, op, vals.List()) + if err != nil { + t.Errorf("NewRequirement(%v, %v, %v) resulted in error:%v", key, op, vals, err) + return Requirement{} + } + return *req +} + +func TestAdd(t *testing.T) { + testCases := []struct { + name string + sel Selector + key string + operator selection.Operator + values []string + refSelector Selector + }{ + { + "keyInOperator", + internalSelector{}, + "key", + selection.In, + []string{"value"}, + internalSelector{Requirement{"key", selection.In, []string{"value"}}}, + }, + { + "keyEqualsOperator", + internalSelector{Requirement{"key", selection.In, []string{"value"}}}, + "key2", + selection.Equals, + []string{"value2"}, + internalSelector{ + Requirement{"key", selection.In, []string{"value"}}, + Requirement{"key2", selection.Equals, []string{"value2"}}, + }, + }, + } + for _, ts := range testCases { + req, err := NewRequirement(ts.key, ts.operator, ts.values) + if err != nil { + t.Errorf("%s - Unable to create labels.Requirement", ts.name) + } + ts.sel = ts.sel.Add(*req) + if !reflect.DeepEqual(ts.sel, ts.refSelector) { + t.Errorf("%s - Expected %v found %v", ts.name, ts.refSelector, ts.sel) + } + } +} + +func TestSafeSort(t *testing.T) { + tests := []struct { + name string + in []string + inCopy []string + want []string + }{ + { + name: "nil strings", + in: nil, + inCopy: nil, + want: nil, + }, + { + name: "ordered strings", + in: []string{"bar", "foo"}, + inCopy: []string{"bar", "foo"}, + want: []string{"bar", "foo"}, + }, + { + name: "unordered strings", + in: []string{"foo", "bar"}, + inCopy: []string{"foo", "bar"}, + want: []string{"bar", "foo"}, + }, + { + name: "duplicated strings", + in: []string{"foo", "bar", "foo", "bar"}, + inCopy: []string{"foo", "bar", "foo", "bar"}, + want: []string{"bar", "bar", "foo", "foo"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := safeSort(tt.in); !reflect.DeepEqual(got, tt.want) { + t.Errorf("safeSort() = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(tt.in, tt.inCopy) { + t.Errorf("after safeSort(), input = %v, want %v", tt.in, tt.inCopy) + } + }) + } +} + +func BenchmarkSelectorFromValidatedSet(b *testing.B) { + set := map[string]string{ + "foo": "foo", + "bar": "bar", + } + matchee := Set(map[string]string{ + "foo": "foo", + "bar": "bar", + "extra": "label", + }) + + for i := 0; i < b.N; i++ { + s := SelectorFromValidatedSet(set) + if s.Empty() { + b.Errorf("Unexpected selector") + } + if !s.Matches(matchee) { + b.Errorf("Unexpected match") + } + } +} + +func BenchmarkSetSelector(b *testing.B) { + set := map[string]string{ + "foo": "foo", + "bar": "bar", + } + matchee := Set(map[string]string{ + "foo": "foo", + "bar": "bar", + "extra": "label", + }) + + for i := 0; i < b.N; i++ { + s := ValidatedSetSelector(set) + if s.Empty() { + b.Errorf("Unexpected selector") + } + if !s.Matches(matchee) { + b.Errorf("Unexpected match") + } + } +} + +func TestSetSelectorString(t *testing.T) { + cases := []struct { + set Set + out string + }{ + { + Set{}, + "", + }, + { + Set{"app": "foo"}, + "app=foo", + }, + { + Set{"app": "foo", "a": "b"}, + "a=b,app=foo", + }, + } + + for _, tt := range cases { + t.Run(tt.out, func(t *testing.T) { + if got := ValidatedSetSelector(tt.set).String(); tt.out != got { + t.Fatalf("expected %v, got %v", tt.out, got) + } + }) + } +} + +func TestRequiresExactMatch(t *testing.T) { + testCases := []struct { + name string + sel Selector + label string + expectedFound bool + expectedValue string + }{ + { + name: "keyInOperatorExactMatch", + sel: internalSelector{Requirement{"key", selection.In, []string{"value"}}}, + label: "key", + expectedFound: true, + expectedValue: "value", + }, + { + name: "keyInOperatorNotExactMatch", + sel: internalSelector{Requirement{"key", selection.In, []string{"value", "value2"}}}, + label: "key", + expectedFound: false, + expectedValue: "", + }, + { + name: "keyInOperatorNotExactMatch", + sel: internalSelector{ + Requirement{"key", selection.In, []string{"value", "value1"}}, + Requirement{"key2", selection.In, []string{"value2"}}, + }, + label: "key2", + expectedFound: true, + expectedValue: "value2", + }, + { + name: "keyEqualOperatorExactMatch", + sel: internalSelector{Requirement{"key", selection.Equals, []string{"value"}}}, + label: "key", + expectedFound: true, + expectedValue: "value", + }, + { + name: "keyDoubleEqualOperatorExactMatch", + sel: internalSelector{Requirement{"key", selection.DoubleEquals, []string{"value"}}}, + label: "key", + expectedFound: true, + expectedValue: "value", + }, + { + name: "keyNotEqualOperatorExactMatch", + sel: internalSelector{Requirement{"key", selection.NotEquals, []string{"value"}}}, + label: "key", + expectedFound: false, + expectedValue: "", + }, + { + name: "keyEqualOperatorExactMatchFirst", + sel: internalSelector{ + Requirement{"key", selection.In, []string{"value"}}, + Requirement{"key2", selection.In, []string{"value2"}}, + }, + label: "key", + expectedFound: true, + expectedValue: "value", + }, + } + for _, ts := range testCases { + t.Run(ts.name, func(t *testing.T) { + value, found := ts.sel.RequiresExactMatch(ts.label) + if found != ts.expectedFound { + t.Errorf("Expected match %v, found %v", ts.expectedFound, found) + } + if found && value != ts.expectedValue { + t.Errorf("Expected value %v, found %v", ts.expectedValue, value) + } + + }) + } +} + +func TestValidatedSelectorFromSet(t *testing.T) { + tests := []struct { + name string + input Set + expectedSelector internalSelector + expectedError field.ErrorList + }{ + { + name: "Simple Set, no error", + input: Set{"key": "val"}, + expectedSelector: internalSelector{ + Requirement{ + key: "key", + operator: selection.Equals, + strValues: []string{"val"}, + }, + }, + }, + { + name: "Invalid Set, value too long", + input: Set{"Key": "axahm2EJ8Phiephe2eixohbee9eGeiyees1thuozi1xoh0GiuH3diewi8iem7Nui"}, + expectedError: field.ErrorList{ + &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "values[0][Key]", + BadValue: "axahm2EJ8Phiephe2eixohbee9eGeiyees1thuozi1xoh0GiuH3diewi8iem7Nui", + }, + }, + }, + } + + for _, tc := range tests { + selector, err := ValidatedSelectorFromSet(tc.input) + if diff := cmp.Diff(tc.expectedError.ToAggregate(), err, ignoreDetail); diff != "" { + t.Errorf("ValidatedSelectorFromSet %#v returned unexpected error (-want,+got):\n%s", tc.name, diff) + } + if err == nil { + if diff := cmp.Diff(tc.expectedSelector, selector); diff != "" { + t.Errorf("ValidatedSelectorFromSet %#v returned unexpected selector (-want,+got):\n%s", tc.name, diff) + } + } + } +} + +func BenchmarkRequirementString(b *testing.B) { + r := Requirement{ + key: "environment", + operator: selection.NotIn, + strValues: []string{ + "dev", + }, + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + if r.String() != "environment notin (dev)" { + b.Errorf("Unexpected Requirement string") + } + } +} + +func BenchmarkRequirementMatches(b *testing.B) { + r := Requirement{ + key: "environment", + operator: selection.NotIn, + strValues: []string{ + "dev", + }, + } + labels := Set(map[string]string{ + "key": "value", + "environment": "dev", + }) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + r.Matches(labels) + } +} + +func TestRequirementEqual(t *testing.T) { + tests := []struct { + name string + x, y *Requirement + want bool + }{ + { + name: "same requirements should be equal", + x: &Requirement{ + key: "key", + operator: selection.Equals, + strValues: []string{"foo", "bar"}, + }, + y: &Requirement{ + key: "key", + operator: selection.Equals, + strValues: []string{"foo", "bar"}, + }, + want: true, + }, + { + name: "requirements with different keys should not be equal", + x: &Requirement{ + key: "key1", + operator: selection.Equals, + strValues: []string{"foo", "bar"}, + }, + y: &Requirement{ + key: "key2", + operator: selection.Equals, + strValues: []string{"foo", "bar"}, + }, + want: false, + }, + { + name: "requirements with different operators should not be equal", + x: &Requirement{ + key: "key", + operator: selection.Equals, + strValues: []string{"foo", "bar"}, + }, + y: &Requirement{ + key: "key", + operator: selection.In, + strValues: []string{"foo", "bar"}, + }, + want: false, + }, + { + name: "requirements with different values should not be equal", + x: &Requirement{ + key: "key", + operator: selection.Equals, + strValues: []string{"foo", "bar"}, + }, + y: &Requirement{ + key: "key", + operator: selection.Equals, + strValues: []string{"foobar"}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := cmp.Equal(tt.x, tt.y); got != tt.want { + t.Errorf("cmp.Equal() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatchesNothing(t *testing.T) { + tests := []struct { + name string + selector string + set map[string]string + labelSelector Selector + want bool + }{ + { + name: "MatchNothing should match Nothing()", + labelSelector: Nothing(), + want: true, + }, + { + name: "MatchNothing should match sharedNothingSelector", + labelSelector: sharedNothingSelector, + want: true, + }, + { + name: "MatchNothing should not match Everything()", + labelSelector: Everything(), + want: false, + }, + { + name: "MatchNothing should not match sharedEverythingSelector", + labelSelector: sharedEverythingSelector, + want: false, + }, + { + name: "MatchNothing should not match empty set", + set: map[string]string{}, + want: false, + }, + { + name: "MatchNothing should not match non-empty set", + set: map[string]string{"key": "value"}, + want: false, + }, + { + name: "MatchNothing should not match empty selector", + selector: "", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - exists", + selector: "a", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - not exists", + selector: "!a", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - equals", + selector: "a=b", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - not equals", + selector: "a!=b", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - in", + selector: "a in (b)", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - notin", + selector: "a notin (b)", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - conflict exists and not exists", + selector: "a,!a", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - conflict equals and not equals", + selector: "a=b,a!=b", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - conflict in and notin", + selector: "a in (b),a notin (b)", + want: false, + }, + } + + for i := 0; i < len(tests); i++ { + if tests[i].labelSelector != nil { + expectMatchNothing(t, tests[i].labelSelector, tests[i].want) + } else if tests[i].set != nil { + expectMatchNothing(t, SelectorFromSet(tests[i].set), tests[i].want) + } else { + selector, err := Parse(tests[i].selector) + if err != nil { + t.Errorf("Unable to parse %v as a selector.\n", selector) + } + expectMatchNothing(t, selector, tests[i].want) + } + } +} + +func expectMatchNothing(t *testing.T, selector Selector, want bool) { + if MatchesNothing(selector) != want { + t.Errorf("Wanted %s to MatchNothing '%t', but it did not.\n", selector, want) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/zz_generated.deepcopy.go new file mode 100644 index 0000000000..fdf4c31e1e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/labels/zz_generated.deepcopy.go @@ -0,0 +1,43 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package labels + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Requirement) DeepCopyInto(out *Requirement) { + *out = *in + if in.strValues != nil { + in, out := &in.strValues, &out.strValues + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Requirement. +func (in *Requirement) DeepCopy() *Requirement { + if in == nil { + return nil + } + out := new(Requirement) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/allocator.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/allocator.go new file mode 100644 index 0000000000..8bf22ae8ac --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/allocator.go @@ -0,0 +1,76 @@ +/* +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 runtime + +import ( + "sync" +) + +// AllocatorPool simply stores Allocator objects to avoid additional memory allocations +// by caching created but unused items for later reuse, relieving pressure on the garbage collector. +// +// Usage: +// +// memoryAllocator := runtime.AllocatorPool.Get().(*runtime.Allocator) +// defer runtime.AllocatorPool.Put(memoryAllocator) +// +// A note for future: +// +// consider introducing multiple pools for storing buffers of different sizes +// perhaps this could allow us to be more efficient. +var AllocatorPool = sync.Pool{ + New: func() interface{} { + return &Allocator{} + }, +} + +// Allocator knows how to allocate memory +// It exists to make the cost of object serialization cheaper. +// In some cases, it allows for allocating memory only once and then reusing it. +// This approach puts less load on GC and leads to less fragmented memory in general. +type Allocator struct { + buf []byte +} + +var _ MemoryAllocator = &Allocator{} + +// Allocate reserves memory for n bytes only if the underlying array doesn't have enough capacity +// otherwise it returns previously allocated block of memory. +// +// Note that the returned array is not zeroed, it is the caller's +// responsibility to clean the memory if needed. +func (a *Allocator) Allocate(n uint64) []byte { + if uint64(cap(a.buf)) >= n { + a.buf = a.buf[:n] + return a.buf + } + // grow the buffer + size := uint64(2*cap(a.buf)) + n + a.buf = make([]byte, size) + a.buf = a.buf[:n] + return a.buf +} + +// SimpleAllocator a wrapper around make([]byte) +// conforms to the MemoryAllocator interface +type SimpleAllocator struct{} + +var _ MemoryAllocator = &SimpleAllocator{} + +func (sa *SimpleAllocator) Allocate(n uint64) []byte { + return make([]byte, n) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/allocator_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/allocator_test.go new file mode 100644 index 0000000000..067a5dda5e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/allocator_test.go @@ -0,0 +1,78 @@ +/* +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 runtime + +import ( + "math/rand" + "testing" +) + +func TestAllocatorRandomInputs(t *testing.T) { + maxBytes := 5 * 1000000 // 5 MB + iterations := rand.Intn(10000) + 10 + target := &Allocator{} + + for i := 0; i < iterations; i++ { + bytesToAllocate := rand.Intn(maxBytes) + buff := target.Allocate(uint64(bytesToAllocate)) + if cap(buff) < bytesToAllocate { + t.Fatalf("expected the buffer to allocate: %v bytes whereas it allocated: %v bytes", bytesToAllocate, cap(buff)) + } + if len(buff) != bytesToAllocate { + t.Fatalf("unexpected length of the buffer, expected: %v, got: %v", bytesToAllocate, len(buff)) + } + } +} + +func TestAllocatorNeverShrinks(t *testing.T) { + target := &Allocator{} + initialSize := 1000000 // 1MB + initialBuff := target.Allocate(uint64(initialSize)) + if cap(initialBuff) < initialSize { + t.Fatalf("unexpected size of the buffer, expected at least 1MB, got: %v", cap(initialBuff)) + } + + for i := initialSize; i > 0; i = i / 10 { + newBuff := target.Allocate(uint64(i)) + if cap(newBuff) < initialSize { + t.Fatalf("allocator is now allowed to shrink memory") + } + if len(newBuff) != i { + t.Fatalf("unexpected length of the buffer, expected: %v, got: %v", i, len(newBuff)) + } + } +} + +func TestAllocatorZero(t *testing.T) { + target := &Allocator{} + initialSize := 1000000 // 1MB + buff := target.Allocate(uint64(initialSize)) + if cap(buff) < initialSize { + t.Fatalf("unexpected size of the buffer, expected at least 1MB, got: %v", cap(buff)) + } + if len(buff) != initialSize { + t.Fatalf("unexpected length of the buffer, expected: %v, got: %v", initialSize, len(buff)) + } + + buff = target.Allocate(0) + if cap(buff) < initialSize { + t.Fatalf("unexpected size of the buffer, expected at least 1MB, got: %v", cap(buff)) + } + if len(buff) != 0 { + t.Fatalf("unexpected length of the buffer, expected: 0, got: %v", len(buff)) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec.go new file mode 100644 index 0000000000..654835b3ed --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec.go @@ -0,0 +1,398 @@ +/* +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 runtime + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/url" + "reflect" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/conversion/queryparams" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" +) + +// codec binds an encoder and decoder. +type codec struct { + Encoder + Decoder +} + +// NewCodec creates a Codec from an Encoder and Decoder. +func NewCodec(e Encoder, d Decoder) Codec { + return codec{e, d} +} + +// Encode is a convenience wrapper for encoding to a []byte from an Encoder +func Encode(e Encoder, obj Object) ([]byte, error) { + buf := &bytes.Buffer{} + if err := e.Encode(obj, buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// Decode is a convenience wrapper for decoding data into an Object. +func Decode(d Decoder, data []byte) (Object, error) { + obj, _, err := d.Decode(data, nil, nil) + return obj, err +} + +// DecodeInto performs a Decode into the provided object. +func DecodeInto(d Decoder, data []byte, into Object) error { + out, gvk, err := d.Decode(data, nil, into) + if err != nil { + return err + } + if out != into { + return fmt.Errorf("unable to decode %s into %v", gvk, reflect.TypeOf(into)) + } + return nil +} + +// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. +func EncodeOrDie(e Encoder, obj Object) string { + bytes, err := Encode(e, obj) + if err != nil { + panic(err) + } + return string(bytes) +} + +// UseOrCreateObject returns obj if the canonical ObjectKind returned by the provided typer matches gvk, or +// invokes the ObjectCreator to instantiate a new gvk. Returns an error if the typer cannot find the object. +func UseOrCreateObject(t ObjectTyper, c ObjectCreater, gvk schema.GroupVersionKind, obj Object) (Object, error) { + if obj != nil { + kinds, _, err := t.ObjectKinds(obj) + if err != nil { + return nil, err + } + for _, kind := range kinds { + if gvk == kind { + return obj, nil + } + } + } + return c.New(gvk) +} + +// NoopEncoder converts an Decoder to a Serializer or Codec for code that expects them but only uses decoding. +type NoopEncoder struct { + Decoder +} + +var _ Serializer = NoopEncoder{} + +const noopEncoderIdentifier Identifier = "noop" + +func (n NoopEncoder) Encode(obj Object, w io.Writer) error { + // There is no need to handle runtime.CacheableObject, as we don't + // process the obj at all. + return fmt.Errorf("encoding is not allowed for this codec: %v", reflect.TypeOf(n.Decoder)) +} + +// Identifier implements runtime.Encoder interface. +func (n NoopEncoder) Identifier() Identifier { + return noopEncoderIdentifier +} + +// NoopDecoder converts an Encoder to a Serializer or Codec for code that expects them but only uses encoding. +type NoopDecoder struct { + Encoder +} + +var _ Serializer = NoopDecoder{} + +func (n NoopDecoder) Decode(data []byte, gvk *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) { + return nil, nil, fmt.Errorf("decoding is not allowed for this codec: %v", reflect.TypeOf(n.Encoder)) +} + +// NewParameterCodec creates a ParameterCodec capable of transforming url values into versioned objects and back. +func NewParameterCodec(scheme *Scheme) ParameterCodec { + return ¶meterCodec{ + typer: scheme, + convertor: scheme, + creator: scheme, + defaulter: scheme, + } +} + +// parameterCodec implements conversion to and from query parameters and objects. +type parameterCodec struct { + typer ObjectTyper + convertor ObjectConvertor + creator ObjectCreater + defaulter ObjectDefaulter +} + +var _ ParameterCodec = ¶meterCodec{} + +// DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then +// converts that object to into (if necessary). Returns an error if the operation cannot be completed. +func (c *parameterCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into Object) error { + if len(parameters) == 0 { + return nil + } + targetGVKs, _, err := c.typer.ObjectKinds(into) + if err != nil { + return err + } + for i := range targetGVKs { + if targetGVKs[i].GroupVersion() == from { + if err := c.convertor.Convert(¶meters, into, nil); err != nil { + return err + } + // in the case where we going into the same object we're receiving, default on the outbound object + if c.defaulter != nil { + c.defaulter.Default(into) + } + return nil + } + } + + input, err := c.creator.New(from.WithKind(targetGVKs[0].Kind)) + if err != nil { + return err + } + if err := c.convertor.Convert(¶meters, input, nil); err != nil { + return err + } + // if we have defaulter, default the input before converting to output + if c.defaulter != nil { + c.defaulter.Default(input) + } + return c.convertor.Convert(input, into, nil) +} + +// EncodeParameters converts the provided object into the to version, then converts that object to url.Values. +// Returns an error if conversion is not possible. +func (c *parameterCodec) EncodeParameters(obj Object, to schema.GroupVersion) (url.Values, error) { + gvks, _, err := c.typer.ObjectKinds(obj) + if err != nil { + return nil, err + } + gvk := gvks[0] + if to != gvk.GroupVersion() { + out, err := c.convertor.ConvertToVersion(obj, to) + if err != nil { + return nil, err + } + obj = out + } + return queryparams.Convert(obj) +} + +type base64Serializer struct { + Encoder + Decoder + + identifier Identifier +} + +func NewBase64Serializer(e Encoder, d Decoder) Serializer { + return &base64Serializer{ + Encoder: e, + Decoder: d, + identifier: identifier(e), + } +} + +func identifier(e Encoder) Identifier { + result := map[string]string{ + "name": "base64", + } + if e != nil { + result["encoder"] = string(e.Identifier()) + } + identifier, err := json.Marshal(result) + if err != nil { + //nolint:logcheck // Should not be reached. + klog.Fatalf("Failed marshaling identifier for base64Serializer: %v", err) + } + return Identifier(identifier) +} + +func (s base64Serializer) Encode(obj Object, stream io.Writer) error { + if co, ok := obj.(CacheableObject); ok { + return co.CacheEncode(s.Identifier(), s.doEncode, stream) + } + return s.doEncode(obj, stream) +} + +func (s base64Serializer) doEncode(obj Object, stream io.Writer) error { + e := base64.NewEncoder(base64.StdEncoding, stream) + err := s.Encoder.Encode(obj, e) + e.Close() + return err +} + +// Identifier implements runtime.Encoder interface. +func (s base64Serializer) Identifier() Identifier { + return s.identifier +} + +func (s base64Serializer) Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) { + out := make([]byte, base64.StdEncoding.DecodedLen(len(data))) + n, err := base64.StdEncoding.Decode(out, data) + if err != nil { + return nil, nil, err + } + return s.Decoder.Decode(out[:n], defaults, into) +} + +// SerializerInfoForMediaType returns the first info in types that has a matching media type (which cannot +// include media-type parameters), or the first info with an empty media type, or false if no type matches. +func SerializerInfoForMediaType(types []SerializerInfo, mediaType string) (SerializerInfo, bool) { + for _, info := range types { + if info.MediaType == mediaType { + return info, true + } + } + for _, info := range types { + if len(info.MediaType) == 0 { + return info, true + } + } + return SerializerInfo{}, false +} + +var ( + // InternalGroupVersioner will always prefer the internal version for a given group version kind. + InternalGroupVersioner GroupVersioner = internalGroupVersioner{} + // DisabledGroupVersioner will reject all kinds passed to it. + DisabledGroupVersioner GroupVersioner = disabledGroupVersioner{} +) + +const ( + internalGroupVersionerIdentifier = "internal" + disabledGroupVersionerIdentifier = "disabled" +) + +type internalGroupVersioner struct{} + +// KindForGroupVersionKinds returns an internal Kind if one is found, or converts the first provided kind to the internal version. +func (internalGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { + for _, kind := range kinds { + if kind.Version == APIVersionInternal { + return kind, true + } + } + for _, kind := range kinds { + return schema.GroupVersionKind{Group: kind.Group, Version: APIVersionInternal, Kind: kind.Kind}, true + } + return schema.GroupVersionKind{}, false +} + +// Identifier implements GroupVersioner interface. +func (internalGroupVersioner) Identifier() string { + return internalGroupVersionerIdentifier +} + +type disabledGroupVersioner struct{} + +// KindForGroupVersionKinds returns false for any input. +func (disabledGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { + return schema.GroupVersionKind{}, false +} + +// Identifier implements GroupVersioner interface. +func (disabledGroupVersioner) Identifier() string { + return disabledGroupVersionerIdentifier +} + +// Assert that schema.GroupVersion and GroupVersions implement GroupVersioner +var _ GroupVersioner = schema.GroupVersion{} +var _ GroupVersioner = schema.GroupVersions{} +var _ GroupVersioner = multiGroupVersioner{} + +type multiGroupVersioner struct { + target schema.GroupVersion + acceptedGroupKinds []schema.GroupKind + coerce bool +} + +// NewMultiGroupVersioner returns the provided group version for any kind that matches one of the provided group kinds. +// Kind may be empty in the provided group kind, in which case any kind will match. +func NewMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKind) GroupVersioner { + if len(groupKinds) == 0 || (len(groupKinds) == 1 && groupKinds[0].Group == gv.Group) { + return gv + } + return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds} +} + +// NewCoercingMultiGroupVersioner returns the provided group version for any incoming kind. +// Incoming kinds that match the provided groupKinds are preferred. +// Kind may be empty in the provided group kind, in which case any kind will match. +// Examples: +// +// gv=mygroup/__internal, groupKinds=mygroup/Foo, anothergroup/Bar +// KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group/kind) +// +// gv=mygroup/__internal, groupKinds=mygroup, anothergroup +// KindForGroupVersionKinds(yetanother/v1/Baz, anothergroup/v1/Bar) -> mygroup/__internal/Bar (matched preferred group) +// +// gv=mygroup/__internal, groupKinds=mygroup, anothergroup +// KindForGroupVersionKinds(yetanother/v1/Baz, yetanother/v1/Bar) -> mygroup/__internal/Baz (no preferred group/kind match, uses first kind in list) +func NewCoercingMultiGroupVersioner(gv schema.GroupVersion, groupKinds ...schema.GroupKind) GroupVersioner { + return multiGroupVersioner{target: gv, acceptedGroupKinds: groupKinds, coerce: true} +} + +// KindForGroupVersionKinds returns the target group version if any kind matches any of the original group kinds. It will +// use the originating kind where possible. +func (v multiGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { + for _, src := range kinds { + for _, kind := range v.acceptedGroupKinds { + if kind.Group != src.Group { + continue + } + if len(kind.Kind) > 0 && kind.Kind != src.Kind { + continue + } + return v.target.WithKind(src.Kind), true + } + } + if v.coerce && len(kinds) > 0 { + return v.target.WithKind(kinds[0].Kind), true + } + return schema.GroupVersionKind{}, false +} + +// Identifier implements GroupVersioner interface. +func (v multiGroupVersioner) Identifier() string { + groupKinds := make([]string, 0, len(v.acceptedGroupKinds)) + for _, gk := range v.acceptedGroupKinds { + groupKinds = append(groupKinds, gk.String()) + } + result := map[string]string{ + "name": "multi", + "target": v.target.String(), + "accepted": strings.Join(groupKinds, ","), + "coerce": strconv.FormatBool(v.coerce), + } + identifier, err := json.Marshal(result) + if err != nil { + //nolint:logcheck // Should not be reached. + klog.Fatalf("Failed marshaling Identifier for %#v: %v", v, err) + } + return string(identifier) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec_check.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec_check.go new file mode 100644 index 0000000000..e884007766 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec_check.go @@ -0,0 +1,56 @@ +/* +Copyright 2016 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 runtime + +import ( + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/json" +) + +// CheckCodec makes sure that the codec can encode objects like internalType, +// decode all of the external types listed, and also decode them into the given +// object. (Will modify internalObject.) (Assumes JSON serialization.) +// TODO: verify that the correct external version is chosen on encode... +func CheckCodec(c Codec, internalType Object, externalTypes ...schema.GroupVersionKind) error { + if _, err := Encode(c, internalType); err != nil { + return fmt.Errorf("internal type not encodable: %v", err) + } + for _, et := range externalTypes { + typeMeta := TypeMeta{ + Kind: et.Kind, + APIVersion: et.GroupVersion().String(), + } + exBytes, err := json.Marshal(&typeMeta) + if err != nil { + return err + } + obj, err := Decode(c, exBytes) + if err != nil { + return fmt.Errorf("external type %s not interpretable: %v", et, err) + } + if reflect.TypeOf(obj) != reflect.TypeOf(internalType) { + return fmt.Errorf("decode of external type %s produced: %#v", et, obj) + } + if err = DecodeInto(c, exBytes, internalType); err != nil { + return fmt.Errorf("external type %s not convertible to internal type: %v", et, err) + } + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec_test.go new file mode 100644 index 0000000000..9425db212a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/codec_test.go @@ -0,0 +1,104 @@ +/* +Copyright 2019 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 runtime_test + +import ( + "io" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" +) + +func gv(group, version string) schema.GroupVersion { + return schema.GroupVersion{Group: group, Version: version} +} +func gvk(group, version, kind string) schema.GroupVersionKind { + return schema.GroupVersionKind{Group: group, Version: version, Kind: kind} +} +func gk(group, kind string) schema.GroupKind { + return schema.GroupKind{Group: group, Kind: kind} +} + +func TestCoercingMultiGroupVersioner(t *testing.T) { + testcases := []struct { + name string + target schema.GroupVersion + preferredKinds []schema.GroupKind + kinds []schema.GroupVersionKind + expectKind schema.GroupVersionKind + expectedId string + }{ + { + name: "matched preferred group/kind", + target: gv("mygroup", "__internal"), + preferredKinds: []schema.GroupKind{gk("mygroup", "Foo"), gk("anothergroup", "Bar")}, + kinds: []schema.GroupVersionKind{gvk("yetanother", "v1", "Baz"), gvk("anothergroup", "v1", "Bar")}, + expectKind: gvk("mygroup", "__internal", "Bar"), + expectedId: "{\"accepted\":\"Foo.mygroup,Bar.anothergroup\",\"coerce\":\"true\",\"name\":\"multi\",\"target\":\"mygroup/__internal\"}", + }, + { + name: "matched preferred group", + target: gv("mygroup", "__internal"), + preferredKinds: []schema.GroupKind{gk("mygroup", ""), gk("anothergroup", "")}, + kinds: []schema.GroupVersionKind{gvk("yetanother", "v1", "Baz"), gvk("anothergroup", "v1", "Bar")}, + expectKind: gvk("mygroup", "__internal", "Bar"), + expectedId: "{\"accepted\":\".mygroup,.anothergroup\",\"coerce\":\"true\",\"name\":\"multi\",\"target\":\"mygroup/__internal\"}", + }, + { + name: "no preferred group/kind match, uses first kind in list", + target: gv("mygroup", "__internal"), + preferredKinds: []schema.GroupKind{gk("mygroup", ""), gk("anothergroup", "")}, + kinds: []schema.GroupVersionKind{gvk("yetanother", "v1", "Baz"), gvk("yetanother", "v1", "Bar")}, + expectKind: gvk("mygroup", "__internal", "Baz"), + expectedId: "{\"accepted\":\".mygroup,.anothergroup\",\"coerce\":\"true\",\"name\":\"multi\",\"target\":\"mygroup/__internal\"}", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + v := runtime.NewCoercingMultiGroupVersioner(tc.target, tc.preferredKinds...) + kind, ok := v.KindForGroupVersionKinds(tc.kinds) + if !ok { + t.Error("got no kind") + } + if kind != tc.expectKind { + t.Errorf("expected %#v, got %#v", tc.expectKind, kind) + } + if e, a := tc.expectedId, v.Identifier(); e != a { + t.Errorf("unexpected identifier: %s, expected: %s", a, e) + } + }) + } +} + +type mockEncoder struct{} + +func (m *mockEncoder) Encode(obj runtime.Object, w io.Writer) error { + _, err := w.Write([]byte("mock-result")) + return err +} + +func (m *mockEncoder) Identifier() runtime.Identifier { + return runtime.Identifier("mock-identifier") +} + +func TestCacheableObject(t *testing.T) { + serializer := runtime.NewBase64Serializer(&mockEncoder{}, nil) + runtimetesting.CacheableObjectTest(t, serializer) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/conversion.go new file mode 100644 index 0000000000..7cef382de2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/conversion.go @@ -0,0 +1,183 @@ +/* +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 runtime defines conversions between generic types and structs to map query strings +// to struct objects. +package runtime + +import ( + "fmt" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/conversion" +) + +// DefaultMetaV1FieldSelectorConversion auto-accepts metav1 values for name and namespace. +// A cluster scoped resource specifying namespace empty works fine and specifying a particular +// namespace will return no results, as expected. +func DefaultMetaV1FieldSelectorConversion(label, value string) (string, string, error) { + switch label { + case "metadata.name": + return label, value, nil + case "metadata.namespace": + return label, value, nil + default: + return "", "", fmt.Errorf("%q is not a known field selector: only %q, %q", label, "metadata.name", "metadata.namespace") + } +} + +func Convert_Slice_string_To_string(in *[]string, out *string, s conversion.Scope) error { + if len(*in) == 0 { + *out = "" + return nil + } + *out = (*in)[0] + return nil +} + +func Convert_Slice_string_To_int(in *[]string, out *int, s conversion.Scope) error { + if len(*in) == 0 { + *out = 0 + return nil + } + str := (*in)[0] + i, err := strconv.Atoi(str) + if err != nil { + return err + } + *out = i + return nil +} + +// Convert_Slice_string_To_bool will convert a string parameter to boolean. +// Only the absence of a value (i.e. zero-length slice), a value of "false", or a +// value of "0" resolve to false. +// Any other value (including empty string) resolves to true. +func Convert_Slice_string_To_bool(in *[]string, out *bool, s conversion.Scope) error { + if len(*in) == 0 { + *out = false + return nil + } + switch { + case (*in)[0] == "0", strings.EqualFold((*in)[0], "false"): + *out = false + default: + *out = true + } + return nil +} + +// Convert_Slice_string_To_bool will convert a string parameter to boolean. +// Only the absence of a value (i.e. zero-length slice), a value of "false", or a +// value of "0" resolve to false. +// Any other value (including empty string) resolves to true. +func Convert_Slice_string_To_Pointer_bool(in *[]string, out **bool, s conversion.Scope) error { + if len(*in) == 0 { + boolVar := false + *out = &boolVar + return nil + } + switch { + case (*in)[0] == "0", strings.EqualFold((*in)[0], "false"): + boolVar := false + *out = &boolVar + default: + boolVar := true + *out = &boolVar + } + return nil +} + +func string_to_int64(in string) (int64, error) { + return strconv.ParseInt(in, 10, 64) +} + +func Convert_string_To_int64(in *string, out *int64, s conversion.Scope) error { + if in == nil { + *out = 0 + return nil + } + i, err := string_to_int64(*in) + if err != nil { + return err + } + *out = i + return nil +} + +func Convert_Slice_string_To_int64(in *[]string, out *int64, s conversion.Scope) error { + if len(*in) == 0 { + *out = 0 + return nil + } + i, err := string_to_int64((*in)[0]) + if err != nil { + return err + } + *out = i + return nil +} + +func Convert_string_To_Pointer_int64(in *string, out **int64, s conversion.Scope) error { + if in == nil { + *out = nil + return nil + } + i, err := string_to_int64(*in) + if err != nil { + return err + } + *out = &i + return nil +} + +func Convert_Slice_string_To_Pointer_int64(in *[]string, out **int64, s conversion.Scope) error { + if len(*in) == 0 { + *out = nil + return nil + } + i, err := string_to_int64((*in)[0]) + if err != nil { + return err + } + *out = &i + return nil +} + +func RegisterStringConversions(s *Scheme) error { + if err := s.AddConversionFunc((*[]string)(nil), (*string)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_string(a.(*[]string), b.(*string), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (*int)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_int(a.(*[]string), b.(*int), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (*bool)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_bool(a.(*[]string), b.(*bool), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*[]string)(nil), (*int64)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Slice_string_To_int64(a.(*[]string), b.(*int64), scope) + }); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter.go new file mode 100644 index 0000000000..5aa118f58f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter.go @@ -0,0 +1,887 @@ +/* +Copyright 2017 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 runtime + +import ( + encodingjson "encoding/json" + "fmt" + "math" + "os" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "sigs.k8s.io/structured-merge-diff/v6/value" + + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/util/json" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + "k8s.io/klog/v2" +) + +// UnstructuredConverter is an interface for converting between interface{} +// and map[string]interface representation. +type UnstructuredConverter interface { + ToUnstructured(obj interface{}) (map[string]interface{}, error) + FromUnstructured(u map[string]interface{}, obj interface{}) error +} + +type structField struct { + structType reflect.Type + field int +} + +type fieldInfo struct { + name string + nameValue reflect.Value + omitempty bool + omitzero func(dv reflect.Value) bool +} + +type fieldsCacheMap map[structField]*fieldInfo + +type fieldsCache struct { + sync.Mutex + value atomic.Value +} + +func newFieldsCache() *fieldsCache { + cache := &fieldsCache{} + cache.value.Store(make(fieldsCacheMap)) + return cache +} + +var ( + mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) + stringType = reflect.TypeOf(string("")) + fieldCache = newFieldsCache() + + // DefaultUnstructuredConverter performs unstructured to Go typed object conversions. + DefaultUnstructuredConverter = &unstructuredConverter{ + mismatchDetection: parseBool(os.Getenv("KUBE_PATCH_CONVERSION_DETECTOR")), + comparison: conversion.EqualitiesOrDie( + func(a, b time.Time) bool { + return a.UTC() == b.UTC() + }, + ), + } +) + +func parseBool(key string) bool { + if len(key) == 0 { + return false + } + value, err := strconv.ParseBool(key) + if err != nil { + utilruntime.HandleError(fmt.Errorf("couldn't parse '%s' as bool for unstructured mismatch detection", key)) + } + return value +} + +// unstructuredConverter knows how to convert between interface{} and +// Unstructured in both ways. +type unstructuredConverter struct { + // If true, we will be additionally running conversion via json + // to ensure that the result is true. + // This is supposed to be set only in tests. + mismatchDetection bool + // comparison is the default test logic used to compare + comparison conversion.Equalities +} + +// NewTestUnstructuredConverter creates an UnstructuredConverter that accepts JSON typed maps and translates them +// to Go types via reflection. It performs mismatch detection automatically and is intended for use by external +// test tools. Use DefaultUnstructuredConverter if you do not explicitly need mismatch detection. +func NewTestUnstructuredConverter(comparison conversion.Equalities) UnstructuredConverter { + return NewTestUnstructuredConverterWithValidation(comparison) +} + +// NewTestUnstrucutredConverterWithValidation allows for access to +// FromUnstructuredWithValidation from within tests. +func NewTestUnstructuredConverterWithValidation(comparison conversion.Equalities) *unstructuredConverter { + return &unstructuredConverter{ + mismatchDetection: true, + comparison: comparison, + } +} + +// fromUnstructuredContext provides options for informing the converter +// the state of its recursive walk through the conversion process. +type fromUnstructuredContext struct { + // isInlined indicates whether the converter is currently in + // an inlined field or not to determine whether it should + // validate the matchedKeys yet or only collect them. + // This should only be set from `structFromUnstructured` + isInlined bool + // matchedKeys is a stack of the set of all fields that exist in the + // concrete go type of the object being converted into. + // This should only be manipulated via `pushMatchedKeyTracker`, + // `recordMatchedKey`, or `popAndVerifyMatchedKeys` + matchedKeys []map[string]struct{} + // parentPath collects the path that the conversion + // takes as it traverses the unstructured json map. + // It is used to report the full path to any unknown + // fields that the converter encounters. + parentPath []string + // returnUnknownFields indicates whether or not + // unknown field errors should be collected and + // returned to the caller + returnUnknownFields bool + // unknownFieldErrors are the collection of + // the full path to each unknown field in the + // object. + unknownFieldErrors []error +} + +// pushMatchedKeyTracker adds a placeholder set for tracking +// matched keys for the given level. This should only be +// called from `structFromUnstructured`. +func (c *fromUnstructuredContext) pushMatchedKeyTracker() { + if !c.returnUnknownFields { + return + } + + c.matchedKeys = append(c.matchedKeys, nil) +} + +// recordMatchedKey initializes the last element of matchedKeys +// (if needed) and sets 'key'. This should only be called from +// `structFromUnstructured`. +func (c *fromUnstructuredContext) recordMatchedKey(key string) { + if !c.returnUnknownFields { + return + } + + last := len(c.matchedKeys) - 1 + if c.matchedKeys[last] == nil { + c.matchedKeys[last] = map[string]struct{}{} + } + c.matchedKeys[last][key] = struct{}{} +} + +// popAndVerifyMatchedKeys pops the last element of matchedKeys, +// checks the matched keys against the data, and adds unknown +// field errors for any matched keys. +// `mapValue` is the value of sv containing all of the keys that exist at this level +// (ie. sv.MapKeys) in the source data. +// `matchedKeys` are all the keys found for that level in the destination object. +// This should only be called from `structFromUnstructured`. +func (c *fromUnstructuredContext) popAndVerifyMatchedKeys(mapValue reflect.Value) { + if !c.returnUnknownFields { + return + } + + last := len(c.matchedKeys) - 1 + curMatchedKeys := c.matchedKeys[last] + c.matchedKeys[last] = nil + c.matchedKeys = c.matchedKeys[:last] + for _, key := range mapValue.MapKeys() { + if _, ok := curMatchedKeys[key.String()]; !ok { + c.recordUnknownField(key.String()) + } + } +} + +func (c *fromUnstructuredContext) recordUnknownField(field string) { + if !c.returnUnknownFields { + return + } + + pathLen := len(c.parentPath) + c.pushKey(field) + errPath := strings.Join(c.parentPath, "") + c.parentPath = c.parentPath[:pathLen] + c.unknownFieldErrors = append(c.unknownFieldErrors, fmt.Errorf(`unknown field "%s"`, errPath)) +} + +func (c *fromUnstructuredContext) pushIndex(index int) { + if !c.returnUnknownFields { + return + } + + c.parentPath = append(c.parentPath, "[", strconv.Itoa(index), "]") +} + +func (c *fromUnstructuredContext) pushKey(key string) { + if !c.returnUnknownFields { + return + } + + if len(c.parentPath) > 0 { + c.parentPath = append(c.parentPath, ".") + } + c.parentPath = append(c.parentPath, key) + +} + +// FromUnstructuredWithValidation converts an object from map[string]interface{} representation into a concrete type. +// It uses encoding/json/Unmarshaler if object implements it or reflection if not. +// It takes a validationDirective that indicates how to behave when it encounters unknown fields. +func (c *unstructuredConverter) FromUnstructuredWithValidation(u map[string]interface{}, obj interface{}, returnUnknownFields bool) error { + t := reflect.TypeOf(obj) + value := reflect.ValueOf(obj) + if t.Kind() != reflect.Pointer || value.IsNil() { + return fmt.Errorf("FromUnstructured requires a non-nil pointer to an object, got %v", t) + } + + fromUnstructuredContext := &fromUnstructuredContext{ + returnUnknownFields: returnUnknownFields, + } + err := fromUnstructured(reflect.ValueOf(u), value.Elem(), fromUnstructuredContext) + if c.mismatchDetection { + newObj := reflect.New(t.Elem()).Interface() + newErr := fromUnstructuredViaJSON(u, newObj) + if (err != nil) != (newErr != nil) { + //nolint:logcheck // Should not be reached. + klog.Fatalf("FromUnstructured unexpected error for %v: error: %v", u, err) + } + if err == nil && !c.comparison.DeepEqual(obj, newObj) { + //nolint:logcheck // Should not be reached. + klog.Fatalf("FromUnstructured mismatch\nobj1: %#v\nobj2: %#v", obj, newObj) + } + } + if err != nil { + return err + } + if returnUnknownFields && len(fromUnstructuredContext.unknownFieldErrors) > 0 { + sort.Slice(fromUnstructuredContext.unknownFieldErrors, func(i, j int) bool { + return fromUnstructuredContext.unknownFieldErrors[i].Error() < + fromUnstructuredContext.unknownFieldErrors[j].Error() + }) + return NewStrictDecodingError(fromUnstructuredContext.unknownFieldErrors) + } + return nil +} + +// FromUnstructured converts an object from map[string]interface{} representation into a concrete type. +// It uses encoding/json/Unmarshaler if object implements it or reflection if not. +func (c *unstructuredConverter) FromUnstructured(u map[string]interface{}, obj interface{}) error { + return c.FromUnstructuredWithValidation(u, obj, false) +} + +func fromUnstructuredViaJSON(u map[string]interface{}, obj interface{}) error { + data, err := json.Marshal(u) + if err != nil { + return err + } + return json.Unmarshal(data, obj) +} + +func fromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { + sv = unwrapInterface(sv) + if !sv.IsValid() { + dv.Set(reflect.Zero(dv.Type())) + return nil + } + st, dt := sv.Type(), dv.Type() + + switch dt.Kind() { + case reflect.Map, reflect.Slice, reflect.Pointer, reflect.Struct, reflect.Interface: + // Those require non-trivial conversion. + default: + // This should handle all simple types. + if st.AssignableTo(dt) { + dv.Set(sv) + return nil + } + // We cannot simply use "ConvertibleTo", as JSON doesn't support conversions + // between those four groups: bools, integers, floats and string. We need to + // do the same. + if st.ConvertibleTo(dt) { + switch st.Kind() { + case reflect.String: + switch dt.Kind() { + case reflect.String: + dv.Set(sv.Convert(dt)) + return nil + } + case reflect.Bool: + switch dt.Kind() { + case reflect.Bool: + dv.Set(sv.Convert(dt)) + return nil + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + switch dt.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + dv.Set(sv.Convert(dt)) + return nil + case reflect.Float32, reflect.Float64: + dv.Set(sv.Convert(dt)) + return nil + } + case reflect.Float32, reflect.Float64: + switch dt.Kind() { + case reflect.Float32, reflect.Float64: + dv.Set(sv.Convert(dt)) + return nil + } + if sv.Float() == math.Trunc(sv.Float()) { + dv.Set(sv.Convert(dt)) + return nil + } + } + return fmt.Errorf("cannot convert %s to %s", st.String(), dt.String()) + } + } + + // Check if the object has a custom JSON marshaller/unmarshaller. + entry := value.TypeReflectEntryOf(dv.Type()) + if entry.CanConvertFromUnstructured() { + return entry.FromUnstructured(sv, dv) + } + + switch dt.Kind() { + case reflect.Map: + return mapFromUnstructured(sv, dv, ctx) + case reflect.Slice: + return sliceFromUnstructured(sv, dv, ctx) + case reflect.Pointer: + return pointerFromUnstructured(sv, dv, ctx) + case reflect.Struct: + return structFromUnstructured(sv, dv, ctx) + case reflect.Interface: + return interfaceFromUnstructured(sv, dv) + default: + return fmt.Errorf("unrecognized type: %v", dt.Kind()) + } + +} + +func fieldInfoFromField(structType reflect.Type, field int) *fieldInfo { + fieldCacheMap := fieldCache.value.Load().(fieldsCacheMap) + if info, ok := fieldCacheMap[structField{structType, field}]; ok { + return info + } + + // Cache miss - we need to compute the field name. + info := &fieldInfo{} + typeField := structType.Field(field) + jsonTag, exists := typeField.Tag.Lookup("json") + if !exists || len(jsonTag) == 0 { + if !typeField.Anonymous { + // match stdlib behavior for naming fields that don't specify a json tag name + info.name = typeField.Name + } + } else { + items := strings.Split(jsonTag, ",") + info.name = items[0] + if isInlinedFromTag(typeField, items[0], items[1:]) { + // match stdlib behavior when controlled by tag + info.name = "" + } else if len(info.name) == 0 && !typeField.Anonymous { + // match stdlib behavior for naming fields that don't specify a json tag name + info.name = typeField.Name + } + + for i := range items { + if i > 0 && items[i] == "omitempty" { + info.omitempty = true + } + if i > 0 && items[i] == "omitzero" { + info.omitzero = value.OmitZeroFunc(typeField.Type) + } + } + } + info.nameValue = reflect.ValueOf(info.name) + + fieldCache.Lock() + defer fieldCache.Unlock() + fieldCacheMap = fieldCache.value.Load().(fieldsCacheMap) + newFieldCacheMap := make(fieldsCacheMap) + for k, v := range fieldCacheMap { + newFieldCacheMap[k] = v + } + newFieldCacheMap[structField{structType, field}] = info + fieldCache.value.Store(newFieldCacheMap) + return info +} + +func unwrapInterface(v reflect.Value) reflect.Value { + for v.Kind() == reflect.Interface { + v = v.Elem() + } + return v +} + +func mapFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { + st, dt := sv.Type(), dv.Type() + if st.Kind() != reflect.Map { + return fmt.Errorf("cannot restore map from %v", st.Kind()) + } + + if !st.Key().AssignableTo(dt.Key()) && !st.Key().ConvertibleTo(dt.Key()) { + return fmt.Errorf("cannot copy map with non-assignable keys: %v %v", st.Key(), dt.Key()) + } + + if sv.IsNil() { + dv.Set(reflect.Zero(dt)) + return nil + } + dv.Set(reflect.MakeMap(dt)) + for _, key := range sv.MapKeys() { + value := reflect.New(dt.Elem()).Elem() + if val := unwrapInterface(sv.MapIndex(key)); val.IsValid() { + if err := fromUnstructured(val, value, ctx); err != nil { + return err + } + } else { + value.Set(reflect.Zero(dt.Elem())) + } + if st.Key().AssignableTo(dt.Key()) { + dv.SetMapIndex(key, value) + } else { + dv.SetMapIndex(key.Convert(dt.Key()), value) + } + } + return nil +} + +func sliceFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { + st, dt := sv.Type(), dv.Type() + if st.Kind() == reflect.String && dt.Elem().Kind() == reflect.Uint8 { + // We store original []byte representation as string. + // This conversion is allowed, but we need to be careful about + // marshaling data appropriately. + if len(sv.Interface().(string)) > 0 { + marshalled, err := json.Marshal(sv.Interface()) + if err != nil { + return fmt.Errorf("error encoding %s to json: %v", st, err) + } + // TODO: Is this Unmarshal needed? + var data []byte + err = json.Unmarshal(marshalled, &data) + if err != nil { + return fmt.Errorf("error decoding from json: %v", err) + } + dv.SetBytes(data) + } else { + dv.Set(reflect.MakeSlice(dt, 0, 0)) + } + return nil + } + if st.Kind() != reflect.Slice { + return fmt.Errorf("cannot restore slice from %v", st.Kind()) + } + + if sv.IsNil() { + dv.Set(reflect.Zero(dt)) + return nil + } + dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap())) + + pathLen := len(ctx.parentPath) + defer func() { + ctx.parentPath = ctx.parentPath[:pathLen] + }() + for i := 0; i < sv.Len(); i++ { + ctx.pushIndex(i) + if err := fromUnstructured(sv.Index(i), dv.Index(i), ctx); err != nil { + return err + } + ctx.parentPath = ctx.parentPath[:pathLen] + } + return nil +} + +func pointerFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { + st, dt := sv.Type(), dv.Type() + + if st.Kind() == reflect.Pointer && sv.IsNil() { + dv.Set(reflect.Zero(dt)) + return nil + } + dv.Set(reflect.New(dt.Elem())) + switch st.Kind() { + case reflect.Pointer, reflect.Interface: + return fromUnstructured(sv.Elem(), dv.Elem(), ctx) + default: + return fromUnstructured(sv, dv.Elem(), ctx) + } +} + +func structFromUnstructured(sv, dv reflect.Value, ctx *fromUnstructuredContext) error { + st, dt := sv.Type(), dv.Type() + if st.Kind() != reflect.Map { + return fmt.Errorf("cannot restore struct from: %v", st.Kind()) + } + + pathLen := len(ctx.parentPath) + svInlined := ctx.isInlined + defer func() { + ctx.parentPath = ctx.parentPath[:pathLen] + ctx.isInlined = svInlined + }() + if !svInlined { + ctx.pushMatchedKeyTracker() + } + for i := 0; i < dt.NumField(); i++ { + fieldInfo := fieldInfoFromField(dt, i) + fv := dv.Field(i) + + if len(fieldInfo.name) == 0 { + // This field is inlined, recurse into fromUnstructured again + // with the same set of matched keys. + ctx.isInlined = true + if err := fromUnstructured(sv, fv, ctx); err != nil { + return err + } + ctx.isInlined = svInlined + } else { + // This field is not inlined so we recurse into + // child field of sv corresponding to field i of + // dv, with a new set of matchedKeys and updating + // the parentPath to indicate that we are one level + // deeper. + ctx.recordMatchedKey(fieldInfo.name) + value := unwrapInterface(sv.MapIndex(fieldInfo.nameValue)) + if value.IsValid() { + ctx.isInlined = false + ctx.pushKey(fieldInfo.name) + if err := fromUnstructured(value, fv, ctx); err != nil { + return err + } + ctx.parentPath = ctx.parentPath[:pathLen] + ctx.isInlined = svInlined + } else { + fv.Set(reflect.Zero(fv.Type())) + } + } + } + if !svInlined { + ctx.popAndVerifyMatchedKeys(sv) + } + return nil +} + +func interfaceFromUnstructured(sv, dv reflect.Value) error { + // TODO: Is this conversion safe? + dv.Set(sv) + return nil +} + +// ToUnstructured converts an object into map[string]interface{} representation. +// It uses encoding/json/Marshaler if object implements it or reflection if not. +func (c *unstructuredConverter) ToUnstructured(obj interface{}) (map[string]interface{}, error) { + var u map[string]interface{} + var err error + if unstr, ok := obj.(Unstructured); ok { + u = unstr.UnstructuredContent() + } else { + t := reflect.TypeOf(obj) + value := reflect.ValueOf(obj) + if t.Kind() != reflect.Pointer || value.IsNil() { + return nil, fmt.Errorf("ToUnstructured requires a non-nil pointer to an object, got %v", t) + } + u = map[string]interface{}{} + err = toUnstructured(value.Elem(), reflect.ValueOf(&u).Elem()) + } + if c.mismatchDetection { + newUnstr := map[string]interface{}{} + newErr := toUnstructuredViaJSON(obj, &newUnstr) + if (err != nil) != (newErr != nil) { + //nolint:logcheck // Should not be reached. + klog.Fatalf("ToUnstructured unexpected error for %v: error: %v; newErr: %v", obj, err, newErr) + } + if err == nil && !c.comparison.DeepEqual(u, newUnstr) { + //nolint:logcheck // Should not be reached. + klog.Fatalf("ToUnstructured mismatch\nobj1: %#v\nobj2: %#v", u, newUnstr) + } + } + if err != nil { + return nil, err + } + return u, nil +} + +// DeepCopyJSON deep copies the passed value, assuming it is a valid JSON representation i.e. only contains +// types produced by json.Unmarshal() and also int64. +// bool, int64, float64, string, []interface{}, map[string]interface{}, json.Number and nil +func DeepCopyJSON(x map[string]interface{}) map[string]interface{} { + return DeepCopyJSONValue(x).(map[string]interface{}) +} + +// DeepCopyJSONValue deep copies the passed value, assuming it is a valid JSON representation i.e. only contains +// types produced by json.Unmarshal() and also int64. +// bool, int64, float64, string, []interface{}, map[string]interface{}, json.Number and nil +func DeepCopyJSONValue(x interface{}) interface{} { + switch x := x.(type) { + case map[string]interface{}: + if x == nil { + // Typed nil - an interface{} that contains a type map[string]interface{} with a value of nil + return x + } + clone := make(map[string]interface{}, len(x)) + for k, v := range x { + clone[k] = DeepCopyJSONValue(v) + } + return clone + case []interface{}: + if x == nil { + // Typed nil - an interface{} that contains a type []interface{} with a value of nil + return x + } + clone := make([]interface{}, len(x)) + for i, v := range x { + clone[i] = DeepCopyJSONValue(v) + } + return clone + case string, int64, bool, float64, nil, encodingjson.Number: + return x + default: + panic(fmt.Errorf("cannot deep copy %T", x)) + } +} + +func toUnstructuredViaJSON(obj interface{}, u *map[string]interface{}) error { + data, err := json.Marshal(obj) + if err != nil { + return err + } + return json.Unmarshal(data, u) +} + +func toUnstructured(sv, dv reflect.Value) error { + // Check if the object has a custom string converter. + entry := value.TypeReflectEntryOf(sv.Type()) + if entry.CanConvertToUnstructured() { + v, err := entry.ToUnstructured(sv) + if err != nil { + return err + } + if v != nil { + dv.Set(reflect.ValueOf(v)) + } + return nil + } + st := sv.Type() + switch st.Kind() { + case reflect.String: + dv.Set(reflect.ValueOf(sv.String())) + return nil + case reflect.Bool: + dv.Set(reflect.ValueOf(sv.Bool())) + return nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + dv.Set(reflect.ValueOf(sv.Int())) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + val, err := uintToUnstructuredHelper(sv.Uint()) + if err != nil { + return err + } + dv.Set(reflect.ValueOf(val)) + return nil + case reflect.Float32, reflect.Float64: + dv.Set(reflect.ValueOf(sv.Float())) + return nil + case reflect.Map: + return mapToUnstructured(sv, dv) + case reflect.Slice: + return sliceToUnstructured(sv, dv) + case reflect.Pointer: + return pointerToUnstructured(sv, dv) + case reflect.Struct: + return structToUnstructured(sv, dv) + case reflect.Interface: + return interfaceToUnstructured(sv, dv) + default: + return fmt.Errorf("unrecognized type: %v", st.Kind()) + } +} + +func mapToUnstructured(sv, dv reflect.Value) error { + st, dt := sv.Type(), dv.Type() + if sv.IsNil() { + dv.Set(reflect.Zero(dt)) + return nil + } + if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 { + if st.Key().Kind() == reflect.String { + dv.Set(reflect.MakeMap(mapStringInterfaceType)) + dv = dv.Elem() + dt = dv.Type() + } + } + if dt.Kind() != reflect.Map { + return fmt.Errorf("cannot convert map to: %v", dt.Kind()) + } + + if !st.Key().AssignableTo(dt.Key()) && !st.Key().ConvertibleTo(dt.Key()) { + return fmt.Errorf("cannot copy map with non-assignable keys: %v %v", st.Key(), dt.Key()) + } + + for _, key := range sv.MapKeys() { + value := reflect.New(dt.Elem()).Elem() + if err := toUnstructured(sv.MapIndex(key), value); err != nil { + return err + } + if st.Key().AssignableTo(dt.Key()) { + dv.SetMapIndex(key, value) + } else { + dv.SetMapIndex(key.Convert(dt.Key()), value) + } + } + return nil +} + +func sliceToUnstructured(sv, dv reflect.Value) error { + st, dt := sv.Type(), dv.Type() + if sv.IsNil() { + dv.Set(reflect.Zero(dt)) + return nil + } + if st.Elem().Kind() == reflect.Uint8 { + dv.Set(reflect.New(stringType)) + data, err := json.Marshal(sv.Bytes()) + if err != nil { + return err + } + var result string + if err = json.Unmarshal(data, &result); err != nil { + return err + } + dv.Set(reflect.ValueOf(result)) + return nil + } + if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 { + dv.Set(reflect.MakeSlice(reflect.SliceOf(dt), sv.Len(), sv.Cap())) + dv = dv.Elem() + dt = dv.Type() + } + if dt.Kind() != reflect.Slice { + return fmt.Errorf("cannot convert slice to: %v", dt.Kind()) + } + for i := 0; i < sv.Len(); i++ { + if err := toUnstructured(sv.Index(i), dv.Index(i)); err != nil { + return err + } + } + return nil +} + +func pointerToUnstructured(sv, dv reflect.Value) error { + if sv.IsNil() { + // We're done - we don't need to store anything. + return nil + } + return toUnstructured(sv.Elem(), dv) +} + +func isEmpty(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Map, reflect.Slice: + // TODO: It seems that 0-len maps are ignored in it. + return v.IsNil() || v.Len() == 0 + case reflect.Pointer, reflect.Interface: + return v.IsNil() + } + return false +} + +func structToUnstructured(sv, dv reflect.Value) error { + st, dt := sv.Type(), dv.Type() + if dt.Kind() == reflect.Interface && dv.NumMethod() == 0 { + dv.Set(reflect.MakeMapWithSize(mapStringInterfaceType, st.NumField())) + dv = dv.Elem() + dt = dv.Type() + } + if dt.Kind() != reflect.Map { + return fmt.Errorf("cannot convert struct to: %v", dt.Kind()) + } + realMap := dv.Interface().(map[string]interface{}) + + for i := 0; i < st.NumField(); i++ { + fieldInfo := fieldInfoFromField(st, i) + fv := sv.Field(i) + + if fieldInfo.name == "-" { + // This field should be skipped. + continue + } + if fieldInfo.omitempty && isEmpty(fv) { + // omitempty fields should be ignored. + continue + } + if fieldInfo.omitzero != nil && fieldInfo.omitzero(fv) { + // omitzero fields should be ignored + continue + } + if len(fieldInfo.name) == 0 { + // This field is inlined. + if err := toUnstructured(fv, dv); err != nil { + return err + } + continue + } + switch fv.Type().Kind() { + case reflect.String: + realMap[fieldInfo.name] = fv.String() + case reflect.Bool: + realMap[fieldInfo.name] = fv.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + realMap[fieldInfo.name] = fv.Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + val, err := uintToUnstructuredHelper(fv.Uint()) + if err != nil { + return err + } + realMap[fieldInfo.name] = val + case reflect.Float32, reflect.Float64: + realMap[fieldInfo.name] = fv.Float() + default: + subv := reflect.New(dt.Elem()).Elem() + if err := toUnstructured(fv, subv); err != nil { + return err + } + dv.SetMapIndex(fieldInfo.nameValue, subv) + } + } + return nil +} + +func interfaceToUnstructured(sv, dv reflect.Value) error { + if !sv.IsValid() || sv.IsNil() { + dv.Set(reflect.Zero(dv.Type())) + return nil + } + return toUnstructured(sv.Elem(), dv) +} + +func uintToUnstructuredHelper(uVal uint64) (int64, error) { + if uVal > math.MaxInt64 { + return 0, fmt.Errorf("unsigned value %d does not fit into int64 (overflow)", uVal) + } + return int64(uVal), nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter_test.go new file mode 100644 index 0000000000..0b6aa5b780 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter_test.go @@ -0,0 +1,1213 @@ +/* +Copyright 2015 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. +*/ + +// These tests are in a separate package to break cyclic dependency in tests. +// Unstructured type depends on unstructured converter package but we want to test how the converter handles +// the Unstructured type so we need to import both. + +package runtime_test + +import ( + encodingjson "encoding/json" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "testing" + "time" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/json" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/randfill" +) + +var simpleEquality = conversion.EqualitiesOrDie( + func(a, b time.Time) bool { + return a.UTC() == b.UTC() + }, +) + +// Define a number of test types. +type A struct { + A int `json:"aa,omitempty"` + B string `json:"ab,omitempty"` + C bool `json:"ac,omitempty"` + D uint `json:"ad,omitempty"` +} + +type B struct { + A A `json:"ba"` + B string `json:"bb"` + C map[string]string `json:"bc"` + D []string `json:"bd"` +} + +type C struct { + A []A `json:"ca"` + B `json:""` + C string `json:"cc"` + D *int64 `json:"cd"` + E map[string]int `json:"ce"` + F []bool `json:"cf"` + G []int `json:"cg"` + H float32 `json:"ch"` + I []interface{} `json:"ci"` +} + +type D struct { + A []interface{} `json:"da"` +} + +type E struct { + A interface{} `json:"ea"` +} + +type F struct { + A string `json:"fa"` + B map[string]string `json:"fb"` + C []A `json:"fc"` + D int `json:"fd"` + E float32 `json:"fe"` + F []string `json:"ff"` + G []int `json:"fg"` + H []bool `json:"fh"` + I []float32 `json:"fi"` + J []byte `json:"fj"` +} + +type G struct { + CustomValue1 CustomValue `json:"customValue1"` + CustomValue2 *CustomValue `json:"customValue2"` + CustomPointer1 CustomPointer `json:"customPointer1"` + CustomPointer2 *CustomPointer `json:"customPointer2"` +} + +type H struct { + A A `json:"ha"` + C `json:""` +} + +type I struct { + A A `json:"ia"` + H `json:""` + + UL1 UnknownLevel1 `json:"ul1"` +} + +type UnknownLevel1 struct { + A int64 `json:"a"` + InlinedAA `json:""` + InlinedAAA `json:""` +} +type InlinedAA struct { + AA int64 `json:"aa"` +} +type InlinedAAA struct { + AAA int64 `json:"aaa"` + Child UnknownLevel2 `json:"child"` +} + +type UnknownLevel2 struct { + B int64 `json:"b"` + InlinedBB `json:""` + InlinedBBB `json:""` +} +type InlinedBB struct { + BB int64 `json:"bb"` +} +type InlinedBBB struct { + BBB int64 `json:"bbb"` + Child UnknownLevel3 `json:"child"` +} + +type UnknownLevel3 struct { + C int64 `json:"c"` + InlinedCC `json:""` + InlinedCCC `json:""` +} +type InlinedCC struct { + CC int64 `json:"cc"` +} +type InlinedCCC struct { + CCC int64 `json:"ccc"` +} + +type CustomValue struct { + data []byte +} + +// MarshalJSON has a value receiver on this type. +func (c CustomValue) MarshalJSON() ([]byte, error) { + return c.data, nil +} + +type CustomPointer struct { + data []byte +} + +// MarshalJSON has a pointer receiver on this type. +func (c *CustomPointer) MarshalJSON() ([]byte, error) { + return c.data, nil +} + +func doRoundTrip(t *testing.T, item interface{}) { + data, err := json.Marshal(item) + if err != nil { + t.Errorf("Error when marshaling object: %v", err) + return + } + + unstr := make(map[string]interface{}) + err = json.Unmarshal(data, &unstr) + if err != nil { + t.Errorf("Error when unmarshaling to unstructured: %v", err) + return + } + + data, err = json.Marshal(unstr) + if err != nil { + t.Errorf("Error when marshaling unstructured: %v", err) + return + } + unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() + err = json.Unmarshal(data, unmarshalledObj) + if err != nil { + t.Errorf("Error when unmarshaling to object: %v", err) + return + } + if !reflect.DeepEqual(item, unmarshalledObj) { + t.Errorf("Object changed during JSON operations, diff: %v", cmp.Diff(item, unmarshalledObj)) + return + } + + // TODO: should be using mismatch detection but fails due to another error + newUnstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item) + if err != nil { + t.Errorf("ToUnstructured failed: %v", err) + return + } + + copiedNewUnstr := runtime.DeepCopyJSONValue(newUnstr) + if value, ok := copiedNewUnstr.(map[string]interface{}); ok { + newUnstr = value + } else { + t.Errorf("DeepCopyJSONValue return unexpected type %T", copiedNewUnstr) + return + } + + newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() + err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(newUnstr, newObj) + if err != nil { + t.Errorf("FromUnstructured failed: %v", err) + return + } + + if !reflect.DeepEqual(item, newObj) { + t.Errorf("Object changed, diff: %v", cmp.Diff(item, newObj)) + } +} + +func TestRoundTrip(t *testing.T) { + intVal := int64(42) + testCases := []struct { + obj interface{} + }{ + { + obj: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + }, + // Not testing a list with nil Items because items is a non-optional field and hence + // is always marshaled into an empty array which is not equal to nil when unmarshalled and will fail. + // That is expected. + Items: []unstructured.Unstructured{}, + }, + }, + { + obj: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + }, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "Pod", + }, + }, + }, + }, + }, + { + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Pod", + }, + }, + }, + { + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Foo", + "metadata": map[string]interface{}{ + "name": "foo1", + }, + }, + }, + }, + { + // This (among others) tests nil map, slice and pointer. + obj: &C{ + C: "ccc", + }, + }, + { + // This (among others) tests empty map and slice. + obj: &C{ + A: []A{}, + C: "ccc", + E: map[string]int{}, + I: []interface{}{}, + }, + }, + { + obj: &C{ + A: []A{ + { + A: 1, + B: "11", + C: true, + }, + { + A: 2, + B: "22", + C: false, + }, + }, + B: B{ + A: A{ + A: 3, + B: "33", + }, + B: "bbb", + C: map[string]string{ + "k1": "v1", + "k2": "v2", + }, + D: []string{"s1", "s2"}, + }, + C: "ccc", + D: &intVal, + E: map[string]int{ + "k1": 1, + "k2": 2, + }, + F: []bool{true, false, false}, + G: []int{1, 2, 5}, + H: 3.3, + I: []interface{}{nil, nil, nil}, + }, + }, + { + // Test slice of interface{} with empty slices. + obj: &D{ + A: []interface{}{[]interface{}{}, []interface{}{}}, + }, + }, + { + // Test slice of interface{} with different values. + obj: &D{ + A: []interface{}{float64(3.5), int64(4), "3.0", nil}, + }, + }, + { + // Test uint values. + obj: &A{ + D: 1, + }, + }, + } + + for i := range testCases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + doRoundTrip(t, testCases[i].obj) + }) + } +} + +// TestUnknownFields checks for the collection of unknown +// field errors from the various possible locations of +// unknown fields (e.g. fields on struct, inlined struct, slice, etc) +func TestUnknownFields(t *testing.T) { + // simples checks that basic unknown fields are found + // in fields, subfields and slices. + var simplesData = `{ +"ca": [ + { + "aa": 1, + "ab": "ab", + "ac": true, + "unknown1": 24 + } +], +"cc": "ccstring", +"unknown2": "foo" +}` + + var simplesErrs = []string{ + `unknown field "ca[0].unknown1"`, + `unknown field "unknown2"`, + } + + // same-name, different-levels checks that + // fields at a higher level in the json + // are not persisted to unrecognized fields + // at lower levels and vice-versa. + // + // In this case, the field "cc" exists at the root level + // but not in the nested field ul1. If we are + // improperly retaining matched keys, this not + // see an issue with "cc" existing inside "ul1" + // + // The opposite for "aaa", which exists at the + // nested level but not at the root. + var sameNameDiffLevelData = ` + { + "cc": "foo", + "aaa": 1, + "ul1": { + "aa": 1, + "aaa": 1, + "cc": 1 + + } +}` + var sameNameDiffLevelErrs = []string{ + `unknown field "aaa"`, + `unknown field "ul1.cc"`, + } + + // inlined-inlined confirms that we see + // fields that are doubly nested and don't recognize + // those that aren't + var inlinedInlinedData = `{ + "bb": "foo", + "bc": { + "foo": "bar" + }, + "bd": ["d1", "d2"], + "aa": 1 +}` + + var inlinedInlinedErrs = []string{ + `unknown field "aa"`, + } + + // combined tests everything together + var combinedData = ` + { + "ia": { + "aa": 1, + "ab": "ab", + "unknownI": "foo" + }, + "ha": { + "aa": 2, + "ab": "ab2", + "unknownH": "foo" + }, + "ca":[ + { + "aa":1, + "ab":"11", + "ac":true + }, + { + "aa":2, + "ab":"22", + "unknown1": "foo" + }, + { + "aa":3, + "ab":"33", + "unknown2": "foo" + } + ], + "ba":{ + "aa":3, + "ab":"33", + "ac": true, + "unknown3": 26, + "unknown4": "foo" + }, + "unknown5": "foo", + "bb":"bbb", + "bc":{ + "k1":"v1", + "k2":"v2" + }, + "bd":[ + "s1", + "s2" + ], + "cc":"ccc", + "cd":42, + "ce":{ + "k1":1, + "k2":2 + }, + "cf":[ + true, + false, + false + ], + "cg": + [ + 1, + 2, + 5 + ], + "ch":3.3, + "ci":[ + null, + null, + null + ], + "ul1": { + "a": 1, + "aa": 1, + "aaa": 1, + "b": 1, + "bb": 1, + "bbb": 1, + "c": 1, + "cc": 1, + "ccc": 1, + "child": { + "a": 1, + "aa": 1, + "aaa": 1, + "b": 1, + "bb": 1, + "bbb": 1, + "c": 1, + "cc": 1, + "ccc": 1, + "child": { + "a": 1, + "aa": 1, + "aaa": 1, + "b": 1, + "bb": 1, + "bbb": 1, + "c": 1, + "cc": 1, + "ccc": 1 + } + } + } +}` + + var combinedErrs = []string{ + `unknown field "ca[1].unknown1"`, + `unknown field "ca[2].unknown2"`, + `unknown field "ba.unknown3"`, + `unknown field "ba.unknown4"`, + `unknown field "unknown5"`, + `unknown field "ha.unknownH"`, + `unknown field "ia.unknownI"`, + + `unknown field "ul1.b"`, + `unknown field "ul1.bb"`, + `unknown field "ul1.bbb"`, + `unknown field "ul1.c"`, + `unknown field "ul1.cc"`, + `unknown field "ul1.ccc"`, + + `unknown field "ul1.child.a"`, + `unknown field "ul1.child.aa"`, + `unknown field "ul1.child.aaa"`, + `unknown field "ul1.child.c"`, + `unknown field "ul1.child.cc"`, + `unknown field "ul1.child.ccc"`, + + `unknown field "ul1.child.child.a"`, + `unknown field "ul1.child.child.aa"`, + `unknown field "ul1.child.child.aaa"`, + `unknown field "ul1.child.child.b"`, + `unknown field "ul1.child.child.bb"`, + `unknown field "ul1.child.child.bbb"`, + } + + testCases := []struct { + jsonData string + obj interface{} + returnUnknownFields bool + expectedErrs []string + }{ + { + jsonData: simplesData, + obj: &C{}, + returnUnknownFields: true, + expectedErrs: simplesErrs, + }, + { + jsonData: simplesData, + obj: &C{}, + returnUnknownFields: false, + }, + { + jsonData: sameNameDiffLevelData, + obj: &I{}, + returnUnknownFields: true, + expectedErrs: sameNameDiffLevelErrs, + }, + { + jsonData: sameNameDiffLevelData, + obj: &I{}, + returnUnknownFields: false, + }, + { + jsonData: inlinedInlinedData, + obj: &I{}, + returnUnknownFields: true, + expectedErrs: inlinedInlinedErrs, + }, + { + jsonData: inlinedInlinedData, + obj: &I{}, + returnUnknownFields: false, + }, + { + jsonData: combinedData, + obj: &I{}, + returnUnknownFields: true, + expectedErrs: combinedErrs, + }, + { + jsonData: combinedData, + obj: &I{}, + returnUnknownFields: false, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + unstr := make(map[string]interface{}) + err := json.Unmarshal([]byte(tc.jsonData), &unstr) + if err != nil { + t.Errorf("Error when unmarshaling to unstructured: %v", err) + return + } + err = runtime.NewTestUnstructuredConverterWithValidation(simpleEquality).FromUnstructuredWithValidation(unstr, tc.obj, tc.returnUnknownFields) + if len(tc.expectedErrs) == 0 && err != nil { + t.Errorf("unexpected err: %v", err) + } + var errString string + if err != nil { + errString = err.Error() + } + missedErrs := []string{} + failed := false + for _, expected := range tc.expectedErrs { + if !strings.Contains(errString, expected) { + failed = true + missedErrs = append(missedErrs, expected) + } else { + errString = strings.Replace(errString, expected, "", 1) + } + } + if failed { + for _, e := range missedErrs { + t.Errorf("missing err: %v\n", e) + } + } + leftoverErrors := strings.TrimSpace(strings.TrimPrefix(strings.ReplaceAll(errString, ",", ""), "strict decoding error:")) + if leftoverErrors != "" { + t.Errorf("found unexpected errors: %s", leftoverErrors) + } + }) + } +} + +// BenchmarkFromUnstructuredWithValidation benchmarks +// the time and memory required to perform FromUnstructured +// with the various validation directives (Ignore, Warn, Strict) +func BenchmarkFromUnstructuredWithValidation(b *testing.B) { + re := regexp.MustCompile("^I$") + f := randfill.NewWithSeed(1).NilChance(0.1).SkipFieldsWithPattern(re) + iObj := &I{} + f.Fill(&iObj) + + unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(iObj) + if err != nil { + b.Fatalf("ToUnstructured failed: %v", err) + return + } + for _, shouldReturn := range []bool{false, true} { + b.Run(fmt.Sprintf("shouldReturn=%t", shouldReturn), func(b *testing.B) { + newObj := reflect.New(reflect.TypeOf(iObj).Elem()).Interface() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if err = runtime.NewTestUnstructuredConverterWithValidation(simpleEquality).FromUnstructuredWithValidation(unstr, newObj, shouldReturn); err != nil { + b.Fatalf("FromUnstructured failed: %v", err) + return + } + } + }) + } +} + +// Verifies that: +// 1) serialized json -> object +// 2) serialized json -> map[string]interface{} -> object +// produces the same object. +func doUnrecognized(t *testing.T, jsonData string, item interface{}, expectedErr error) { + unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() + err := json.Unmarshal([]byte(jsonData), unmarshalledObj) + if (err != nil) != (expectedErr != nil) { + t.Errorf("Unexpected error when unmarshaling to object: %v, expected: %v", err, expectedErr) + return + } + + unstr := make(map[string]interface{}) + err = json.Unmarshal([]byte(jsonData), &unstr) + if err != nil { + t.Errorf("Error when unmarshaling to unstructured: %v", err) + return + } + newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface() + err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, newObj) + if (err != nil) != (expectedErr != nil) { + t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr) + } + + if expectedErr == nil && !reflect.DeepEqual(unmarshalledObj, newObj) { + t.Errorf("Object changed, diff: %v", cmp.Diff(unmarshalledObj, newObj)) + } +} + +func TestUnrecognized(t *testing.T) { + testCases := []struct { + data string + obj interface{} + err error + }{ + { + data: "{\"da\":[3.5,4,\"3.0\",null]}", + obj: &D{}, + }, + { + data: "{\"ea\":[3.5,4,\"3.0\",null]}", + obj: &E{}, + }, + { + data: "{\"ea\":[null,null,null]}", + obj: &E{}, + }, + { + data: "{\"ea\":[[],[null]]}", + obj: &E{}, + }, + { + data: "{\"ea\":{\"a\":[],\"b\":null}}", + obj: &E{}, + }, + { + data: "{\"fa\":\"fa\",\"fb\":{\"a\":\"a\"}}", + obj: &F{}, + }, + { + data: "{\"fa\":\"fa\",\"fb\":{\"a\":null}}", + obj: &F{}, + }, + { + data: "{\"fc\":[null]}", + obj: &F{}, + }, + { + data: "{\"fc\":[{\"aa\":123,\"ab\":\"bbb\"}]}", + obj: &F{}, + }, + { + // Only unknown fields + data: "{\"fx\":[{\"aa\":123,\"ab\":\"bbb\"}],\"fz\":123}", + obj: &F{}, + }, + { + data: "{\"fc\":[{\"aa\":\"aaa\",\"ab\":\"bbb\"}]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"), + }, + { + data: "{\"fd\":123,\"fe\":3.5}", + obj: &F{}, + }, + { + data: "{\"ff\":[\"abc\"],\"fg\":[123],\"fh\":[true,false]}", + obj: &F{}, + }, + { + data: "{\"fj\":\"\"}", + obj: &F{}, + }, + { + // Invalid string data + data: "{\"fa\":123}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), + }, + { + // Invalid string data + data: "{\"fa\":13.5}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), + }, + { + // Invalid string data + data: "{\"fa\":true}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal bool into Go value of type string"), + }, + { + // Invalid []string data + data: "{\"ff\":123}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"), + }, + { + // Invalid []string data + data: "{\"ff\":3.5}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type []string"), + }, + { + // Invalid []string data + data: "{\"ff\":[123,345]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type string"), + }, + { + // Invalid []int data + data: "{\"fg\":123}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type []int"), + }, + { + // Invalid []int data + data: "{\"fg\":\"abc\"}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal string into Go value of type []int"), + }, + { + // Invalid []int data + data: "{\"fg\":[\"abc\"]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal string into Go value of type int"), + }, + { + // Invalid []int data + data: "{\"fg\":[3.5]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"), + }, + { + // Invalid []int data + data: "{\"fg\":[true,false]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"), + }, + { + // Invalid []bool data + data: "{\"fh\":123}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type []bool"), + }, + { + // Invalid []bool data + data: "{\"fh\":\"abc\"}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal string into Go value of type []bool"), + }, + { + // Invalid []bool data + data: "{\"fh\":[\"abc\"]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal string into Go value of type bool"), + }, + { + // Invalid []bool data + data: "{\"fh\":[3.5]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"), + }, + { + // Invalid []bool data + data: "{\"fh\":[123]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type bool"), + }, + { + // Invalid []float data + data: "{\"fi\":123}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal number into Go value of type []float32"), + }, + { + // Invalid []float data + data: "{\"fi\":\"abc\"}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal string into Go value of type []float32"), + }, + { + // Invalid []float data + data: "{\"fi\":[\"abc\"]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal string into Go value of type float32"), + }, + { + // Invalid []float data + data: "{\"fi\":[true]}", + obj: &F{}, + err: fmt.Errorf("json: cannot unmarshal bool into Go value of type float32"), + }, + } + + for _, tc := range testCases { + t.Run(tc.data, func(t *testing.T) { + doUnrecognized(t, tc.data, tc.obj, tc.err) + }) + } +} + +func TestDeepCopyJSON(t *testing.T) { + src := map[string]interface{}{ + "a": nil, + "b": int64(123), + "c": map[string]interface{}{ + "a": "b", + }, + "d": []interface{}{ + int64(1), int64(2), + }, + "e": "estr", + "f": true, + "g": encodingjson.Number("123"), + } + deepCopy := runtime.DeepCopyJSON(src) + assert.Equal(t, src, deepCopy) +} + +func TestFloatIntConversion(t *testing.T) { + unstr := map[string]interface{}{"fd": float64(3)} + + var obj F + if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil { + t.Errorf("Unexpected error in FromUnstructured: %v", err) + } + + data, err := json.Marshal(unstr) + if err != nil { + t.Fatalf("Error when marshaling unstructured: %v", err) + } + var unmarshalled F + if err := json.Unmarshal(data, &unmarshalled); err != nil { + t.Fatalf("Error when unmarshaling to object: %v", err) + } + + if !reflect.DeepEqual(obj, unmarshalled) { + t.Errorf("Incorrect conversion, diff: %v", cmp.Diff(obj, unmarshalled)) + } +} + +func TestIntFloatConversion(t *testing.T) { + unstr := map[string]interface{}{"ch": int64(3)} + + var obj C + if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil { + t.Errorf("Unexpected error in FromUnstructured: %v", err) + } + + data, err := json.Marshal(unstr) + if err != nil { + t.Fatalf("Error when marshaling unstructured: %v", err) + } + var unmarshalled C + if err := json.Unmarshal(data, &unmarshalled); err != nil { + t.Fatalf("Error when unmarshaling to object: %v", err) + } + + if !reflect.DeepEqual(obj, unmarshalled) { + t.Errorf("Incorrect conversion, diff: %v", cmp.Diff(obj, unmarshalled)) + } +} + +func TestCustomToUnstructured(t *testing.T) { + testcases := []struct { + Data string + Expected interface{} + }{ + {Data: `null`, Expected: nil}, + {Data: `true`, Expected: true}, + {Data: `false`, Expected: false}, + {Data: `[]`, Expected: []interface{}{}}, + {Data: `[1]`, Expected: []interface{}{int64(1)}}, + {Data: `{}`, Expected: map[string]interface{}{}}, + {Data: `{"a":1}`, Expected: map[string]interface{}{"a": int64(1)}}, + {Data: `0`, Expected: int64(0)}, + {Data: `0.0`, Expected: float64(0)}, + } + + for _, tc := range testcases { + t.Run(tc.Data, func(t *testing.T) { + t.Parallel() + result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(&G{ + CustomValue1: CustomValue{data: []byte(tc.Data)}, + CustomValue2: &CustomValue{data: []byte(tc.Data)}, + CustomPointer1: CustomPointer{data: []byte(tc.Data)}, + CustomPointer2: &CustomPointer{data: []byte(tc.Data)}, + }) + require.NoError(t, err) + for field, fieldResult := range result { + assert.Equal(t, tc.Expected, fieldResult, field) + } + }) + } +} + +func TestCustomToUnstructuredTopLevel(t *testing.T) { + // Only objects are supported at the top level + topLevelCases := []interface{}{ + &CustomValue{data: []byte(`{"a":1}`)}, + &CustomPointer{data: []byte(`{"a":1}`)}, + } + expected := map[string]interface{}{"a": int64(1)} + for i, obj := range topLevelCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(obj) + require.NoError(t, err) + assert.Equal(t, expected, result) + }) + } +} + +type OmitemptyNameField struct { + I int `json:"omitempty"` +} + +func TestOmitempty(t *testing.T) { + expected := `{"omitempty":0}` + + o := &OmitemptyNameField{} + jsonData, err := json.Marshal(o) + if err != nil { + t.Fatal(err) + } + if e, a := expected, string(jsonData); e != a { + t.Fatalf("expected\n%s\ngot\n%s", e, a) + } + + unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&o) + if err != nil { + t.Fatal(err) + } + jsonUnstrData, err := json.Marshal(unstr) + if err != nil { + t.Fatal(err) + } + if e, a := expected, string(jsonUnstrData); e != a { + t.Fatalf("expected\n%s\ngot\n%s", e, a) + } +} + +type InlineTestPrimitive struct { + NoNameTagPrimitive int64 `json:""` + NoNameTagInlinePrimitive int64 `json:",inline"` + NoNameTagOmitemptyPrimitive int64 `json:",omitempty"` +} +type InlineTestAnonymous struct { + NoTag + NoNameTag `json:""` + NameTag `json:"nameTagEmbedded"` + NoNameTagInline `json:",inline"` + NoNameTagEmbed `json:",embed"` //nolint:staticcheck // SA5008 intentionally exercising a tag not present until Go 1.27 + NoNameTagOmitempty `json:",omitempty"` +} +type InlineTestNamed struct { + NoTag NoTag + NoNameTag NoNameTag `json:""` + NameTag NameTag `json:"nameTagEmbedded"` + NoNameTagInline NoNameTagInline `json:",inline"` + NoNameTagEmbed NoNameTagEmbed `json:",embed"` //nolint:staticcheck // intentionally exercising a tag not present until Go 1.27 + NoNameTagOmitempty NoNameTagOmitempty `json:",omitempty"` +} +type NoTag struct { + Data0 int `json:"data0"` +} +type NameTag struct { + Data1 int `json:"data1"` +} +type NoNameTag struct { + Data2 int `json:"data2"` +} +type NoNameTagInline struct { + Data3 int `json:"data3"` +} +type NoNameTagOmitempty struct { + Data4 int `json:"data4"` +} +type NoNameTagEmbed struct { + Data5 int `json:"data5"` +} + +func TestInline(t *testing.T) { + testcases := []struct { + name string + obj any + expect map[string]any + }{ + { + name: "primitive-zero", + obj: &InlineTestPrimitive{}, + expect: map[string]any{ + "NoNameTagPrimitive": int64(0), + "NoNameTagInlinePrimitive": int64(0), + }, + }, + { + name: "primitive-set", + obj: &InlineTestPrimitive{ + NoNameTagPrimitive: 1, + NoNameTagInlinePrimitive: 2, + NoNameTagOmitemptyPrimitive: 3, + }, + expect: map[string]any{ + "NoNameTagPrimitive": int64(1), + "NoNameTagInlinePrimitive": int64(2), + "NoNameTagOmitemptyPrimitive": int64(3), + }, + }, + { + name: "anonymous-zero", + obj: &InlineTestAnonymous{}, + expect: map[string]any{ + "data0": int64(0), + "data2": int64(0), + "data3": int64(0), + "data4": int64(0), + "data5": int64(0), + "nameTagEmbedded": map[string]any{"data1": int64(0)}, + }, + }, + { + name: "anonymous-set", + obj: &InlineTestAnonymous{}, + expect: map[string]any{ + "data0": int64(0), + "data2": int64(0), + "data3": int64(0), + "data4": int64(0), + "data5": int64(0), + "nameTagEmbedded": map[string]any{"data1": int64(0)}, + }, + }, + { + name: "named-zero", + obj: &InlineTestNamed{}, + expect: func() map[string]any { + m := map[string]any{ + "NoTag": map[string]any{"data0": int64(0)}, + "nameTagEmbedded": map[string]any{"data1": int64(0)}, + "NoNameTag": map[string]any{"data2": int64(0)}, + "NoNameTagInline": map[string]any{"data3": int64(0)}, + "NoNameTagOmitempty": map[string]any{"data4": int64(0)}, + } + if stdlibSupportsEmbedTag { + m["data5"] = int64(0) + } else { + m["NoNameTagEmbed"] = map[string]any{"data5": int64(0)} + } + return m + }(), + }, + { + name: "named-set", + obj: &InlineTestNamed{ + NoTag: NoTag{Data0: 10}, + NameTag: NameTag{Data1: 11}, + NoNameTag: NoNameTag{Data2: 12}, + NoNameTagInline: NoNameTagInline{Data3: 13}, + NoNameTagOmitempty: NoNameTagOmitempty{Data4: 14}, + NoNameTagEmbed: NoNameTagEmbed{Data5: 15}, + }, + expect: func() map[string]any { + m := map[string]any{ + "NoTag": map[string]any{"data0": int64(10)}, + "nameTagEmbedded": map[string]any{"data1": int64(11)}, + "NoNameTag": map[string]any{"data2": int64(12)}, + "NoNameTagInline": map[string]any{"data3": int64(13)}, + "NoNameTagOmitempty": map[string]any{"data4": int64(14)}, + } + if stdlibSupportsEmbedTag { + m["data5"] = int64(15) + } else { + m["NoNameTagEmbed"] = map[string]any{"data5": int64(15)} + } + return m + }(), + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + defer func() { + // handle panics + if err := recover(); err != nil { + t.Fatal(err) + } + }() + + // Check the expectation against stdlib + jsonData, err := json.Marshal(tc.obj) + if err != nil { + t.Fatal(err) + } + jsonUnstr := map[string]any{} + if err := json.Unmarshal(jsonData, &jsonUnstr); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(tc.expect, jsonUnstr) { + t.Fatal(cmp.Diff(tc.expect, jsonUnstr)) + } + + // Check the expectation against DefaultUnstructuredConverter.ToUnstructured + unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.obj) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(tc.expect, unstr) { + t.Fatal(cmp.Diff(tc.expect, unstr)) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter_zero_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter_zero_test.go new file mode 100644 index 0000000000..e807115a0e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/converter_zero_test.go @@ -0,0 +1,359 @@ +/* +Copyright 2025 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. +*/ + +// These tests are in a separate package to break cyclic dependency in tests. +// Unstructured type depends on unstructured converter package but we want to test how the converter handles +// the Unstructured type so we need to import both. + +package runtime + +import ( + encodingjson "encoding/json" + "fmt" + "reflect" + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/json" + + "github.com/google/go-cmp/cmp" +) + +type ZeroParent struct { + Int int `json:"int,omitzero"` + IntP *int `json:"intP,omitzero"` + String string `json:"string,omitzero"` + StringP *string `json:"stringP,omitzero"` + Bool bool `json:"bool,omitzero"` + BoolP bool `json:"boolP,omitzero"` + Slice []int `json:"slice,omitzero"` + SliceP *[]int `json:"sliceP,omitzero"` + Map map[string]int `json:"map,omitzero"` + MapP *map[string]int `json:"mapP,omitzero"` + Struct ZeroChild `json:"struct,omitzero"` + StructP *ZeroChild `json:"structP,omitzero"` + CustomPrimitive ZeroCustomPrimitive `json:"customPrimitive,omitzero"` + CustomPrimitiveP *ZeroCustomPrimitiveP `json:"customPrimitiveP,omitzero"` + CustomStruct ZeroCustomStruct `json:"customStruct,omitzero"` + CustomStructP *ZeroCustomStructP `json:"customStructP,omitzero"` + CustomPPrimitive ZeroCustomPPrimitive `json:"customPPrimitive,omitzero"` + CustomPPrimitiveP *ZeroCustomPPrimitiveP `json:"customPPrimitiveP,omitzero"` + CustomPStruct ZeroCustomPStruct `json:"customPStruct,omitzero"` + CustomPStructP *ZeroCustomPStructP `json:"customPStructP,omitzero"` +} +type ZeroChild struct { + Data int `json:"data"` +} + +type ZeroCustomPrimitive int + +func (z ZeroCustomPrimitive) IsZero() bool { + return z == 42 +} + +type ZeroCustomPrimitiveP int + +func (z ZeroCustomPrimitiveP) IsZero() bool { + return z == 42 +} + +type ZeroCustomStruct struct { + Data int `json:"data"` +} + +func (z ZeroCustomStruct) IsZero() bool { + return z.Data == 42 +} + +type ZeroCustomStructP struct { + Data int `json:"data"` +} + +func (z ZeroCustomStructP) IsZero() bool { + return z.Data == 42 +} + +type ZeroCustomPPrimitive int + +func (z *ZeroCustomPPrimitive) IsZero() bool { + return *z == 42 +} + +type ZeroCustomPPrimitiveP int + +func (z *ZeroCustomPPrimitiveP) IsZero() bool { + return *z == 42 +} + +type ZeroCustomPStruct struct { + Data int `json:"data"` +} + +func (z *ZeroCustomPStruct) IsZero() bool { + return z.Data == 42 +} + +type ZeroCustomPStructP struct { + Data int `json:"data"` +} + +func (z *ZeroCustomPStructP) IsZero() bool { + return z.Data == 42 +} + +func TestOmitZero2(t *testing.T) { + testcases := []struct { + name string + obj any + expect map[string]any + }{ + { + name: "emptyzero", + obj: &ZeroParent{}, + expect: map[string]any{ + "customPPrimitive": int64(0), + "customPStruct": map[string]any{"data": int64(0)}, + "customPrimitive": int64(0), + "customStruct": map[string]any{"data": int64(0)}, + }, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + jsonData, err := json.Marshal(tc.obj) + if err != nil { + t.Fatal(err) + } + jsonUnstructured := map[string]any{} + if err := json.Unmarshal(jsonData, &jsonUnstructured); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(jsonUnstructured, tc.expect) { + t.Fatal(cmp.Diff(tc.expect, jsonUnstructured)) + } + + unstr, err := DefaultUnstructuredConverter.ToUnstructured(tc.obj) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(unstr, tc.expect) { + t.Fatal(cmp.Diff(tc.expect, unstr)) + } + }) + } +} + +type NonZeroStruct struct{} + +func (nzs NonZeroStruct) IsZero() bool { + return false +} + +type NoPanicStruct struct { + Int int `json:"int,omitzero"` +} + +func (nps *NoPanicStruct) IsZero() bool { + return nps.Int != 0 +} + +type isZeroer interface { + IsZero() bool +} + +type OptionalsZero struct { + Sr string `json:"sr"` + So string `json:"so,omitzero"` + Sw string `json:"-"` + + Ir int `json:"omitzero"` // actually named omitzero, not an option + Io int `json:"io,omitzero"` + + Slr []string `json:"slr,random"` //nolint:staticcheck // SA5008 + Slo []string `json:"slo,omitzero"` + SloNonNil []string `json:"slononnil,omitzero"` + + Mr map[string]any `json:"mr"` + Mo map[string]any `json:",omitzero"` + Moo map[string]any `json:"moo,omitzero"` + + Fr float64 `json:"fr"` + Fo float64 `json:"fo,omitzero"` + Foo float64 `json:"foo,omitzero"` + Foo2 [2]float64 `json:"foo2,omitzero"` + + Br bool `json:"br"` + Bo bool `json:"bo,omitzero"` + + Ur uint `json:"ur"` + Uo uint `json:"uo,omitzero"` + + Str struct{} `json:"str"` + Sto struct{} `json:"sto,omitzero"` + + Time time.Time `json:"time,omitzero"` + TimeLocal time.Time `json:"timelocal,omitzero"` + Nzs NonZeroStruct `json:"nzs,omitzero"` + + NilIsZeroer isZeroer `json:"niliszeroer,omitzero"` // nil interface + NonNilIsZeroer isZeroer `json:"nonniliszeroer,omitzero"` // non-nil interface + NoPanicStruct0 isZeroer `json:"nps0,omitzero"` // non-nil interface with nil pointer + NoPanicStruct1 isZeroer `json:"nps1,omitzero"` // non-nil interface with non-nil pointer + NoPanicStruct2 *NoPanicStruct `json:"nps2,omitzero"` // nil pointer + NoPanicStruct3 *NoPanicStruct `json:"nps3,omitzero"` // non-nil pointer + NoPanicStruct4 NoPanicStruct `json:"nps4,omitzero"` // concrete type +} + +func TestOmitZero(t *testing.T) { + const want = `{ + "Mo": {}, + "br": false, + "fr": 0, + "mr": {}, + "nps1": {}, + "nps3": {}, + "nps4": {}, + "nzs": {}, + "omitzero": 0, + "slononnil": [], + "slr": null, + "sr": "", + "str": {}, + "ur": 0 +}` + var o OptionalsZero + o.Sw = "something" + o.SloNonNil = make([]string, 0) + o.Mr = map[string]any{} + o.Mo = map[string]any{} + + o.Foo = -0 + o.Foo2 = [2]float64{+0, -0} + + o.TimeLocal = time.Time{}.Local() + + o.NonNilIsZeroer = time.Time{} + o.NoPanicStruct0 = (*NoPanicStruct)(nil) + o.NoPanicStruct1 = &NoPanicStruct{} + o.NoPanicStruct3 = &NoPanicStruct{} + + unstr, err := DefaultUnstructuredConverter.ToUnstructured(&o) + if err != nil { + t.Fatal(err) + } + + got, err := encodingjson.MarshalIndent(unstr, "", " ") + if err != nil { + t.Fatalf("MarshalIndent error: %v", err) + } + if got := string(got); got != want { + t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", got, want) + } +} + +func TestOmitZeroMap(t *testing.T) { + const want = `{ + "foo": { + "br": false, + "fr": 0, + "mr": null, + "nps4": {}, + "nzs": {}, + "omitzero": 0, + "slr": null, + "sr": "", + "str": {}, + "ur": 0 + } +}` + + m := map[string]OptionalsZero{"foo": {}} + + unstr, err := DefaultUnstructuredConverter.ToUnstructured(&m) + if err != nil { + t.Fatal(err) + } + + got, err := encodingjson.MarshalIndent(unstr, "", " ") + if err != nil { + t.Fatalf("MarshalIndent error: %v", err) + } + if got := string(got); got != want { + fmt.Println(got) + t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", got, want) + } +} + +type OptionalsEmptyZero struct { + Sr string `json:"sr"` + So string `json:"so,omitempty,omitzero"` + Sw string `json:"-"` + + Io int `json:"io,omitempty,omitzero"` + + Slr []string `json:"slr,random"` //nolint:staticcheck // SA5008 + Slo []string `json:"slo,omitempty,omitzero"` + SloNonNil []string `json:"slononnil,omitempty,omitzero"` + + Mr map[string]any `json:"mr"` + Mo map[string]any `json:",omitempty,omitzero"` + + Fr float64 `json:"fr"` + Fo float64 `json:"fo,omitempty,omitzero"` + + Br bool `json:"br"` + Bo bool `json:"bo,omitempty,omitzero"` + + Ur uint `json:"ur"` + Uo uint `json:"uo,omitempty,omitzero"` + + Str struct{} `json:"str"` + Sto struct{} `json:"sto,omitempty,omitzero"` + + Time time.Time `json:"time,omitempty,omitzero"` + Nzs NonZeroStruct `json:"nzs,omitempty,omitzero"` +} + +func TestOmitEmptyZero(t *testing.T) { + const want = `{ + "br": false, + "fr": 0, + "mr": {}, + "nzs": {}, + "slr": null, + "sr": "", + "str": {}, + "ur": 0 +}` + var o OptionalsEmptyZero + o.Sw = "something" + o.SloNonNil = make([]string, 0) + o.Mr = map[string]any{} + o.Mo = map[string]any{} + + unstr, err := DefaultUnstructuredConverter.ToUnstructured(&o) + if err != nil { + t.Fatal(err) + } + + got, err := encodingjson.MarshalIndent(unstr, "", " ") + if err != nil { + t.Fatalf("MarshalIndent error: %v", err) + } + if got := string(got); got != want { + t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", got, want) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/doc.go new file mode 100644 index 0000000000..fd012dbc79 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/doc.go @@ -0,0 +1,53 @@ +/* +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. +*/ + +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.runtime + +// Package runtime includes helper functions for working with API objects +// that follow the kubernetes API object conventions, which are: +// +// 0. Your API objects have a common metadata struct member, TypeMeta. +// +// 1. Your code refers to an internal set of API objects. +// +// 2. In a separate package, you have an external set of API objects. +// +// 3. The external set is considered to be versioned, and no breaking +// changes are ever made to it (fields may be added but not changed +// or removed). +// +// 4. As your api evolves, you'll make an additional versioned package +// with every major change. +// +// 5. Versioned packages have conversion functions which convert to +// and from the internal version. +// +// 6. You'll continue to support older versions according to your +// deprecation policy, and you can easily provide a program/library +// to update old versions into new versions because of 5. +// +// 7. All of your serializations and deserializations are handled in a +// centralized place. +// +// Package runtime provides a conversion helper to make 5 easy, and the +// Encode/Decode/DecodeInto trio to accomplish 7. You can also register +// additional "codecs" which use a version of your choice. It's +// recommended that you register your types with runtime in your +// package's init function. +// +// As a bonus, a few common types useful from all api objects and versions +// are provided in types.go. +package runtime diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/embedded.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/embedded.go new file mode 100644 index 0000000000..7251e65f6e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/embedded.go @@ -0,0 +1,149 @@ +/* +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 runtime + +import ( + "errors" + + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type encodable struct { + E Encoder `json:"-"` + obj Object + versions []schema.GroupVersion +} + +func (e encodable) GetObjectKind() schema.ObjectKind { return e.obj.GetObjectKind() } +func (e encodable) DeepCopyObject() Object { + out := e + out.obj = e.obj.DeepCopyObject() + copy(out.versions, e.versions) + return out +} + +// NewEncodable creates an object that will be encoded with the provided codec on demand. +// Provided as a convenience for test cases dealing with internal objects. +func NewEncodable(e Encoder, obj Object, versions ...schema.GroupVersion) Object { + if _, ok := obj.(*Unknown); ok { + return obj + } + return encodable{e, obj, versions} +} + +func (e encodable) UnmarshalJSON(in []byte) error { + return errors.New("runtime.encodable cannot be unmarshalled from JSON") +} + +// Marshal may get called on pointers or values, so implement MarshalJSON on value. +// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go +func (e encodable) MarshalJSON() ([]byte, error) { + return Encode(e.E, e.obj) +} + +// NewEncodableList creates an object that will be encoded with the provided codec on demand. +// Provided as a convenience for test cases dealing with internal objects. +func NewEncodableList(e Encoder, objects []Object, versions ...schema.GroupVersion) []Object { + out := make([]Object, len(objects)) + for i := range objects { + if _, ok := objects[i].(*Unknown); ok { + out[i] = objects[i] + continue + } + out[i] = NewEncodable(e, objects[i], versions...) + } + return out +} + +func (e *Unknown) UnmarshalJSON(in []byte) error { + if e == nil { + return errors.New("runtime.Unknown: UnmarshalJSON on nil pointer") + } + e.TypeMeta = TypeMeta{} + e.Raw = append(e.Raw[0:0], in...) + e.ContentEncoding = "" + e.ContentType = ContentTypeJSON + return nil +} + +// Marshal may get called on pointers or values, so implement MarshalJSON on value. +// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go +func (e Unknown) MarshalJSON() ([]byte, error) { + // If ContentType is unset, we assume this is JSON. + if e.ContentType != "" && e.ContentType != ContentTypeJSON { + return nil, errors.New("runtime.Unknown: MarshalJSON on non-json data") + } + if e.Raw == nil { + return []byte("null"), nil + } + return e.Raw, nil +} + +func Convert_runtime_Object_To_runtime_RawExtension(in *Object, out *RawExtension, s conversion.Scope) error { + if in == nil { + out.Raw = []byte("null") + return nil + } + obj := *in + if unk, ok := obj.(*Unknown); ok { + if unk.Raw != nil { + out.Raw = unk.Raw + return nil + } + obj = out.Object + } + if obj == nil { + out.Raw = nil + return nil + } + out.Object = obj + return nil +} + +func Convert_runtime_RawExtension_To_runtime_Object(in *RawExtension, out *Object, s conversion.Scope) error { + if in.Object != nil { + *out = in.Object + return nil + } + data := in.Raw + if len(data) == 0 || (len(data) == 4 && string(data) == "null") { + *out = nil + return nil + } + *out = &Unknown{ + Raw: data, + // TODO: Set ContentEncoding and ContentType appropriately. + // Currently we set ContentTypeJSON to make tests passing. + ContentType: ContentTypeJSON, + } + return nil +} + +func RegisterEmbeddedConversions(s *Scheme) error { + if err := s.AddConversionFunc((*Object)(nil), (*RawExtension)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_runtime_Object_To_runtime_RawExtension(a.(*Object), b.(*RawExtension), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*RawExtension)(nil), (*Object)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_runtime_RawExtension_To_runtime_Object(a.(*RawExtension), b.(*Object), scope) + }); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/embedded_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/embedded_test.go new file mode 100644 index 0000000000..ded6584730 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/embedded_test.go @@ -0,0 +1,261 @@ +/* +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 runtime_test + +import ( + "encoding/json" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" + "k8s.io/apimachinery/pkg/util/diff" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} + externalGVK := externalGV.WithKind("ObjectTest") + + s := runtime.NewScheme() + s.AddKnownTypes(internalGV, &runtimetesting.ObjectTest{}) + s.AddKnownTypeWithName(externalGVK, &runtimetesting.ObjectTestExternal{}) + utilruntime.Must(runtimetesting.RegisterConversions(s)) + + codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) + + obj, gvk, err := codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{}]}`), nil, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + test := obj.(*runtimetesting.ObjectTest) + if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.Raw) != "{}" || unk.ContentType != runtime.ContentTypeJSON { + t.Fatalf("unexpected object: %#v", test.Items[0]) + } + if *gvk != externalGVK { + t.Fatalf("unexpected kind: %#v", gvk) + } + + obj, gvk, err = codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{"kind":"Other","apiVersion":"v1"}]}`), nil, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + test = obj.(*runtimetesting.ObjectTest) + if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.Raw) != `{"kind":"Other","apiVersion":"v1"}` || unk.ContentType != runtime.ContentTypeJSON { + t.Fatalf("unexpected object: %#v", test.Items[0]) + } + if *gvk != externalGVK { + t.Fatalf("unexpected kind: %#v", gvk) + } +} + +func TestArrayOfRuntimeObject(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} + + s := runtime.NewScheme() + s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{}) + s.AddKnownTypeWithName(externalGV.WithKind("EmbeddedTest"), &runtimetesting.EmbeddedTestExternal{}) + s.AddKnownTypes(internalGV, &runtimetesting.ObjectTest{}) + s.AddKnownTypeWithName(externalGV.WithKind("ObjectTest"), &runtimetesting.ObjectTestExternal{}) + utilruntime.Must(runtimetesting.RegisterConversions(s)) + + codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) + + innerItems := []runtime.Object{ + &runtimetesting.EmbeddedTest{ID: "baz"}, + } + items := []runtime.Object{ + &runtimetesting.EmbeddedTest{ID: "foo"}, + &runtimetesting.EmbeddedTest{ID: "bar"}, + // TODO: until YAML is removed, this JSON must be in ascending key order to ensure consistent roundtrip serialization + &runtime.Unknown{ + Raw: []byte(`{"apiVersion":"unknown.group/unknown","foo":"bar","kind":"OtherTest"}`), + ContentType: runtime.ContentTypeJSON, + }, + &runtimetesting.ObjectTest{ + Items: runtime.NewEncodableList(codec, innerItems), + }, + } + internal := &runtimetesting.ObjectTest{ + Items: runtime.NewEncodableList(codec, items), + } + wire, err := runtime.Encode(codec, internal) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("Wire format is:\n%s\n", string(wire)) + + obj := &runtimetesting.ObjectTestExternal{} + if err := json.Unmarshal(wire, obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("exact wire is: %s", string(obj.Items[0].Raw)) + + items[3] = &runtimetesting.ObjectTest{Items: innerItems} + internal.Items = items + + decoded, err := runtime.Decode(codec, wire) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + list, err := meta.ExtractList(decoded) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if errs := runtime.DecodeList(list, codec); len(errs) > 0 { + t.Fatalf("unexpected error: %v", errs) + } + + list2, err := meta.ExtractList(list[3]) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if errs := runtime.DecodeList(list2, codec); len(errs) > 0 { + t.Fatalf("unexpected error: %v", errs) + } + if err := meta.SetList(list[3], list2); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // we want DecodeList to set type meta if possible, even on runtime.Unknown objects + internal.Items[2].(*runtime.Unknown).TypeMeta = runtime.TypeMeta{Kind: "OtherTest", APIVersion: "unknown.group/unknown"} + if e, a := internal.Items, list; !reflect.DeepEqual(e, a) { + t.Errorf("mismatched decoded: %s", diff.ObjectGoPrintSideBySide(e, a)) + } +} + +func TestNestedObject(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} + embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest") + + s := runtime.NewScheme() + s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{}) + s.AddKnownTypeWithName(embeddedTestExternalGVK, &runtimetesting.EmbeddedTestExternal{}) + utilruntime.Must(runtimetesting.RegisterConversions(s)) + + codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) + + inner := &runtimetesting.EmbeddedTest{ + ID: "inner", + } + outer := &runtimetesting.EmbeddedTest{ + ID: "outer", + Object: runtime.NewEncodable(codec, inner), + } + + wire, err := runtime.Encode(codec, outer) + if err != nil { + t.Fatalf("Unexpected encode error '%v'", err) + } + + t.Logf("Wire format is:\n%v\n", string(wire)) + + decoded, err := runtime.Decode(codec, wire) + if err != nil { + t.Fatalf("Unexpected decode error %v", err) + } + + // for later tests + outer.Object = inner + + if e, a := outer, decoded; reflect.DeepEqual(e, a) { + t.Errorf("Expected unequal %#v %#v", e, a) + } + + obj, err := runtime.Decode(codec, decoded.(*runtimetesting.EmbeddedTest).Object.(*runtime.Unknown).Raw) + if err != nil { + t.Fatal(err) + } + decoded.(*runtimetesting.EmbeddedTest).Object = obj + if e, a := outer, decoded; !reflect.DeepEqual(e, a) { + t.Errorf("Expected equal %#v %#v", e, a) + } + + // test JSON decoding of the external object, which should preserve + // raw bytes + var externalViaJSON runtimetesting.EmbeddedTestExternal + err = json.Unmarshal(wire, &externalViaJSON) + if err != nil { + t.Fatalf("Unexpected decode error %v", err) + } + if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.ID != "outer" { + t.Errorf("Expected objects to have type info set, got %#v", externalViaJSON) + } + if len(externalViaJSON.EmptyObject.Raw) > 0 { + t.Errorf("Expected deserialization of empty nested objects into empty bytes, got %#v", externalViaJSON) + } + + // test JSON decoding, too, since Decode uses yaml unmarshalling. + // Generic Unmarshalling of JSON cannot load the nested objects because there is + // no default schema set. Consumers wishing to get direct JSON decoding must use + // the external representation + var decodedViaJSON runtimetesting.EmbeddedTest + err = json.Unmarshal(wire, &decodedViaJSON) + if err == nil { + t.Fatal("Expeceted decode error") + } + if _, ok := err.(*json.UnmarshalTypeError); !ok { + t.Fatalf("Unexpected decode error: %v", err) + } + if a := decodedViaJSON; a.Object != nil || a.EmptyObject != nil { + t.Errorf("Expected embedded objects to be nil: %#v", a) + } +} + +// TestDeepCopyOfRuntimeObject checks to make sure that runtime.Objects's can be passed through DeepCopy with fidelity +func TestDeepCopyOfRuntimeObject(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} + embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest") + + s := runtime.NewScheme() + s.AddKnownTypes(internalGV, &runtimetesting.EmbeddedTest{}) + s.AddKnownTypeWithName(embeddedTestExternalGVK, &runtimetesting.EmbeddedTestExternal{}) + utilruntime.Must(runtimetesting.RegisterConversions(s)) + + original := &runtimetesting.EmbeddedTest{ + ID: "outer", + Object: &runtimetesting.EmbeddedTest{ + ID: "inner", + }, + } + + codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) + + originalData, err := runtime.Encode(codec, original) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + t.Logf("originalRole = %v\n", string(originalData)) + + copyOfOriginal := original.DeepCopy() + copiedData, err := runtime.Encode(codec, copyOfOriginal) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + t.Logf("copyOfRole = %v\n", string(copiedData)) + + if !reflect.DeepEqual(original, copyOfOriginal) { + t.Errorf("expected \n%v\n, got \n%v", string(originalData), string(copiedData)) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/error.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/error.go new file mode 100644 index 0000000000..7dfa45762f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/error.go @@ -0,0 +1,172 @@ +/* +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 runtime + +import ( + "fmt" + "reflect" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type notRegisteredErr struct { + schemeName string + gvk schema.GroupVersionKind + target GroupVersioner + t reflect.Type +} + +func NewNotRegisteredErrForKind(schemeName string, gvk schema.GroupVersionKind) error { + return ¬RegisteredErr{schemeName: schemeName, gvk: gvk} +} + +func NewNotRegisteredErrForType(schemeName string, t reflect.Type) error { + return ¬RegisteredErr{schemeName: schemeName, t: t} +} + +func NewNotRegisteredErrForTarget(schemeName string, t reflect.Type, target GroupVersioner) error { + return ¬RegisteredErr{schemeName: schemeName, t: t, target: target} +} + +func NewNotRegisteredGVKErrForTarget(schemeName string, gvk schema.GroupVersionKind, target GroupVersioner) error { + return ¬RegisteredErr{schemeName: schemeName, gvk: gvk, target: target} +} + +func (k *notRegisteredErr) Error() string { + if k.t != nil && k.target != nil { + return fmt.Sprintf("%v is not suitable for converting to %q in scheme %q", k.t, k.target, k.schemeName) + } + nullGVK := schema.GroupVersionKind{} + if k.gvk != nullGVK && k.target != nil { + return fmt.Sprintf("%q is not suitable for converting to %q in scheme %q", k.gvk.GroupVersion(), k.target, k.schemeName) + } + if k.t != nil { + return fmt.Sprintf("no kind is registered for the type %v in scheme %q", k.t, k.schemeName) + } + if len(k.gvk.Kind) == 0 { + return fmt.Sprintf("no version %q has been registered in scheme %q", k.gvk.GroupVersion(), k.schemeName) + } + if k.gvk.Version == APIVersionInternal { + return fmt.Sprintf("no kind %q is registered for the internal version of group %q in scheme %q", k.gvk.Kind, k.gvk.Group, k.schemeName) + } + + return fmt.Sprintf("no kind %q is registered for version %q in scheme %q", k.gvk.Kind, k.gvk.GroupVersion(), k.schemeName) +} + +// IsNotRegisteredError returns true if the error indicates the provided +// object or input data is not registered. +func IsNotRegisteredError(err error) bool { + if err == nil { + return false + } + _, ok := err.(*notRegisteredErr) + return ok +} + +type missingKindErr struct { + data string +} + +func NewMissingKindErr(data string) error { + return &missingKindErr{data} +} + +func (k *missingKindErr) Error() string { + return fmt.Sprintf("Object 'Kind' is missing in '%s'", k.data) +} + +// IsMissingKind returns true if the error indicates that the provided object +// is missing a 'Kind' field. +func IsMissingKind(err error) bool { + if err == nil { + return false + } + _, ok := err.(*missingKindErr) + return ok +} + +type missingVersionErr struct { + data string +} + +func NewMissingVersionErr(data string) error { + return &missingVersionErr{data} +} + +func (k *missingVersionErr) Error() string { + return fmt.Sprintf("Object 'apiVersion' is missing in '%s'", k.data) +} + +// IsMissingVersion returns true if the error indicates that the provided object +// is missing a 'Version' field. +func IsMissingVersion(err error) bool { + if err == nil { + return false + } + _, ok := err.(*missingVersionErr) + return ok +} + +// strictDecodingError is a base error type that is returned by a strict Decoder such +// as UniversalStrictDecoder. +type strictDecodingError struct { + errors []error +} + +// NewStrictDecodingError creates a new strictDecodingError object. +func NewStrictDecodingError(errors []error) error { + return &strictDecodingError{ + errors: errors, + } +} + +func (e *strictDecodingError) Error() string { + var s strings.Builder + s.WriteString("strict decoding error: ") + for i, err := range e.errors { + if i != 0 { + s.WriteString(", ") + } + s.WriteString(err.Error()) + } + return s.String() +} + +func (e *strictDecodingError) Errors() []error { + return e.errors +} + +// IsStrictDecodingError returns true if the error indicates that the provided object +// strictness violations. +func IsStrictDecodingError(err error) bool { + if err == nil { + return false + } + _, ok := err.(*strictDecodingError) + return ok +} + +// AsStrictDecodingError returns a strict decoding error +// containing all the strictness violations. +func AsStrictDecodingError(err error) (*strictDecodingError, bool) { + if err == nil { + return nil, false + } + strictErr, ok := err.(*strictDecodingError) + return strictErr, ok +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/extension.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/extension.go new file mode 100644 index 0000000000..60c000bcb7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/extension.go @@ -0,0 +1,141 @@ +/* +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 runtime + +import ( + "bytes" + "errors" + "fmt" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "k8s.io/apimachinery/pkg/util/json" +) + +// RawExtension intentionally avoids implementing value.UnstructuredConverter for now because the +// signature of ToUnstructured does not allow returning an error value in cases where the conversion +// is not possible (content type is unrecognized or bytes don't match content type). +func rawToUnstructured(raw []byte, contentType string) (interface{}, error) { + switch contentType { + case ContentTypeJSON: + var u interface{} + if err := json.Unmarshal(raw, &u); err != nil { + return nil, fmt.Errorf("failed to parse RawExtension bytes as JSON: %w", err) + } + return u, nil + case ContentTypeCBOR: + var u interface{} + if err := cbor.Unmarshal(raw, &u); err != nil { + return nil, fmt.Errorf("failed to parse RawExtension bytes as CBOR: %w", err) + } + return u, nil + default: + return nil, fmt.Errorf("cannot convert RawExtension with unrecognized content type to unstructured") + } +} + +func (re RawExtension) guessContentType() string { + switch { + case bytes.HasPrefix(re.Raw, cborSelfDescribed): + return ContentTypeCBOR + case len(re.Raw) > 0: + switch re.Raw[0] { + case '\t', '\r', '\n', ' ', '{', '[', 'n', 't', 'f', '"', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + // Prefixes for the four whitespace characters, objects, arrays, strings, numbers, true, false, and null. + return ContentTypeJSON + } + } + return "" +} + +func (re *RawExtension) UnmarshalJSON(in []byte) error { + if re == nil { + return errors.New("runtime.RawExtension: UnmarshalJSON on nil pointer") + } + if bytes.Equal(in, []byte("null")) { + return nil + } + re.Raw = append(re.Raw[0:0], in...) + return nil +} + +var ( + cborNull = []byte{0xf6} + cborSelfDescribed = []byte{0xd9, 0xd9, 0xf7} +) + +func (re *RawExtension) UnmarshalCBOR(in []byte) error { + if re == nil { + return errors.New("runtime.RawExtension: UnmarshalCBOR on nil pointer") + } + if !bytes.Equal(in, cborNull) { + if !bytes.HasPrefix(in, cborSelfDescribed) { + // The self-described CBOR tag doesn't change the interpretation of the data + // item it encloses, but it is useful as a magic number. Its encoding is + // also what is used to implement the CBOR RecognizingDecoder. + re.Raw = append(re.Raw[:0], cborSelfDescribed...) + } + re.Raw = append(re.Raw, in...) + } + return nil +} + +// MarshalJSON may get called on pointers or values, so implement MarshalJSON on value. +// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go +func (re RawExtension) MarshalJSON() ([]byte, error) { + if re.Raw == nil { + // TODO: this is to support legacy behavior of JSONPrinter and YAMLPrinter, which + // expect to call json.Marshal on arbitrary versioned objects (even those not in + // the scheme). pkg/kubectl/resource#AsVersionedObjects and its interaction with + // kubectl get on objects not in the scheme needs to be updated to ensure that the + // objects that are not part of the scheme are correctly put into the right form. + if re.Object != nil { + return json.Marshal(re.Object) + } + return []byte("null"), nil + } + + contentType := re.guessContentType() + if contentType == ContentTypeJSON { + return re.Raw, nil + } + + u, err := rawToUnstructured(re.Raw, contentType) + if err != nil { + return nil, err + } + return json.Marshal(u) +} + +func (re RawExtension) MarshalCBOR() ([]byte, error) { + if re.Raw == nil { + if re.Object != nil { + return cbor.Marshal(re.Object) + } + return cbor.Marshal(nil) + } + + contentType := re.guessContentType() + if contentType == ContentTypeCBOR { + return re.Raw, nil + } + + u, err := rawToUnstructured(re.Raw, contentType) + if err != nil { + return nil, err + } + return cbor.Marshal(u) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/extension_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/extension_test.go new file mode 100644 index 0000000000..3a296987b7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/extension_test.go @@ -0,0 +1,264 @@ +/* +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 runtime_test + +import ( + "bytes" + "encoding/json" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" + + "github.com/google/go-cmp/cmp" +) + +func TestEmbeddedRawExtensionMarshal(t *testing.T) { + type test struct { + Ext runtime.RawExtension + } + + extension := test{Ext: runtime.RawExtension{Raw: []byte(`{"foo":"bar"}`)}} + data, err := json.Marshal(extension) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(data) != `{"Ext":{"foo":"bar"}}` { + t.Errorf("unexpected data: %s", string(data)) + } +} +func TestEmbeddedRawExtensionUnmarshal(t *testing.T) { + type test struct { + Ext runtime.RawExtension + } + + testCases := map[string]struct { + orig test + }{ + "non-empty object": { + orig: test{Ext: runtime.RawExtension{Raw: []byte(`{"foo":"bar"}`)}}, + }, + "empty object": { + orig: test{Ext: runtime.RawExtension{}}, + }, + } + + for k, tc := range testCases { + new := test{} + data, _ := json.Marshal(tc.orig) + if err := json.Unmarshal(data, &new); err != nil { + t.Errorf("%s: umarshal error: %v", k, err) + } + if !reflect.DeepEqual(tc.orig, new) { + t.Errorf("%s: unmarshaled struct differs from original: %v %v", k, tc.orig, new) + } + } +} + +func TestEmbeddedRawExtensionRoundTrip(t *testing.T) { + type test struct { + Ext runtime.RawExtension + } + + testCases := map[string]struct { + orig test + }{ + "non-empty object": { + orig: test{Ext: runtime.RawExtension{Raw: []byte(`{"foo":"bar"}`)}}, + }, + "empty object": { + orig: test{Ext: runtime.RawExtension{}}, + }, + } + + for k, tc := range testCases { + new1 := test{} + new2 := test{} + data, err := json.Marshal(tc.orig) + if err != nil { + t.Errorf("1st marshal error: %v", err) + } + if err = json.Unmarshal(data, &new1); err != nil { + t.Errorf("1st unmarshal error: %v", err) + } + newData, err := json.Marshal(new1) + if err != nil { + t.Errorf("2st marshal error: %v", err) + } + if err = json.Unmarshal(newData, &new2); err != nil { + t.Errorf("2nd unmarshal error: %v", err) + } + if !bytes.Equal(data, newData) { + t.Errorf("%s: re-marshaled data differs from original: %v %v", k, data, newData) + } + if !reflect.DeepEqual(tc.orig, new1) { + t.Errorf("%s: unmarshaled struct differs from original: %v %v", k, tc.orig, new1) + } + if !reflect.DeepEqual(new1, new2) { + t.Errorf("%s: re-unmarshaled struct differs from original: %v %v", k, new1, new2) + } + } +} + +func TestRawExtensionMarshalUnstructured(t *testing.T) { + for _, tc := range []struct { + Name string + In runtime.RawExtension + WantCBOR []byte + ExpectedErrorCBOR string + WantJSON string + ExpectedErrorJSON string + }{ + { + Name: "nil bytes and nil object", + In: runtime.RawExtension{}, + WantCBOR: []byte{0xf6}, + WantJSON: "null", + }, + { + Name: "nil bytes and non-nil object", + In: runtime.RawExtension{Object: &runtimetesting.ExternalSimple{TestString: "foo"}}, + WantCBOR: []byte("\xa1\x4atestString\x43foo"), + WantJSON: `{"testString":"foo"}`, + }, + { + Name: "cbor bytes not enclosed in self-described tag", + In: runtime.RawExtension{Raw: []byte{0x43, 'f', 'o', 'o'}}, // 'foo' + ExpectedErrorCBOR: "cannot convert RawExtension with unrecognized content type to unstructured", + ExpectedErrorJSON: "cannot convert RawExtension with unrecognized content type to unstructured", + }, + { + Name: "cbor bytes enclosed in self-described tag", + In: runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0x43, 'f', 'o', 'o'}}, // 55799('foo') + WantCBOR: []byte{0xd9, 0xd9, 0xf7, 0x43, 'f', 'o', 'o'}, // 55799('foo') + WantJSON: `"foo"`, + }, + { + Name: "json bytes", + In: runtime.RawExtension{Raw: []byte(`"foo"`)}, + WantCBOR: []byte{0x43, 'f', 'o', 'o'}, + WantJSON: `"foo"`, + }, + { + Name: "ambiguous bytes not enclosed in self-described cbor tag", + In: runtime.RawExtension{Raw: []byte{'0'}}, // CBOR -17 / JSON 0 + WantCBOR: []byte{0x00}, + WantJSON: `0`, + }, + { + Name: "ambiguous bytes enclosed in self-described cbor tag", + In: runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, '0'}}, // 55799(-17) + WantCBOR: []byte{0xd9, 0xd9, 0xf7, '0'}, + WantJSON: `-17`, + }, + { + Name: "unrecognized bytes", + In: runtime.RawExtension{Raw: []byte{0xff}}, + ExpectedErrorCBOR: "cannot convert RawExtension with unrecognized content type to unstructured", + ExpectedErrorJSON: "cannot convert RawExtension with unrecognized content type to unstructured", + }, + { + Name: "invalid cbor with self-described cbor prefix", + In: runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0xff}}, + WantCBOR: []byte{0xd9, 0xd9, 0xf7, 0xff}, // verbatim + ExpectedErrorJSON: `failed to parse RawExtension bytes as CBOR: cbor: unexpected "break" code`, + }, + { + Name: "invalid json with json prefix", + In: runtime.RawExtension{Raw: []byte(`{{`)}, + ExpectedErrorCBOR: `failed to parse RawExtension bytes as JSON: invalid character '{' looking for beginning of object key string`, + WantJSON: `{{`, // verbatim + }, + } { + t.Run(tc.Name, func(t *testing.T) { + t.Run("CBOR", func(t *testing.T) { + got, err := tc.In.MarshalCBOR() + if err != nil { + if tc.ExpectedErrorCBOR == "" { + t.Fatalf("unexpected error: %v", err) + } + if msg := err.Error(); msg != tc.ExpectedErrorCBOR { + t.Fatalf("expected error %q but got %q", tc.ExpectedErrorCBOR, msg) + } + } + + if diff := cmp.Diff(tc.WantCBOR, got); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + + t.Run("JSON", func(t *testing.T) { + got, err := tc.In.MarshalJSON() + if err != nil { + if tc.ExpectedErrorJSON == "" { + t.Fatalf("unexpected error: %v", err) + } + if msg := err.Error(); msg != tc.ExpectedErrorJSON { + t.Fatalf("expected error %q but got %q", tc.ExpectedErrorJSON, msg) + } + } + + if diff := cmp.Diff(tc.WantJSON, string(got)); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + }) + } +} + +func TestRawExtensionUnmarshalCBOR(t *testing.T) { + for _, tc := range []struct { + Name string + In []byte + Want runtime.RawExtension + }{ + { + // From json.Unmarshaler: By convention, to approximate the behavior of + // Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as + // a no-op. + Name: "no-op on null", + In: []byte{0xf6}, + Want: runtime.RawExtension{}, + }, + { + Name: "input copied verbatim", + In: []byte{0xd9, 0xd9, 0xf7, 0x5f, 0x41, 'f', 0x42, 'o', 'o', 0xff}, // 55799(_ 'f' 'oo') + Want: runtime.RawExtension{ + Raw: []byte{0xd9, 0xd9, 0xf7, 0x5f, 0x41, 'f', 0x42, 'o', 'o', 0xff}, // 55799(_ 'f' 'oo') + }, + }, + { + Name: "input enclosed in self-described tag if absent", + In: []byte{0x5f, 0x41, 'f', 0x42, 'o', 'o', 0xff}, // (_ 'f' 'oo') + Want: runtime.RawExtension{ + Raw: []byte{0xd9, 0xd9, 0xf7, 0x5f, 0x41, 'f', 0x42, 'o', 'o', 0xff}, // 55799(_ 'f' 'oo') + }, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + var got runtime.RawExtension + if err := got.UnmarshalCBOR(tc.In); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if diff := cmp.Diff(tc.Want, got); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_126.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_126.go new file mode 100644 index 0000000000..686ff6abb3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_126.go @@ -0,0 +1,26 @@ +//go:build !go1.27 + +/* +Copyright 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 runtime + +import "reflect" + +func isInlinedFromTag(fieldType reflect.StructField, tagName string, tagDirectives []string) bool { + // go <1.27 doesn't honor ",inline" + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_126_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_126_test.go new file mode 100644 index 0000000000..b1da16b9a1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_126_test.go @@ -0,0 +1,21 @@ +//go:build !go1.27 + +/* +Copyright 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 runtime_test + +const stdlibSupportsEmbedTag = false diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_127.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_127.go new file mode 100644 index 0000000000..abaa79a2db --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_127.go @@ -0,0 +1,33 @@ +//go:build go1.27 + +/* +Copyright 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 runtime + +import ( + "reflect" +) + +func isInlinedFromTag(field reflect.StructField, tagName string, tagDirectives []string) bool { + fieldType := field.Type + if fieldType.Kind() == reflect.Pointer && fieldType.Name() == "" { + // optionally unwrap a single level + fieldType = fieldType.Elem() + } + // TODO: when switching to direct use of json/v2, error on non-struct embedding and use of embed with other directives + return fieldType.Kind() == reflect.Struct && tagName == "" && len(tagDirectives) == 1 && tagDirectives[0] == "embed" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_127_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_127_test.go new file mode 100644 index 0000000000..8cb7fc1e51 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/fieldinfo_127_test.go @@ -0,0 +1,21 @@ +//go:build go1.27 + +/* +Copyright 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 runtime_test + +const stdlibSupportsEmbedTag = true diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/generated.pb.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/generated.pb.go new file mode 100644 index 0000000000..f5e78d4b36 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/generated.pb.go @@ -0,0 +1,716 @@ +/* +Copyright 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/apimachinery/pkg/runtime/generated.proto + +package runtime + +import ( + fmt "fmt" + + io "io" + math_bits "math/bits" + reflect "reflect" + strings "strings" +) + +func (m *RawExtension) Reset() { *m = RawExtension{} } + +func (m *TypeMeta) Reset() { *m = TypeMeta{} } + +func (m *Unknown) Reset() { *m = Unknown{} } + +func (m *RawExtension) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RawExtension) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RawExtension) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Raw != nil { + i -= len(m.Raw) + copy(dAtA[i:], m.Raw) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Raw))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TypeMeta) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TypeMeta) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TypeMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Kind) + copy(dAtA[i:], m.Kind) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Kind))) + i-- + dAtA[i] = 0x12 + i -= len(m.APIVersion) + copy(dAtA[i:], m.APIVersion) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.APIVersion))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Unknown) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Unknown) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Unknown) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.ContentType) + copy(dAtA[i:], m.ContentType) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ContentType))) + i-- + dAtA[i] = 0x22 + i -= len(m.ContentEncoding) + copy(dAtA[i:], m.ContentEncoding) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ContentEncoding))) + i-- + dAtA[i] = 0x1a + if m.Raw != nil { + i -= len(m.Raw) + copy(dAtA[i:], m.Raw) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Raw))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.TypeMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *RawExtension) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Raw != nil { + l = len(m.Raw) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *TypeMeta) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.APIVersion) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Kind) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *Unknown) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.TypeMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if m.Raw != nil { + l = len(m.Raw) + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.ContentEncoding) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.ContentType) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *RawExtension) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RawExtension{`, + `Raw:` + valueToStringGenerated(this.Raw) + `,`, + `}`, + }, "") + return s +} +func (this *TypeMeta) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&TypeMeta{`, + `APIVersion:` + fmt.Sprintf("%v", this.APIVersion) + `,`, + `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, + `}`, + }, "") + return s +} +func (this *Unknown) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Unknown{`, + `TypeMeta:` + strings.Replace(strings.Replace(this.TypeMeta.String(), "TypeMeta", "TypeMeta", 1), `&`, ``, 1) + `,`, + `Raw:` + valueToStringGenerated(this.Raw) + `,`, + `ContentEncoding:` + fmt.Sprintf("%v", this.ContentEncoding) + `,`, + `ContentType:` + fmt.Sprintf("%v", this.ContentType) + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *RawExtension) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RawExtension: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RawExtension: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Raw = append(m.Raw[:0], dAtA[iNdEx:postIndex]...) + if m.Raw == nil { + m.Raw = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TypeMeta) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TypeMeta: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TypeMeta: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field APIVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.APIVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kind = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Unknown) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Unknown: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Unknown: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TypeMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TypeMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Raw", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Raw = append(m.Raw[:0], dAtA[iNdEx:postIndex]...) + if m.Raw == nil { + m.Raw = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContentEncoding", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContentEncoding = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContentType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContentType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenerated + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenerated + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/generated.proto b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/generated.proto new file mode 100644 index 0000000000..93d187d94a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/generated.proto @@ -0,0 +1,134 @@ +/* +Copyright 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.apimachinery.pkg.runtime; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/apimachinery/pkg/runtime"; + +// RawExtension is used to hold extensions in external versions. +// +// To use this, make a field which has RawExtension as its type in your external, versioned +// struct, and Object in your internal struct. You also need to register your +// various plugin types. +// +// // Internal package: +// +// type MyAPIObject struct { +// runtime.TypeMeta `json:""` +// MyPlugin runtime.Object `json:"myPlugin"` +// } +// +// type PluginA struct { +// AOption string `json:"aOption"` +// } +// +// // External package: +// +// type MyAPIObject struct { +// runtime.TypeMeta `json:""` +// MyPlugin runtime.RawExtension `json:"myPlugin"` +// } +// +// type PluginA struct { +// AOption string `json:"aOption"` +// } +// +// // On the wire, the JSON will look something like this: +// +// { +// "kind":"MyAPIObject", +// "apiVersion":"v1", +// "myPlugin": { +// "kind":"PluginA", +// "aOption":"foo", +// }, +// } +// +// So what happens? Decode first uses json or yaml to unmarshal the serialized data into +// your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. +// The next step is to copy (using pkg/conversion) into the internal struct. The runtime +// package's DefaultScheme has conversion functions installed which will unpack the +// JSON stored in RawExtension, turning it into the correct object type, and storing it +// in the Object. (TODO: In the case where the object is of an unknown type, a +// runtime.Unknown object will be created and stored.) +// +// +k8s:deepcopy-gen=true +// +protobuf=true +// +k8s:openapi-gen=true +message RawExtension { + // Raw is the underlying serialization of this object. + // + // TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data. + optional bytes raw = 1; +} + +// TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, +// like this: +// +// type MyAwesomeAPIObject struct { +// runtime.TypeMeta `json:""` +// ... // other fields +// } +// +// func (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind +// +// TypeMeta is provided here for convenience. You may use it directly from this package or define +// your own with the same fields. +// +// +k8s:deepcopy-gen=false +// +protobuf=true +// +k8s:openapi-gen=true +message TypeMeta { + // +optional + optional string apiVersion = 1; + + // +optional + optional string kind = 2; +} + +// Unknown allows api objects with unknown types to be passed-through. This can be used +// to deal with the API objects from a plug-in. Unknown objects still have functioning +// TypeMeta features-- kind, version, etc. +// TODO: Make this object have easy access to field based accessors and settors for +// metadata and field mutatation. +// +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +protobuf=true +// +k8s:openapi-gen=true +message Unknown { + optional TypeMeta typeMeta = 1; + + // Raw will hold the complete serialized object which couldn't be matched + // with a registered type. Most likely, nothing should be done with this + // except for passing it through the system. + optional bytes raw = 2; + + // ContentEncoding is encoding used to encode 'Raw' data. + // Unspecified means no encoding. + optional string contentEncoding = 3; + + // ContentType is serialization method used to serialize 'Raw'. + // Unspecified means ContentTypeJSON. + optional string contentType = 4; +} + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/helper.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/helper.go new file mode 100644 index 0000000000..242a349865 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/helper.go @@ -0,0 +1,310 @@ +/* +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 runtime + +import ( + "fmt" + "io" + "reflect" + + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/errors" +) + +// unsafeObjectConvertor implements ObjectConvertor using the unsafe conversion path. +type unsafeObjectConvertor struct { + *Scheme +} + +var _ ObjectConvertor = unsafeObjectConvertor{} + +// ConvertToVersion converts in to the provided outVersion without copying the input first, which +// is only safe if the output object is not mutated or reused. +func (c unsafeObjectConvertor) ConvertToVersion(in Object, outVersion GroupVersioner) (Object, error) { + return c.Scheme.UnsafeConvertToVersion(in, outVersion) +} + +// UnsafeObjectConvertor performs object conversion without copying the object structure, +// for use when the converted object will not be reused or mutated. Primarily for use within +// versioned codecs, which use the external object for serialization but do not return it. +func UnsafeObjectConvertor(scheme *Scheme) ObjectConvertor { + return unsafeObjectConvertor{scheme} +} + +// SetField puts the value of src, into fieldName, which must be a member of v. +// The value of src must be assignable to the field. +func SetField(src interface{}, v reflect.Value, fieldName string) error { + field := v.FieldByName(fieldName) + if !field.IsValid() { + return fmt.Errorf("couldn't find %v field in %T", fieldName, v.Interface()) + } + srcValue := reflect.ValueOf(src) + if srcValue.Type().AssignableTo(field.Type()) { + field.Set(srcValue) + return nil + } + if srcValue.Type().ConvertibleTo(field.Type()) { + field.Set(srcValue.Convert(field.Type())) + return nil + } + return fmt.Errorf("couldn't assign/convert %v to %v", srcValue.Type(), field.Type()) +} + +// Field puts the value of fieldName, which must be a member of v, into dest, +// which must be a variable to which this field's value can be assigned. +func Field(v reflect.Value, fieldName string, dest interface{}) error { + field := v.FieldByName(fieldName) + if !field.IsValid() { + return fmt.Errorf("couldn't find %v field in %T", fieldName, v.Interface()) + } + destValue, err := conversion.EnforcePtr(dest) + if err != nil { + return err + } + if field.Type().AssignableTo(destValue.Type()) { + destValue.Set(field) + return nil + } + if field.Type().ConvertibleTo(destValue.Type()) { + destValue.Set(field.Convert(destValue.Type())) + return nil + } + return fmt.Errorf("couldn't assign/convert %v to %v", field.Type(), destValue.Type()) +} + +// FieldPtr puts the address of fieldName, which must be a member of v, +// into dest, which must be an address of a variable to which this field's +// address can be assigned. +func FieldPtr(v reflect.Value, fieldName string, dest interface{}) error { + field := v.FieldByName(fieldName) + if !field.IsValid() { + return fmt.Errorf("couldn't find %v field in %T", fieldName, v.Interface()) + } + v, err := conversion.EnforcePtr(dest) + if err != nil { + return err + } + field = field.Addr() + if field.Type().AssignableTo(v.Type()) { + v.Set(field) + return nil + } + if field.Type().ConvertibleTo(v.Type()) { + v.Set(field.Convert(v.Type())) + return nil + } + return fmt.Errorf("couldn't assign/convert %v to %v", field.Type(), v.Type()) +} + +// EncodeList ensures that each object in an array is converted to a Unknown{} in serialized form. +// TODO: accept a content type. +func EncodeList(e Encoder, objects []Object) error { + var errs []error + for i := range objects { + data, err := Encode(e, objects[i]) + if err != nil { + errs = append(errs, err) + continue + } + // TODO: Set ContentEncoding and ContentType. + objects[i] = &Unknown{Raw: data} + } + return errors.NewAggregate(errs) +} + +func decodeListItem(obj *Unknown, decoders []Decoder) (Object, error) { + for _, decoder := range decoders { + // TODO: Decode based on ContentType. + obj, err := Decode(decoder, obj.Raw) + if err != nil { + if IsNotRegisteredError(err) { + continue + } + return nil, err + } + return obj, nil + } + // could not decode, so leave the object as Unknown, but give the decoders the + // chance to set Unknown.TypeMeta if it is available. + for _, decoder := range decoders { + if err := DecodeInto(decoder, obj.Raw, obj); err == nil { + return obj, nil + } + } + return obj, nil +} + +// DecodeList alters the list in place, attempting to decode any objects found in +// the list that have the Unknown type. Any errors that occur are returned +// after the entire list is processed. Decoders are tried in order. +func DecodeList(objects []Object, decoders ...Decoder) []error { + errs := []error(nil) + for i, obj := range objects { + switch t := obj.(type) { + case *Unknown: + decoded, err := decodeListItem(t, decoders) + if err != nil { + errs = append(errs, err) + break + } + objects[i] = decoded + } + } + return errs +} + +// MultiObjectTyper returns the types of objects across multiple schemes in order. +type MultiObjectTyper []ObjectTyper + +var _ ObjectTyper = MultiObjectTyper{} + +func (m MultiObjectTyper) ObjectKinds(obj Object) (gvks []schema.GroupVersionKind, unversionedType bool, err error) { + for _, t := range m { + gvks, unversionedType, err = t.ObjectKinds(obj) + if err == nil { + return + } + } + return +} + +func (m MultiObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool { + for _, t := range m { + if t.Recognizes(gvk) { + return true + } + } + return false +} + +// SetZeroValue would set the object of objPtr to zero value of its type. +func SetZeroValue(objPtr Object) error { + v, err := conversion.EnforcePtr(objPtr) + if err != nil { + return err + } + v.Set(reflect.Zero(v.Type())) + return nil +} + +// DefaultFramer is valid for any stream that can read objects serially without +// any separation in the stream. +var DefaultFramer = defaultFramer{} + +type defaultFramer struct{} + +func (defaultFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { return r } +func (defaultFramer) NewFrameWriter(w io.Writer) io.Writer { return w } + +// WithVersionEncoder serializes an object and ensures the GVK is set. +type WithVersionEncoder struct { + Version GroupVersioner + Encoder + ObjectTyper +} + +// Encode does not do conversion. It sets the gvk during serialization. +func (e WithVersionEncoder) Encode(obj Object, stream io.Writer) error { + gvks, _, err := e.ObjectTyper.ObjectKinds(obj) + if err != nil { + if IsNotRegisteredError(err) { + return e.Encoder.Encode(obj, stream) + } + return err + } + kind := obj.GetObjectKind() + oldGVK := kind.GroupVersionKind() + gvk := gvks[0] + if e.Version != nil { + preferredGVK, ok := e.Version.KindForGroupVersionKinds(gvks) + if ok { + gvk = preferredGVK + } + } + + // The gvk only needs to be set if not already as desired. + if gvk != oldGVK { + kind.SetGroupVersionKind(gvk) + defer kind.SetGroupVersionKind(oldGVK) + } + + return e.Encoder.Encode(obj, stream) +} + +// WithoutVersionDecoder clears the group version kind of a deserialized object. +type WithoutVersionDecoder struct { + Decoder +} + +// Decode does not do conversion. It removes the gvk during deserialization. +func (d WithoutVersionDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) { + obj, gvk, err := d.Decoder.Decode(data, defaults, into) + if obj != nil { + kind := obj.GetObjectKind() + // clearing the gvk is just a convention of a codec + kind.SetGroupVersionKind(schema.GroupVersionKind{}) + } + return obj, gvk, err +} + +type encoderWithAllocator struct { + encoder EncoderWithAllocator + memAllocator MemoryAllocator +} + +// NewEncoderWithAllocator returns a new encoder +func NewEncoderWithAllocator(e EncoderWithAllocator, a MemoryAllocator) Encoder { + return &encoderWithAllocator{ + encoder: e, + memAllocator: a, + } +} + +// Encode writes the provided object to the nested writer +func (e *encoderWithAllocator) Encode(obj Object, w io.Writer) error { + return e.encoder.EncodeWithAllocator(obj, w, e.memAllocator) +} + +// Identifier returns identifier of this encoder. +func (e *encoderWithAllocator) Identifier() Identifier { + return e.encoder.Identifier() +} + +// The legacy discovery endpoint requires that its response Encoder implement Serializer. +// https://github.com/kubernetes/kubernetes/blob/4a1340bfd58fdb3846d4342c101e0bcb574fbfb1/staging/src/k8s.io/apiserver/pkg/endpoints/discovery/util.go#L101-L107 +var _ Serializer = nondeterministicEncoderToEncoderAdapter{} + +type nondeterministicEncoderToEncoderAdapter struct { + NondeterministicEncoder + + Decoder +} + +func (e nondeterministicEncoderToEncoderAdapter) Encode(obj Object, w io.Writer) error { + return e.EncodeNondeterministic(obj, w) +} + +// UseNondeterministicEncoding returns an Encoder that encodes objects using the provided +// Serializer's EncodeNondeterministic method if it implements NondeterministicEncoder, otherwise it +// returns the provided Serializer as-is. +func UseNondeterministicEncoding(serializer Serializer) Encoder { + if nondeterministic, ok := serializer.(NondeterministicEncoder); ok { + return nondeterministicEncoderToEncoderAdapter{NondeterministicEncoder: nondeterministic, Decoder: serializer} + } + return serializer +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/interfaces.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/interfaces.go new file mode 100644 index 0000000000..8456c21d31 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/interfaces.go @@ -0,0 +1,393 @@ +/* +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 runtime + +import ( + "io" + "net/url" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + // APIVersionInternal may be used if you are registering a type that should not + // be considered stable or serialized - it is a convention only and has no + // special behavior in this package. + APIVersionInternal = "__internal" +) + +// GroupVersioner refines a set of possible conversion targets into a single option. +type GroupVersioner interface { + // KindForGroupVersionKinds returns a desired target group version kind for the given input, or returns ok false if no + // target is known. In general, if the return target is not in the input list, the caller is expected to invoke + // Scheme.New(target) and then perform a conversion between the current Go type and the destination Go type. + // Sophisticated implementations may use additional information about the input kinds to pick a destination kind. + KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (target schema.GroupVersionKind, ok bool) + // Identifier returns string representation of the object. + // Identifiers of two different encoders should be equal only if for every input + // kinds they return the same result. + Identifier() string +} + +// Identifier represents an identifier. +// Identitier of two different objects should be equal if and only if for every +// input the output they produce is exactly the same. +type Identifier string + +// Encoder writes objects to a serialized form +type Encoder interface { + // Encode writes an object to a stream. Implementations may return errors if the versions are + // incompatible, or if no conversion is defined. + Encode(obj Object, w io.Writer) error + // Identifier returns an identifier of the encoder. + // Identifiers of two different encoders should be equal if and only if for every input + // object it will be encoded to the same representation by both of them. + // + // Identifier is intended for use with CacheableObject#CacheEncode method. In order to + // correctly handle CacheableObject, Encode() method should look similar to below, where + // doEncode() is the encoding logic of implemented encoder: + // func (e *MyEncoder) Encode(obj Object, w io.Writer) error { + // if co, ok := obj.(CacheableObject); ok { + // return co.CacheEncode(e.Identifier(), e.doEncode, w) + // } + // return e.doEncode(obj, w) + // } + Identifier() Identifier +} + +// NondeterministicEncoder is implemented by Encoders that can serialize objects more efficiently in +// cases where the output does not need to be deterministic. +type NondeterministicEncoder interface { + Encoder + + // EncodeNondeterministic writes an object to the stream. Unlike the Encode method of + // Encoder, EncodeNondeterministic does not guarantee that any two invocations will write + // the same sequence of bytes to the io.Writer. Any differences will not be significant to a + // generic decoder. For example, map entries and struct fields might be encoded in any + // order. + EncodeNondeterministic(Object, io.Writer) error +} + +// MemoryAllocator is responsible for allocating memory. +// By encapsulating memory allocation into its own interface, we can reuse the memory +// across many operations in places we know it can significantly improve the performance. +type MemoryAllocator interface { + // Allocate reserves memory for n bytes. + // Note that implementations of this method are not required to zero the returned array. + // It is the caller's responsibility to clean the memory if needed. + Allocate(n uint64) []byte +} + +// EncoderWithAllocator serializes objects in a way that allows callers to manage any additional memory allocations. +type EncoderWithAllocator interface { + Encoder + // EncodeWithAllocator writes an object to a stream as Encode does. + // In addition, it allows for providing a memory allocator for efficient memory usage during object serialization + EncodeWithAllocator(obj Object, w io.Writer, memAlloc MemoryAllocator) error +} + +// Decoder attempts to load an object from data. +type Decoder interface { + // Decode attempts to deserialize the provided data using either the innate typing of the scheme or the + // default kind, group, and version provided. It returns a decoded object as well as the kind, group, and + // version from the serialized data, or an error. If into is non-nil, it will be used as the target type + // and implementations may choose to use it rather than reallocating an object. However, the object is not + // guaranteed to be populated. The returned object is not guaranteed to match into. If defaults are + // provided, they are applied to the data by default. If no defaults or partial defaults are provided, the + // type of the into may be used to guide conversion decisions. + Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error) +} + +// Serializer is the core interface for transforming objects into a serialized format and back. +// Implementations may choose to perform conversion of the object, but no assumptions should be made. +type Serializer interface { + Encoder + Decoder +} + +// Codec is a Serializer that deals with the details of versioning objects. It offers the same +// interface as Serializer, so this is a marker to consumers that care about the version of the objects +// they receive. +type Codec Serializer + +// ParameterCodec defines methods for serializing and deserializing API objects to url.Values and +// performing any necessary conversion. Unlike the normal Codec, query parameters are not self describing +// and the desired version must be specified. +type ParameterCodec interface { + // DecodeParameters takes the given url.Values in the specified group version and decodes them + // into the provided object, or returns an error. + DecodeParameters(parameters url.Values, from schema.GroupVersion, into Object) error + // EncodeParameters encodes the provided object as query parameters or returns an error. + EncodeParameters(obj Object, to schema.GroupVersion) (url.Values, error) +} + +// Framer is a factory for creating readers and writers that obey a particular framing pattern. +type Framer interface { + NewFrameReader(r io.ReadCloser) io.ReadCloser + NewFrameWriter(w io.Writer) io.Writer +} + +// SerializerInfo contains information about a specific serialization format +type SerializerInfo struct { + // MediaType is the value that represents this serializer over the wire. + MediaType string + // MediaTypeType is the first part of the MediaType ("application" in "application/json"). + MediaTypeType string + // MediaTypeSubType is the second part of the MediaType ("json" in "application/json"). + MediaTypeSubType string + // EncodesAsText indicates this serializer can be encoded to UTF-8 safely. + EncodesAsText bool + // Serializer is the individual object serializer for this media type. + Serializer Serializer + // PrettySerializer, if set, can serialize this object in a form biased towards + // readability. + PrettySerializer Serializer + // StrictSerializer, if set, deserializes this object strictly, + // erring on unknown fields. + StrictSerializer Serializer + // StreamSerializer, if set, describes the streaming serialization format + // for this media type. + StreamSerializer *StreamSerializerInfo +} + +// StreamSerializerInfo contains information about a specific stream serialization format +type StreamSerializerInfo struct { + // EncodesAsText indicates this serializer can be encoded to UTF-8 safely. + EncodesAsText bool + // Serializer is the top level object serializer for this type when streaming + Serializer + // Framer is the factory for retrieving streams that separate objects on the wire + Framer +} + +// NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers +// for multiple supported media types. This would commonly be accepted by a server component +// that performs HTTP content negotiation to accept multiple formats. +type NegotiatedSerializer interface { + // SupportedMediaTypes is the media types supported for reading and writing single objects. + SupportedMediaTypes() []SerializerInfo + + // EncoderForVersion returns an encoder that ensures objects being written to the provided + // serializer are in the provided group version. + EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder + // DecoderToVersion returns a decoder that ensures objects being read by the provided + // serializer are in the provided group version by default. + DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder +} + +// ClientNegotiator handles turning an HTTP content type into the appropriate encoder. +// Use NewClientNegotiator or NewVersionedClientNegotiator to create this interface from +// a NegotiatedSerializer. +type ClientNegotiator interface { + // Encoder returns the appropriate encoder for the provided contentType (e.g. application/json) + // and any optional mediaType parameters (e.g. pretty=1), or an error. If no serializer is found + // a NegotiateError will be returned. The current client implementations consider params to be + // optional modifiers to the contentType and will ignore unrecognized parameters. + Encoder(contentType string, params map[string]string) (Encoder, error) + // Decoder returns the appropriate decoder for the provided contentType (e.g. application/json) + // and any optional mediaType parameters (e.g. pretty=1), or an error. If no serializer is found + // a NegotiateError will be returned. The current client implementations consider params to be + // optional modifiers to the contentType and will ignore unrecognized parameters. + Decoder(contentType string, params map[string]string) (Decoder, error) + // StreamDecoder returns the appropriate stream decoder for the provided contentType (e.g. + // application/json) and any optional mediaType parameters (e.g. pretty=1), or an error. If no + // serializer is found a NegotiateError will be returned. The Serializer and Framer will always + // be returned if a Decoder is returned. The current client implementations consider params to be + // optional modifiers to the contentType and will ignore unrecognized parameters. + StreamDecoder(contentType string, params map[string]string) (Decoder, Serializer, Framer, error) +} + +// StorageSerializer is an interface used for obtaining encoders, decoders, and serializers +// that can read and write data at rest. This would commonly be used by client tools that must +// read files, or server side storage interfaces that persist restful objects. +type StorageSerializer interface { + // SupportedMediaTypes are the media types supported for reading and writing objects. + SupportedMediaTypes() []SerializerInfo + + // UniversalDeserializer returns a Serializer that can read objects in multiple supported formats + // by introspecting the data at rest. + UniversalDeserializer() Decoder + + // EncoderForVersion returns an encoder that ensures objects being written to the provided + // serializer are in the provided group version. + EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder + // DecoderForVersion returns a decoder that ensures objects being read by the provided + // serializer are in the provided group version by default. + DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder +} + +// NestedObjectEncoder is an optional interface that objects may implement to be given +// an opportunity to encode any nested Objects / RawExtensions during serialization. +type NestedObjectEncoder interface { + EncodeNestedObjects(e Encoder) error +} + +// NestedObjectDecoder is an optional interface that objects may implement to be given +// an opportunity to decode any nested Objects / RawExtensions during serialization. +// It is possible for DecodeNestedObjects to return a non-nil error but for the decoding +// to have succeeded in the case of strict decoding errors (e.g. unknown/duplicate fields). +// As such it is important for callers of DecodeNestedObjects to check to confirm whether +// an error is a runtime.StrictDecodingError before short circuiting. +// Similarly, implementations of DecodeNestedObjects should ensure that a runtime.StrictDecodingError +// is only returned when the rest of decoding has succeeded. +type NestedObjectDecoder interface { + DecodeNestedObjects(d Decoder) error +} + +/////////////////////////////////////////////////////////////////////////////// +// Non-codec interfaces + +type ObjectDefaulter interface { + // Default takes an object (must be a pointer) and applies any default values. + // Defaulters may not error. + Default(in Object) +} + +type ObjectVersioner interface { + ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error) + PrioritizedVersionsForGroup(group string) []schema.GroupVersion +} + +// ObjectConvertor converts an object to a different version. +type ObjectConvertor interface { + // Convert attempts to convert one object into another, or returns an error. This + // method does not mutate the in object, but the in and out object might share data structures, + // i.e. the out object cannot be mutated without mutating the in object as well. + // The context argument will be passed to all nested conversions. + Convert(in, out, context interface{}) error + // ConvertToVersion takes the provided object and converts it the provided version. This + // method does not mutate the in object, but the in and out object might share data structures, + // i.e. the out object cannot be mutated without mutating the in object as well. + // This method is similar to Convert() but handles specific details of choosing the correct + // output version. + ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error) + ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) +} + +// ObjectTyper contains methods for extracting the APIVersion and Kind +// of objects. +type ObjectTyper interface { + // ObjectKinds returns the all possible group,version,kind of the provided object, true if + // the object is unversioned, or an error if the object is not recognized + // (IsNotRegisteredError will return true). + ObjectKinds(Object) ([]schema.GroupVersionKind, bool, error) + // Recognizes returns true if the scheme is able to handle the provided version and kind, + // or more precisely that the provided version is a possible conversion or decoding + // target. + Recognizes(gvk schema.GroupVersionKind) bool +} + +// ObjectCreater contains methods for instantiating an object by kind and version. +type ObjectCreater interface { + New(kind schema.GroupVersionKind) (out Object, err error) +} + +// EquivalentResourceMapper provides information about resources that address the same underlying data as a specified resource +type EquivalentResourceMapper interface { + // EquivalentResourcesFor returns a list of resources that address the same underlying data as resource. + // If subresource is specified, only equivalent resources which also have the same subresource are included. + // The specified resource can be included in the returned list. + EquivalentResourcesFor(resource schema.GroupVersionResource, subresource string) []schema.GroupVersionResource + // KindFor returns the kind expected by the specified resource[/subresource]. + // A zero value is returned if the kind is unknown. + KindFor(resource schema.GroupVersionResource, subresource string) schema.GroupVersionKind +} + +// EquivalentResourceRegistry provides an EquivalentResourceMapper interface, +// and allows registering known resource[/subresource] -> kind +type EquivalentResourceRegistry interface { + EquivalentResourceMapper + // RegisterKindFor registers the existence of the specified resource[/subresource] along with its expected kind. + RegisterKindFor(resource schema.GroupVersionResource, subresource string, kind schema.GroupVersionKind) +} + +// ResourceVersioner provides methods for setting and retrieving +// the resource version from an API object. +type ResourceVersioner interface { + SetResourceVersion(obj Object, version string) error + ResourceVersion(obj Object) (string, error) +} + +// Namer provides methods for retrieving name and namespace of an API object. +type Namer interface { + // Name returns the name of a given object. + Name(obj Object) (string, error) + // Namespace returns the name of a given object. + Namespace(obj Object) (string, error) +} + +// Object interface must be supported by all API types registered with Scheme. Since objects in a scheme are +// expected to be serialized to the wire, the interface an Object must provide to the Scheme allows +// serializers to set the kind, version, and group the object is represented as. An Object may choose +// to return a no-op ObjectKindAccessor in cases where it is not expected to be serialized. +type Object interface { + GetObjectKind() schema.ObjectKind + DeepCopyObject() Object +} + +// CacheableObject allows an object to cache its different serializations +// to avoid performing the same serialization multiple times. +type CacheableObject interface { + // CacheEncode writes an object to a stream. The function will + // be used in case of cache miss. The function takes ownership + // of the object. + // If CacheableObject is a wrapper, then deep-copy of the wrapped object + // should be passed to function. + // CacheEncode assumes that for two different calls with the same , + // function will also be the same. + CacheEncode(id Identifier, encode func(Object, io.Writer) error, w io.Writer) error + // GetObject returns a deep-copy of an object to be encoded - the caller of + // GetObject() is the owner of returned object. The reason for making a copy + // is to avoid bugs, where caller modifies the object and forgets to copy it, + // thus modifying the object for everyone. + // The object returned by GetObject should be the same as the one that is supposed + // to be passed to function in CacheEncode method. + // If CacheableObject is a wrapper, the copy of wrapped object should be returned. + GetObject() Object +} + +// Unstructured objects store values as map[string]interface{}, with only values that can be serialized +// to JSON allowed. +type Unstructured interface { + Object + // NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data. + // This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info. + NewEmptyInstance() Unstructured + // UnstructuredContent returns a non-nil map with this object's contents. Values may be + // []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to + // and from JSON. SetUnstructuredContent should be used to mutate the contents. + UnstructuredContent() map[string]interface{} + // SetUnstructuredContent updates the object content to match the provided map. + SetUnstructuredContent(map[string]interface{}) + // IsList returns true if this type is a list or matches the list convention - has an array called "items". + IsList() bool + // EachListItem should pass a single item out of the list as an Object to the provided function. Any + // error should terminate the iteration. If IsList() returns false, this method should return an error + // instead of calling the provided function. + EachListItem(func(Object) error) error + // EachListItemWithAlloc works like EachListItem, but avoids retaining references to a slice of items. + // It does this by making a shallow copy of non-pointer items before passing them to fn. + // + // If the items passed to fn are not retained, or are retained for the same duration, use EachListItem instead for memory efficiency. + EachListItemWithAlloc(func(Object) error) error +} + +// ApplyConfiguration is an interface that root apply configuration types implement. +type ApplyConfiguration interface { + // IsApplyConfiguration is implemented if the object is the root of an apply configuration. + IsApplyConfiguration() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/local_scheme_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/local_scheme_test.go new file mode 100644 index 0000000000..9b90a71800 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/local_scheme_test.go @@ -0,0 +1,150 @@ +/* +Copyright 2018 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 runtime + +import ( + "testing" + + "reflect" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestPreferredVersionsAllGroups(t *testing.T) { + tests := []struct { + name string + versionPriority map[string][]string + observedVersions []schema.GroupVersion + expectedPrioritized map[string][]schema.GroupVersion + expectedPreferred map[schema.GroupVersion]bool + }{ + { + name: "observedOnly", + observedVersions: []schema.GroupVersion{ + {Group: "", Version: "v3"}, + {Group: "foo", Version: "v1"}, + {Group: "foo", Version: "v2"}, + {Group: "", Version: "v1"}, + }, + expectedPrioritized: map[string][]schema.GroupVersion{ + "": { + {Group: "", Version: "v3"}, + {Group: "", Version: "v1"}, + }, + "foo": { + {Group: "foo", Version: "v1"}, + {Group: "foo", Version: "v2"}, + }, + }, + expectedPreferred: map[schema.GroupVersion]bool{ + {Group: "", Version: "v3"}: true, + {Group: "foo", Version: "v1"}: true, + }, + }, + { + name: "specifiedOnly", + versionPriority: map[string][]string{ + "": {"v3", "v1"}, + "foo": {"v1", "v2"}, + }, + expectedPrioritized: map[string][]schema.GroupVersion{ + "": { + {Group: "", Version: "v3"}, + {Group: "", Version: "v1"}, + }, + "foo": { + {Group: "foo", Version: "v1"}, + {Group: "foo", Version: "v2"}, + }, + }, + expectedPreferred: map[schema.GroupVersion]bool{ + {Group: "", Version: "v3"}: true, + {Group: "foo", Version: "v1"}: true, + }, + }, + { + name: "both", + versionPriority: map[string][]string{ + "": {"v3", "v1"}, + "foo": {"v1", "v2"}, + }, + observedVersions: []schema.GroupVersion{ + {Group: "", Version: "v1"}, + {Group: "", Version: "v3"}, + {Group: "", Version: "v4"}, + {Group: "", Version: "v5"}, + {Group: "bar", Version: "v1"}, + {Group: "bar", Version: "v2"}, + }, + expectedPrioritized: map[string][]schema.GroupVersion{ + "": { + {Group: "", Version: "v3"}, + {Group: "", Version: "v1"}, + {Group: "", Version: "v4"}, + {Group: "", Version: "v5"}, + }, + "foo": { + {Group: "foo", Version: "v1"}, + {Group: "foo", Version: "v2"}, + }, + "bar": { + {Group: "bar", Version: "v1"}, + {Group: "bar", Version: "v2"}, + }, + }, + expectedPreferred: map[schema.GroupVersion]bool{ + {Group: "", Version: "v3"}: true, + {Group: "foo", Version: "v1"}: true, + {Group: "bar", Version: "v1"}: true, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + scheme := NewScheme() + scheme.versionPriority = test.versionPriority + scheme.observedVersions = test.observedVersions + + for group, expected := range test.expectedPrioritized { + actual := scheme.PrioritizedVersionsForGroup(group) + if !reflect.DeepEqual(expected, actual) { + t.Error(cmp.Diff(expected, actual)) + } + } + + prioritizedAll := scheme.PrioritizedVersionsAllGroups() + actualPrioritizedAll := map[string][]schema.GroupVersion{} + for _, actual := range prioritizedAll { + actualPrioritizedAll[actual.Group] = append(actualPrioritizedAll[actual.Group], actual) + } + if !reflect.DeepEqual(test.expectedPrioritized, actualPrioritizedAll) { + t.Error(cmp.Diff(test.expectedPrioritized, actualPrioritizedAll)) + } + + preferredAll := scheme.PreferredVersionAllGroups() + actualPreferredAll := map[schema.GroupVersion]bool{} + for _, actual := range preferredAll { + actualPreferredAll[actual] = true + } + if !reflect.DeepEqual(test.expectedPreferred, actualPreferredAll) { + t.Error(cmp.Diff(test.expectedPreferred, actualPreferredAll)) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/mapper.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/mapper.go new file mode 100644 index 0000000000..3ff84611ab --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/mapper.go @@ -0,0 +1,98 @@ +/* +Copyright 2019 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 runtime + +import ( + "sync" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type equivalentResourceRegistry struct { + // keyFunc computes a key for the specified resource (this allows honoring colocated resources across API groups). + // if null, or if "" is returned, resource.String() is used as the key + keyFunc func(resource schema.GroupResource) string + // resources maps key -> subresource -> equivalent resources (subresource is not included in the returned resources). + // main resources are stored with subresource="". + resources map[string]map[string][]schema.GroupVersionResource + // kinds maps resource -> subresource -> kind + kinds map[schema.GroupVersionResource]map[string]schema.GroupVersionKind + // keys caches the computed key for each GroupResource + keys map[schema.GroupResource]string + + mutex sync.RWMutex +} + +var _ EquivalentResourceMapper = (*equivalentResourceRegistry)(nil) +var _ EquivalentResourceRegistry = (*equivalentResourceRegistry)(nil) + +// NewEquivalentResourceRegistry creates a resource registry that considers all versions of a GroupResource to be equivalent. +func NewEquivalentResourceRegistry() EquivalentResourceRegistry { + return &equivalentResourceRegistry{} +} + +// NewEquivalentResourceRegistryWithIdentity creates a resource mapper with a custom identity function. +// If "" is returned by the function, GroupResource#String is used as the identity. +// GroupResources with the same identity string are considered equivalent. +func NewEquivalentResourceRegistryWithIdentity(keyFunc func(schema.GroupResource) string) EquivalentResourceRegistry { + return &equivalentResourceRegistry{keyFunc: keyFunc} +} + +func (r *equivalentResourceRegistry) EquivalentResourcesFor(resource schema.GroupVersionResource, subresource string) []schema.GroupVersionResource { + r.mutex.RLock() + defer r.mutex.RUnlock() + return r.resources[r.keys[resource.GroupResource()]][subresource] +} +func (r *equivalentResourceRegistry) KindFor(resource schema.GroupVersionResource, subresource string) schema.GroupVersionKind { + r.mutex.RLock() + defer r.mutex.RUnlock() + return r.kinds[resource][subresource] +} +func (r *equivalentResourceRegistry) RegisterKindFor(resource schema.GroupVersionResource, subresource string, kind schema.GroupVersionKind) { + r.mutex.Lock() + defer r.mutex.Unlock() + if r.kinds == nil { + r.kinds = map[schema.GroupVersionResource]map[string]schema.GroupVersionKind{} + } + if r.kinds[resource] == nil { + r.kinds[resource] = map[string]schema.GroupVersionKind{} + } + r.kinds[resource][subresource] = kind + + // get the shared key of the parent resource + key := "" + gr := resource.GroupResource() + if r.keyFunc != nil { + key = r.keyFunc(gr) + } + if key == "" { + key = gr.String() + } + + if r.keys == nil { + r.keys = map[schema.GroupResource]string{} + } + r.keys[gr] = key + + if r.resources == nil { + r.resources = map[string]map[string][]schema.GroupVersionResource{} + } + if r.resources[key] == nil { + r.resources[key] = map[string][]schema.GroupVersionResource{} + } + r.resources[key][subresource] = append(r.resources[key][subresource], resource) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/mapper_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/mapper_test.go new file mode 100644 index 0000000000..d8423e0e31 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/mapper_test.go @@ -0,0 +1,136 @@ +/* +Copyright 2019 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 runtime + +import ( + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestResourceMapper(t *testing.T) { + gvr := func(g, v, r string) schema.GroupVersionResource { + return schema.GroupVersionResource{Group: g, Version: v, Resource: r} + } + + gvk := func(g, v, k string) schema.GroupVersionKind { + return schema.GroupVersionKind{Group: g, Version: v, Kind: k} + } + + kindsToRegister := []struct { + gvr schema.GroupVersionResource + subresource string + gvk schema.GroupVersionKind + }{ + // pods + {gvr("", "v1", "pods"), "", gvk("", "v1", "Pod")}, + // pods/status + {gvr("", "v1", "pods"), "status", gvk("", "v1", "Pod")}, + // deployments + {gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")}, + {gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")}, + {gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")}, + {gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")}, + // deployments/scale (omitted for apps/v1alpha1) + {gvr("apps", "v1", "deployments"), "scale", gvk("", "", "Scale")}, + {gvr("apps", "v1beta1", "deployments"), "scale", gvk("", "", "Scale")}, + {gvr("extensions", "v1beta1", "deployments"), "scale", gvk("", "", "Scale")}, + // deployments/status (omitted for apps/v1alpha1) + {gvr("apps", "v1", "deployments"), "status", gvk("apps", "v1", "Deployment")}, + {gvr("apps", "v1beta1", "deployments"), "status", gvk("apps", "v1beta1", "Deployment")}, + {gvr("extensions", "v1beta1", "deployments"), "status", gvk("extensions", "v1beta1", "Deployment")}, + } + + testcases := []struct { + Name string + IdentityFunc func(schema.GroupResource) string + ResourcesForV1Deployment []schema.GroupVersionResource + ResourcesForV1DeploymentScale []schema.GroupVersionResource + ResourcesForV1DeploymentStatus []schema.GroupVersionResource + }{ + { + Name: "no identityfunc", + ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments")}, + ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, + ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, + }, + { + Name: "empty identityfunc", + IdentityFunc: func(schema.GroupResource) string { return "" }, + // same group + ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments")}, + ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, + ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, + }, + { + Name: "common identityfunc", + IdentityFunc: func(schema.GroupResource) string { return "x" }, + // all resources are seen as equivalent + ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("", "v1", "pods"), gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + // all resources with scale are seen as equivalent + ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + // all resources with status are seen as equivalent + ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("", "v1", "pods"), gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + }, + { + Name: "colocated deployments", + IdentityFunc: func(resource schema.GroupResource) string { + if resource.Resource == "deployments" { + return "deployments" + } + return "" + }, + // all deployments are seen as equivalent + ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + // all deployments with scale are seen as equivalent + ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + // all deployments with status are seen as equivalent + ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + mapper := NewEquivalentResourceRegistryWithIdentity(tc.IdentityFunc) + + // register + for _, data := range kindsToRegister { + mapper.RegisterKindFor(data.gvr, data.subresource, data.gvk) + } + // verify + for _, data := range kindsToRegister { + if kind := mapper.KindFor(data.gvr, data.subresource); kind != data.gvk { + t.Errorf("KindFor(%#v, %v) returned %#v, expected %#v", data.gvr, data.subresource, kind, data.gvk) + } + } + + // Verify equivalents to primary resource + if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), ""); !reflect.DeepEqual(resources, tc.ResourcesForV1Deployment) { + t.Errorf("diff:\n%s", cmp.Diff(tc.ResourcesForV1Deployment, resources)) + } + // Verify equivalents to subresources + if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), "scale"); !reflect.DeepEqual(resources, tc.ResourcesForV1DeploymentScale) { + t.Errorf("diff:\n%s", cmp.Diff(tc.ResourcesForV1DeploymentScale, resources)) + } + if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), "status"); !reflect.DeepEqual(resources, tc.ResourcesForV1DeploymentStatus) { + t.Errorf("diff:\n%s", cmp.Diff(tc.ResourcesForV1DeploymentStatus, resources)) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/negotiate.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/negotiate.go new file mode 100644 index 0000000000..3ab119b0a8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/negotiate.go @@ -0,0 +1,113 @@ +/* +Copyright 2019 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 runtime + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// NegotiateError is returned when a ClientNegotiator is unable to locate +// a serializer for the requested operation. +type NegotiateError struct { + ContentType string + Stream bool +} + +func (e NegotiateError) Error() string { + if e.Stream { + return fmt.Sprintf("no stream serializers registered for %s", e.ContentType) + } + return fmt.Sprintf("no serializers registered for %s", e.ContentType) +} + +type clientNegotiator struct { + serializer NegotiatedSerializer + encode, decode GroupVersioner +} + +func (n *clientNegotiator) Encoder(contentType string, params map[string]string) (Encoder, error) { + // TODO: `pretty=1` is handled in NegotiateOutputMediaType, consider moving it to this method + // if client negotiators truly need to use it + mediaTypes := n.serializer.SupportedMediaTypes() + info, ok := SerializerInfoForMediaType(mediaTypes, contentType) + if !ok { + if len(contentType) != 0 || len(mediaTypes) == 0 { + return nil, NegotiateError{ContentType: contentType} + } + info = mediaTypes[0] + } + return n.serializer.EncoderForVersion(info.Serializer, n.encode), nil +} + +func (n *clientNegotiator) Decoder(contentType string, params map[string]string) (Decoder, error) { + mediaTypes := n.serializer.SupportedMediaTypes() + info, ok := SerializerInfoForMediaType(mediaTypes, contentType) + if !ok { + if len(contentType) != 0 || len(mediaTypes) == 0 { + return nil, NegotiateError{ContentType: contentType} + } + info = mediaTypes[0] + } + return n.serializer.DecoderToVersion(info.Serializer, n.decode), nil +} + +func (n *clientNegotiator) StreamDecoder(contentType string, params map[string]string) (Decoder, Serializer, Framer, error) { + mediaTypes := n.serializer.SupportedMediaTypes() + info, ok := SerializerInfoForMediaType(mediaTypes, contentType) + if !ok { + if len(contentType) != 0 || len(mediaTypes) == 0 { + return nil, nil, nil, NegotiateError{ContentType: contentType, Stream: true} + } + info = mediaTypes[0] + } + if info.StreamSerializer == nil { + return nil, nil, nil, NegotiateError{ContentType: info.MediaType, Stream: true} + } + return n.serializer.DecoderToVersion(info.Serializer, n.decode), info.StreamSerializer.Serializer, info.StreamSerializer.Framer, nil +} + +// NewClientNegotiator will attempt to retrieve the appropriate encoder, decoder, or +// stream decoder for a given content type. Does not perform any conversion, but will +// encode the object to the desired group, version, and kind. Use when creating a client. +func NewClientNegotiator(serializer NegotiatedSerializer, gv schema.GroupVersion) ClientNegotiator { + return &clientNegotiator{ + serializer: serializer, + encode: gv, + } +} + +type simpleNegotiatedSerializer struct { + info SerializerInfo +} + +func NewSimpleNegotiatedSerializer(info SerializerInfo) NegotiatedSerializer { + return &simpleNegotiatedSerializer{info: info} +} + +func (n *simpleNegotiatedSerializer) SupportedMediaTypes() []SerializerInfo { + return []SerializerInfo{n.info} +} + +func (n *simpleNegotiatedSerializer) EncoderForVersion(e Encoder, _ GroupVersioner) Encoder { + return e +} + +func (n *simpleNegotiatedSerializer) DecoderToVersion(d Decoder, _gv GroupVersioner) Decoder { + return d +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/register.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/register.go new file mode 100644 index 0000000000..1cd2e4c387 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/register.go @@ -0,0 +1,31 @@ +/* +Copyright 2015 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 runtime + +import "k8s.io/apimachinery/pkg/runtime/schema" + +// SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta +func (obj *TypeMeta) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} + +// GroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta +func (obj *TypeMeta) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} + +func (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj } diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/generated.pb.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/generated.pb.go new file mode 100644 index 0000000000..ed57e08afe --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/generated.pb.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/apimachinery/pkg/runtime/schema/generated.proto + +package schema diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/generated.proto b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/generated.proto new file mode 100644 index 0000000000..01a9c01e5c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/generated.proto @@ -0,0 +1,26 @@ +/* +Copyright 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.apimachinery.pkg.runtime.schema; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/apimachinery/pkg/runtime/schema"; + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/group_version.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/group_version.go new file mode 100644 index 0000000000..d1c37c9429 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/group_version.go @@ -0,0 +1,305 @@ +/* +Copyright 2015 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 schema + +import ( + "fmt" + "strings" +) + +// ParseResourceArg takes the common style of string which may be either `resource.group.com` or `resource.version.group.com` +// and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended +// but with a knowledge of all GroupVersions, calling code can take a very good guess. If there are only two segments, then +// `*GroupVersionResource` is nil. +// `resource.group.com` -> `group=com, version=group, resource=resource` and `group=group.com, resource=resource` +func ParseResourceArg(arg string) (*GroupVersionResource, GroupResource) { + var gvr *GroupVersionResource + if strings.Count(arg, ".") >= 2 { + s := strings.SplitN(arg, ".", 3) + gvr = &GroupVersionResource{Group: s[2], Version: s[1], Resource: s[0]} + } + + return gvr, ParseGroupResource(arg) +} + +// ParseKindArg takes the common style of string which may be either `Kind.group.com` or `Kind.version.group.com` +// and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended +// but with a knowledge of all GroupKinds, calling code can take a very good guess. If there are only two segments, then +// `*GroupVersionKind` is nil. +// `Kind.group.com` -> `group=com, version=group, kind=Kind` and `group=group.com, kind=Kind` +func ParseKindArg(arg string) (*GroupVersionKind, GroupKind) { + var gvk *GroupVersionKind + if strings.Count(arg, ".") >= 2 { + s := strings.SplitN(arg, ".", 3) + gvk = &GroupVersionKind{Group: s[2], Version: s[1], Kind: s[0]} + } + + return gvk, ParseGroupKind(arg) +} + +// GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying +// concepts during lookup stages without having partially valid types +type GroupResource struct { + Group string + Resource string +} + +func (gr GroupResource) WithVersion(version string) GroupVersionResource { + return GroupVersionResource{Group: gr.Group, Version: version, Resource: gr.Resource} +} + +func (gr GroupResource) Empty() bool { + return len(gr.Group) == 0 && len(gr.Resource) == 0 +} + +func (gr GroupResource) String() string { + if len(gr.Group) == 0 { + return gr.Resource + } + return gr.Resource + "." + gr.Group +} + +func ParseGroupKind(gk string) GroupKind { + i := strings.Index(gk, ".") + if i == -1 { + return GroupKind{Kind: gk} + } + + return GroupKind{Group: gk[i+1:], Kind: gk[:i]} +} + +// ParseGroupResource turns "resource.group" string into a GroupResource struct. Empty strings are allowed +// for each field. +func ParseGroupResource(gr string) GroupResource { + if i := strings.Index(gr, "."); i >= 0 { + return GroupResource{Group: gr[i+1:], Resource: gr[:i]} + } + return GroupResource{Resource: gr} +} + +// GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion +// to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling +type GroupVersionResource struct { + Group string + Version string + Resource string +} + +func (gvr GroupVersionResource) Empty() bool { + return len(gvr.Group) == 0 && len(gvr.Version) == 0 && len(gvr.Resource) == 0 +} + +func (gvr GroupVersionResource) GroupResource() GroupResource { + return GroupResource{Group: gvr.Group, Resource: gvr.Resource} +} + +func (gvr GroupVersionResource) GroupVersion() GroupVersion { + return GroupVersion{Group: gvr.Group, Version: gvr.Version} +} + +func (gvr GroupVersionResource) String() string { + return strings.Join([]string{gvr.Group, "/", gvr.Version, ", Resource=", gvr.Resource}, "") +} + +// GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying +// concepts during lookup stages without having partially valid types +type GroupKind struct { + Group string + Kind string +} + +func (gk GroupKind) Empty() bool { + return len(gk.Group) == 0 && len(gk.Kind) == 0 +} + +func (gk GroupKind) WithVersion(version string) GroupVersionKind { + return GroupVersionKind{Group: gk.Group, Version: version, Kind: gk.Kind} +} + +func (gk GroupKind) String() string { + if len(gk.Group) == 0 { + return gk.Kind + } + return gk.Kind + "." + gk.Group +} + +// GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion +// to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling +type GroupVersionKind struct { + Group string + Version string + Kind string +} + +// Empty returns true if group, version, and kind are empty +func (gvk GroupVersionKind) Empty() bool { + return len(gvk.Group) == 0 && len(gvk.Version) == 0 && len(gvk.Kind) == 0 +} + +func (gvk GroupVersionKind) GroupKind() GroupKind { + return GroupKind{Group: gvk.Group, Kind: gvk.Kind} +} + +func (gvk GroupVersionKind) GroupVersion() GroupVersion { + return GroupVersion{Group: gvk.Group, Version: gvk.Version} +} + +func (gvk GroupVersionKind) String() string { + return gvk.Group + "/" + gvk.Version + ", Kind=" + gvk.Kind +} + +// GroupVersion contains the "group" and the "version", which uniquely identifies the API. +type GroupVersion struct { + Group string + Version string +} + +// Empty returns true if group and version are empty +func (gv GroupVersion) Empty() bool { + return len(gv.Group) == 0 && len(gv.Version) == 0 +} + +// String puts "group" and "version" into a single "group/version" string. For the legacy v1 +// it returns "v1". +func (gv GroupVersion) String() string { + if len(gv.Group) > 0 { + return gv.Group + "/" + gv.Version + } + return gv.Version +} + +// Identifier implements runtime.GroupVersioner interface. +func (gv GroupVersion) Identifier() string { + return gv.String() +} + +// KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false +// if none of the options match the group. It prefers a match to group and version over just group. +// TODO: Move GroupVersion to a package under pkg/runtime, since it's used by scheme. +// TODO: Introduce an adapter type between GroupVersion and runtime.GroupVersioner, and use LegacyCodec(GroupVersion) +// in fewer places. +func (gv GroupVersion) KindForGroupVersionKinds(kinds []GroupVersionKind) (target GroupVersionKind, ok bool) { + for _, gvk := range kinds { + if gvk.Group == gv.Group && gvk.Version == gv.Version { + return gvk, true + } + } + for _, gvk := range kinds { + if gvk.Group == gv.Group { + return gv.WithKind(gvk.Kind), true + } + } + return GroupVersionKind{}, false +} + +// ParseGroupVersion turns "group/version" string into a GroupVersion struct. It reports error +// if it cannot parse the string. +func ParseGroupVersion(gv string) (GroupVersion, error) { + // this can be the internal version for the legacy kube types + // TODO once we've cleared the last uses as strings, this special case should be removed. + if (len(gv) == 0) || (gv == "/") { + return GroupVersion{}, nil + } + + switch strings.Count(gv, "/") { + case 0: + return GroupVersion{"", gv}, nil + case 1: + i := strings.Index(gv, "/") + return GroupVersion{gv[:i], gv[i+1:]}, nil + default: + return GroupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv) + } +} + +// WithKind creates a GroupVersionKind based on the method receiver's GroupVersion and the passed Kind. +func (gv GroupVersion) WithKind(kind string) GroupVersionKind { + return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind} +} + +// WithResource creates a GroupVersionResource based on the method receiver's GroupVersion and the passed Resource. +func (gv GroupVersion) WithResource(resource string) GroupVersionResource { + return GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: resource} +} + +// GroupVersions can be used to represent a set of desired group versions. +// TODO: Move GroupVersions to a package under pkg/runtime, since it's used by scheme. +// TODO: Introduce an adapter type between GroupVersions and runtime.GroupVersioner, and use LegacyCodec(GroupVersion) +// in fewer places. +type GroupVersions []GroupVersion + +// Identifier implements runtime.GroupVersioner interface. +func (gvs GroupVersions) Identifier() string { + groupVersions := make([]string, 0, len(gvs)) + for i := range gvs { + groupVersions = append(groupVersions, gvs[i].String()) + } + return fmt.Sprintf("[%s]", strings.Join(groupVersions, ",")) +} + +// KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false +// if none of the options match the group. +func (gvs GroupVersions) KindForGroupVersionKinds(kinds []GroupVersionKind) (GroupVersionKind, bool) { + var targets []GroupVersionKind + for _, gv := range gvs { + target, ok := gv.KindForGroupVersionKinds(kinds) + if !ok { + continue + } + targets = append(targets, target) + } + if len(targets) == 1 { + return targets[0], true + } + if len(targets) > 1 { + return bestMatch(kinds, targets), true + } + return GroupVersionKind{}, false +} + +// bestMatch tries to pick best matching GroupVersionKind and falls back to the first +// found if no exact match exists. +func bestMatch(kinds []GroupVersionKind, targets []GroupVersionKind) GroupVersionKind { + for _, gvk := range targets { + for _, k := range kinds { + if k == gvk { + return k + } + } + } + return targets[0] +} + +// ToAPIVersionAndKind is a convenience method for satisfying runtime.Object on types that +// do not use TypeMeta. +func (gvk GroupVersionKind) ToAPIVersionAndKind() (string, string) { + if gvk.Empty() { + return "", "" + } + return gvk.GroupVersion().String(), gvk.Kind +} + +// FromAPIVersionAndKind returns a GVK representing the provided fields for types that +// do not use TypeMeta. This method exists to support test types and legacy serializations +// that have a distinct group and kind. +// TODO: further reduce usage of this method. +func FromAPIVersionAndKind(apiVersion, kind string) GroupVersionKind { + if gv, err := ParseGroupVersion(apiVersion); err == nil { + return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind} + } + return GroupVersionKind{Kind: kind} +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/group_version_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/group_version_test.go new file mode 100644 index 0000000000..ff28593c5c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/group_version_test.go @@ -0,0 +1,240 @@ +/* +Copyright 2015 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 schema + +import ( + "testing" +) + +func TestGroupVersionParse(t *testing.T) { + tests := []struct { + input string + out GroupVersion + err func(error) bool + }{ + {input: "v1", out: GroupVersion{Version: "v1"}}, + {input: "v2", out: GroupVersion{Version: "v2"}}, + {input: "/v1", out: GroupVersion{Version: "v1"}}, + {input: "v1/", out: GroupVersion{Group: "v1"}}, + {input: "/v1/", err: func(err error) bool { return err.Error() == "unexpected GroupVersion string: /v1/" }}, + {input: "v1/a", out: GroupVersion{Group: "v1", Version: "a"}}, + } + for i, test := range tests { + out, err := ParseGroupVersion(test.input) + if test.err == nil && err != nil || err == nil && test.err != nil { + t.Errorf("%d: unexpected error: %v", i, err) + continue + } + if test.err != nil && !test.err(err) { + t.Errorf("%d: unexpected error: %v", i, err) + continue + } + if out != test.out { + t.Errorf("%d: unexpected output: %#v", i, out) + } + } +} + +func TestGroupResourceParse(t *testing.T) { + tests := []struct { + input string + out GroupResource + }{ + {input: "v1", out: GroupResource{Resource: "v1"}}, + {input: ".v1", out: GroupResource{Group: "v1"}}, + {input: "v1.", out: GroupResource{Resource: "v1"}}, + {input: "v1.a", out: GroupResource{Group: "a", Resource: "v1"}}, + {input: "b.v1.a", out: GroupResource{Group: "v1.a", Resource: "b"}}, + } + for i, test := range tests { + out := ParseGroupResource(test.input) + if out != test.out { + t.Errorf("%d: unexpected output: %#v", i, out) + } + } +} + +func TestParseResourceArg(t *testing.T) { + tests := []struct { + input string + gvr *GroupVersionResource + gr GroupResource + }{ + {input: "v1", gr: GroupResource{Resource: "v1"}}, + {input: ".v1", gr: GroupResource{Group: "v1"}}, + {input: "v1.", gr: GroupResource{Resource: "v1"}}, + {input: "v1.a", gr: GroupResource{Group: "a", Resource: "v1"}}, + {input: "b.v1.a", gvr: &GroupVersionResource{Group: "a", Version: "v1", Resource: "b"}, gr: GroupResource{Group: "v1.a", Resource: "b"}}, + } + for i, test := range tests { + gvr, gr := ParseResourceArg(test.input) + if (gvr != nil && test.gvr == nil) || (gvr == nil && test.gvr != nil) || (test.gvr != nil && *gvr != *test.gvr) { + t.Errorf("%d: unexpected output: %#v", i, gvr) + } + if gr != test.gr { + t.Errorf("%d: unexpected output: %#v", i, gr) + } + } +} + +func TestKindForGroupVersionKinds(t *testing.T) { + gvks := GroupVersions{ + GroupVersion{Group: "batch", Version: "v1"}, + GroupVersion{Group: "batch", Version: "v2alpha1"}, + GroupVersion{Group: "policy", Version: "v1beta1"}, + } + cases := []struct { + input []GroupVersionKind + target GroupVersionKind + ok bool + }{ + { + input: []GroupVersionKind{{Group: "batch", Version: "v2alpha1", Kind: "ScheduledJob"}}, + target: GroupVersionKind{Group: "batch", Version: "v2alpha1", Kind: "ScheduledJob"}, + ok: true, + }, + { + input: []GroupVersionKind{{Group: "batch", Version: "v3alpha1", Kind: "CronJob"}}, + target: GroupVersionKind{Group: "batch", Version: "v1", Kind: "CronJob"}, + ok: true, + }, + { + input: []GroupVersionKind{{Group: "policy", Version: "v1beta1", Kind: "PodDisruptionBudget"}}, + target: GroupVersionKind{Group: "policy", Version: "v1beta1", Kind: "PodDisruptionBudget"}, + ok: true, + }, + { + input: []GroupVersionKind{{Group: "apps", Version: "v1alpha1", Kind: "StatefulSet"}}, + target: GroupVersionKind{}, + ok: false, + }, + } + + for i, c := range cases { + target, ok := gvks.KindForGroupVersionKinds(c.input) + if c.target != target { + t.Errorf("%d: unexpected target: %v, expected %v", i, target, c.target) + } + if c.ok != ok { + t.Errorf("%d: unexpected ok: %v, expected %v", i, ok, c.ok) + } + } +} + +func TestParseKindArg(t *testing.T) { + tests := []struct { + input string + gvk *GroupVersionKind + gk GroupKind + }{ + {input: "Pod", gk: GroupKind{Kind: "Pod"}}, + {input: ".apps", gk: GroupKind{Group: "apps"}}, + {input: "Pod.", gk: GroupKind{Kind: "Pod"}}, + {input: "StatefulSet.apps", gk: GroupKind{Group: "apps", Kind: "StatefulSet"}}, + {input: "StatefulSet.v1.apps", gvk: &GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}, gk: GroupKind{Group: "v1.apps", Kind: "StatefulSet"}}, + } + for i, test := range tests { + t.Run(test.input, func(t *testing.T) { + gvk, gk := ParseKindArg(test.input) + if (gvk != nil && test.gvk == nil) || (gvk == nil && test.gvk != nil) || (test.gvk != nil && *gvk != *test.gvk) { + t.Errorf("%d: expected output: %#v, got: %#v", i, test.gvk, gvk) + } + if gk != test.gk { + t.Errorf("%d: expected output: %#v, got: %#v", i, test.gk, gk) + } + }) + } +} + +func TestParseGroupKind(t *testing.T) { + tests := []struct { + input string + out GroupKind + }{ + {input: "Pod", out: GroupKind{Kind: "Pod"}}, + {input: ".StatefulSet", out: GroupKind{Group: "StatefulSet"}}, + {input: "StatefulSet.apps", out: GroupKind{Group: "apps", Kind: "StatefulSet"}}, + } + for i, test := range tests { + t.Run(test.input, func(t *testing.T) { + out := ParseGroupKind(test.input) + if out != test.out { + t.Errorf("%d: expected output: %#v, got: %#v", i, test.out, out) + } + }) + } +} + +func TestToAPIVersionAndKind(t *testing.T) { + tests := []struct { + desc string + input GroupVersionKind + GroupVersion string + Kind string + }{ + { + desc: "gvk object is not empty", + input: GroupVersionKind{Version: "V1", Kind: "pod"}, + GroupVersion: "V1", + Kind: "pod", + }, + { + desc: "gvk object is empty", + input: GroupVersionKind{}, + GroupVersion: "", + Kind: "", + }, + } + for i, test := range tests { + version, kind := test.input.ToAPIVersionAndKind() + if version != test.GroupVersion { + t.Errorf("%d: expected version: %#v, got: %#v", i, test.GroupVersion, version) + } + if kind != test.Kind { + t.Errorf("%d: expected kind: %#v, got: %#v", i, test.Kind, kind) + } + } +} + +func TestBestMatch(t *testing.T) { + tests := []struct { + desc string + kinds []GroupVersionKind + targets []GroupVersionKind + output GroupVersionKind + }{ + { + desc: "targets and kinds have match items", + kinds: []GroupVersionKind{{Version: "V1", Kind: "pod"}, {Version: "V2", Kind: "pod"}}, + targets: []GroupVersionKind{{Version: "V1", Kind: "pod"}}, + output: GroupVersionKind{Version: "V1", Kind: "pod"}, + }, + { + desc: "targets and kinds do not have match items", + kinds: []GroupVersionKind{{Version: "V1", Kind: "pod"}, {Version: "V2", Kind: "pod"}}, + targets: []GroupVersionKind{{Version: "V3", Kind: "pod"}, {Version: "V4", Kind: "pod"}}, + output: GroupVersionKind{Version: "V3", Kind: "pod"}, + }, + } + + for i, test := range tests { + out := bestMatch(test.kinds, test.targets) + if out != test.output { + t.Errorf("%d: expected out: %#v, got: %#v", i, test.output, out) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/interfaces.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/interfaces.go new file mode 100644 index 0000000000..f04453fb01 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/schema/interfaces.go @@ -0,0 +1,40 @@ +/* +Copyright 2016 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 schema + +// All objects that are serialized from a Scheme encode their type information. This interface is used +// by serialization to set type information from the Scheme onto the serialized version of an object. +// For objects that cannot be serialized or have unique requirements, this interface may be a no-op. +type ObjectKind interface { + // SetGroupVersionKind sets or clears the intended serialized kind of an object. Passing kind nil + // should clear the current setting. + SetGroupVersionKind(kind GroupVersionKind) + // GroupVersionKind returns the stored group, version, and kind of an object, or an empty struct + // if the object does not expose or provide these fields. + GroupVersionKind() GroupVersionKind +} + +// EmptyObjectKind implements the ObjectKind interface as a noop +var EmptyObjectKind = emptyObjectKind{} + +type emptyObjectKind struct{} + +// SetGroupVersionKind implements the ObjectKind interface +func (emptyObjectKind) SetGroupVersionKind(gvk GroupVersionKind) {} + +// GroupVersionKind implements the ObjectKind interface +func (emptyObjectKind) GroupVersionKind() GroupVersionKind { return GroupVersionKind{} } diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme.go new file mode 100644 index 0000000000..80662e55c4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme.go @@ -0,0 +1,827 @@ +/* +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 runtime + +import ( + "context" + "fmt" + "reflect" + "strings" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/naming" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kube-openapi/pkg/util" +) + +// Scheme defines methods for serializing and deserializing API objects, a type +// registry for converting group, version, and kind information to and from Go +// schemas, and mappings between Go schemas of different versions. A scheme is the +// foundation for a versioned API and versioned configuration over time. +// +// In a Scheme, a Type is a particular Go struct, a Version is a point-in-time +// identifier for a particular representation of that Type (typically backwards +// compatible), a Kind is the unique name for that Type within the Version, and a +// Group identifies a set of Versions, Kinds, and Types that evolve over time. An +// Unversioned Type is one that is not yet formally bound to a type and is promised +// to be backwards compatible (effectively a "v1" of a Type that does not expect +// to break in the future). +// +// Schemes are not expected to change at runtime and are only threadsafe after +// registration is complete. +type Scheme struct { + // gvkToType allows one to figure out the go type of an object with + // the given version and name. + gvkToType map[schema.GroupVersionKind]reflect.Type + + // typeToGVK allows one to find metadata for a given go object. + // The reflect.Type we index by should *not* be a pointer. + typeToGVK map[reflect.Type][]schema.GroupVersionKind + + // unversionedTypes are transformed without conversion in ConvertToVersion. + unversionedTypes map[reflect.Type]schema.GroupVersionKind + + // unversionedKinds are the names of kinds that can be created in the context of any group + // or version + // TODO: resolve the status of unversioned types. + unversionedKinds map[string]reflect.Type + + // Map from version and resource to the corresponding func to convert + // resource field labels in that version to internal version. + fieldLabelConversionFuncs map[schema.GroupVersionKind]FieldLabelConversionFunc + + // defaulterFuncs is a map to funcs to be called with an object to provide defaulting + // the provided object must be a pointer. + defaulterFuncs map[reflect.Type]func(interface{}) + + // validationFuncs is a map to funcs to be called with an object to perform validation. + // The provided object must be a pointer. + // If oldObject is non-nil, update validation is performed and may perform additional + // validation such as transition rules and immutability checks. + validationFuncs map[reflect.Type]func(ctx context.Context, op operation.Operation, object, oldObject interface{}) field.ErrorList + + // converter stores all registered conversion functions. It also has + // default converting behavior. + converter *conversion.Converter + + // versionPriority is a map of groups to ordered lists of versions for those groups indicating the + // default priorities of these versions as registered in the scheme + versionPriority map[string][]string + + // observedVersions keeps track of the order we've seen versions during type registration + observedVersions []schema.GroupVersion + + // schemeName is the name of this scheme. If you don't specify a name, the stack of the NewScheme caller will be used. + // This is useful for error reporting to indicate the origin of the scheme. + schemeName string +} + +// FieldLabelConversionFunc converts a field selector to internal representation. +type FieldLabelConversionFunc func(label, value string) (internalLabel, internalValue string, err error) + +// NewScheme creates a new Scheme. This scheme is pluggable by default. +func NewScheme() *Scheme { + s := &Scheme{ + gvkToType: map[schema.GroupVersionKind]reflect.Type{}, + typeToGVK: map[reflect.Type][]schema.GroupVersionKind{}, + unversionedTypes: map[reflect.Type]schema.GroupVersionKind{}, + unversionedKinds: map[string]reflect.Type{}, + fieldLabelConversionFuncs: map[schema.GroupVersionKind]FieldLabelConversionFunc{}, + defaulterFuncs: map[reflect.Type]func(interface{}){}, + validationFuncs: map[reflect.Type]func(ctx context.Context, op operation.Operation, object, oldObject interface{}) field.ErrorList{}, + versionPriority: map[string][]string{}, + schemeName: naming.GetNameFromCallsite(internalPackages...), + } + s.converter = conversion.NewConverter(nil) + + // Enable couple default conversions by default. + utilruntime.Must(RegisterEmbeddedConversions(s)) + utilruntime.Must(RegisterStringConversions(s)) + return s +} + +// Converter allows access to the converter for the scheme +func (s *Scheme) Converter() *conversion.Converter { + return s.converter +} + +// AddUnversionedTypes registers the provided types as "unversioned", which means that they follow special rules. +// Whenever an object of this type is serialized, it is serialized with the provided group version and is not +// converted. Thus unversioned objects are expected to remain backwards compatible forever, as if they were in an +// API group and version that would never be updated. +// +// TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into +// every version with particular schemas. Resolve this method at that point. +func (s *Scheme) AddUnversionedTypes(version schema.GroupVersion, types ...Object) { + s.addObservedVersion(version) + s.AddKnownTypes(version, types...) + for _, obj := range types { + t := reflect.TypeOf(obj).Elem() + gvk := version.WithKind(t.Name()) + s.unversionedTypes[t] = gvk + if old, ok := s.unversionedKinds[gvk.Kind]; ok && t != old { + panic(fmt.Sprintf("%v.%v has already been registered as unversioned kind %q - kind name must be unique in scheme %q", old.PkgPath(), old.Name(), gvk, s.schemeName)) + } + s.unversionedKinds[gvk.Kind] = t + } +} + +// AddKnownTypes registers all types passed in 'types' as being members of version 'version'. +// All objects passed to types should be pointers to structs. The name that go reports for +// the struct becomes the "kind" field when encoding. Version may not be empty - use the +// APIVersionInternal constant if you have a type that does not have a formal version. +func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) { + s.addObservedVersion(gv) + for _, obj := range types { + t := reflect.TypeOf(obj) + if t.Kind() != reflect.Pointer { + panic("All types must be pointers to structs.") + } + t = t.Elem() + s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj) + } +} + +// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should +// be encoded as. Useful for testing when you don't want to make multiple packages to define +// your structs. Version may not be empty - use the APIVersionInternal constant if you have a +// type that does not have a formal version. +func (s *Scheme) AddKnownTypeWithName(gvk schema.GroupVersionKind, obj Object) { + s.addObservedVersion(gvk.GroupVersion()) + t := reflect.TypeOf(obj) + if len(gvk.Version) == 0 { + panic(fmt.Sprintf("version is required on all types: %s %v", gvk, t)) + } + if t.Kind() != reflect.Pointer { + panic("All types must be pointers to structs.") + } + t = t.Elem() + if t.Kind() != reflect.Struct { + panic("All types must be pointers to structs.") + } + + if oldT, found := s.gvkToType[gvk]; found && oldT != t { + panic(fmt.Sprintf("Double registration of different types for %v: old=%v.%v, new=%v.%v in scheme %q", gvk, oldT.PkgPath(), oldT.Name(), t.PkgPath(), t.Name(), s.schemeName)) + } + + s.gvkToType[gvk] = t + + for _, existingGvk := range s.typeToGVK[t] { + if existingGvk == gvk { + return + } + } + s.typeToGVK[t] = append(s.typeToGVK[t], gvk) + + // if the type implements DeepCopyInto(), register a self-conversion + if m := reflect.ValueOf(obj).MethodByName("DeepCopyInto"); m.IsValid() && m.Type().NumIn() == 1 && m.Type().NumOut() == 0 && m.Type().In(0) == reflect.TypeOf(obj) { + if err := s.AddGeneratedConversionFunc(obj, obj, func(a, b interface{}, scope conversion.Scope) error { + // copy a to b + reflect.ValueOf(a).MethodByName("DeepCopyInto").Call([]reflect.Value{reflect.ValueOf(b)}) + // clear TypeMeta to match legacy reflective conversion + b.(Object).GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) + return nil + }); err != nil { + panic(err) + } + } +} + +// KnownTypes returns the types known for the given version. +func (s *Scheme) KnownTypes(gv schema.GroupVersion) map[string]reflect.Type { + types := make(map[string]reflect.Type) + for gvk, t := range s.gvkToType { + if gv != gvk.GroupVersion() { + continue + } + + types[gvk.Kind] = t + } + return types +} + +// VersionsForGroupKind returns the versions that a particular GroupKind can be converted to within the given group. +// A GroupKind might be converted to a different group. That information is available in EquivalentResourceMapper. +func (s *Scheme) VersionsForGroupKind(gk schema.GroupKind) []schema.GroupVersion { + availableVersions := []schema.GroupVersion{} + for gvk := range s.gvkToType { + if gk != gvk.GroupKind() { + continue + } + + availableVersions = append(availableVersions, gvk.GroupVersion()) + } + + // order the return for stability + ret := []schema.GroupVersion{} + for _, version := range s.PrioritizedVersionsForGroup(gk.Group) { + for _, availableVersion := range availableVersions { + if version != availableVersion { + continue + } + ret = append(ret, availableVersion) + } + } + + return ret +} + +// AllKnownTypes returns the all known types. +func (s *Scheme) AllKnownTypes() map[schema.GroupVersionKind]reflect.Type { + return s.gvkToType +} + +// ObjectKinds returns all possible group,version,kind of the go object, true if the +// object is considered unversioned, or an error if it's not a pointer or is unregistered. +func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) { + // Unstructured objects are always considered to have their declared GVK + if _, ok := obj.(Unstructured); ok { + // we require that the GVK be populated in order to recognize the object + gvk := obj.GetObjectKind().GroupVersionKind() + if len(gvk.Kind) == 0 { + return nil, false, NewMissingKindErr("unstructured object has no kind") + } + if len(gvk.Version) == 0 { + return nil, false, NewMissingVersionErr("unstructured object has no version") + } + return []schema.GroupVersionKind{gvk}, false, nil + } + + v, err := conversion.EnforcePtr(obj) + if err != nil { + return nil, false, err + } + t := v.Type() + + gvks, ok := s.typeToGVK[t] + if !ok { + return nil, false, NewNotRegisteredErrForType(s.schemeName, t) + } + _, unversionedType := s.unversionedTypes[t] + + return gvks, unversionedType, nil +} + +// Recognizes returns true if the scheme is able to handle the provided group,version,kind +// of an object. +func (s *Scheme) Recognizes(gvk schema.GroupVersionKind) bool { + _, exists := s.gvkToType[gvk] + return exists +} + +func (s *Scheme) IsUnversioned(obj Object) (bool, bool) { + v, err := conversion.EnforcePtr(obj) + if err != nil { + return false, false + } + t := v.Type() + + if _, ok := s.typeToGVK[t]; !ok { + return false, false + } + _, ok := s.unversionedTypes[t] + return ok, true +} + +// New returns a new API object of the given version and name, or an error if it hasn't +// been registered. The version and kind fields must be specified. +func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) { + if t, exists := s.gvkToType[kind]; exists { + return reflect.New(t).Interface().(Object), nil + } + + if t, exists := s.unversionedKinds[kind.Kind]; exists { + return reflect.New(t).Interface().(Object), nil + } + return nil, NewNotRegisteredErrForKind(s.schemeName, kind) +} + +// AddIgnoredConversionType identifies a pair of types that should be skipped by +// conversion (because the data inside them is explicitly dropped during +// conversion). +func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error { + return s.converter.RegisterIgnoredConversion(from, to) +} + +// AddConversionFunc registers a function that converts between a and b by passing objects of those +// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce +// any other guarantee. +func (s *Scheme) AddConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error { + return s.converter.RegisterUntypedConversionFunc(a, b, fn) +} + +// AddGeneratedConversionFunc registers a function that converts between a and b by passing objects of those +// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce +// any other guarantee. +func (s *Scheme) AddGeneratedConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error { + return s.converter.RegisterGeneratedUntypedConversionFunc(a, b, fn) +} + +// AddFieldLabelConversionFunc adds a conversion function to convert field selectors +// of the given kind from the given version to internal version representation. +func (s *Scheme) AddFieldLabelConversionFunc(gvk schema.GroupVersionKind, conversionFunc FieldLabelConversionFunc) error { + s.fieldLabelConversionFuncs[gvk] = conversionFunc + return nil +} + +// AddTypeDefaultingFunc registers a function that is passed a pointer to an +// object and can default fields on the object. These functions will be invoked +// when Default() is called. The function will never be called unless the +// defaulted object matches srcType. If this function is invoked twice with the +// same srcType, the fn passed to the later call will be used instead. +func (s *Scheme) AddTypeDefaultingFunc(srcType Object, fn func(interface{})) { + s.defaulterFuncs[reflect.TypeOf(srcType)] = fn +} + +// Default sets defaults on the provided Object. +func (s *Scheme) Default(src Object) { + if fn, ok := s.defaulterFuncs[reflect.TypeOf(src)]; ok { + fn(src) + } +} + +// AddValidationFunc registered a function that can validate the object, and +// oldObject. These functions will be invoked when Validate() or ValidateUpdate() +// is called. The function will never be called unless the validated object +// matches srcType. If this function is invoked twice with the same srcType, the +// fn passed to the later call will be used instead. +func (s *Scheme) AddValidationFunc(srcType Object, fn func(ctx context.Context, op operation.Operation, object, oldObject interface{}) field.ErrorList) { + s.validationFuncs[reflect.TypeOf(srcType)] = fn +} + +// Validate validates the provided Object according to the generated declarative validation code. +// WARNING: This does not validate all objects! The handwritten validation code in validation.go +// is not run when this is called. Only the generated zz_generated.validations.go validation code is run. +func (s *Scheme) Validate(ctx context.Context, options map[string]bool, object Object, subresources ...string) field.ErrorList { + if fn, ok := s.validationFuncs[reflect.TypeOf(object)]; ok { + return fn(ctx, operation.Operation{Type: operation.Create, Request: operation.Request{Subresources: subresources}, Options: options}, object, nil) + } + return nil +} + +// ValidateUpdate validates the provided object and oldObject according to the generated declarative validation code. +// WARNING: This does not validate all objects! The handwritten validation code in validation.go +// is not run when this is called. Only the generated zz_generated.validations.go validation code is run. +func (s *Scheme) ValidateUpdate(ctx context.Context, options map[string]bool, object, oldObject Object, subresources ...string) field.ErrorList { + if fn, ok := s.validationFuncs[reflect.TypeOf(object)]; ok { + return fn(ctx, operation.Operation{Type: operation.Update, Request: operation.Request{Subresources: subresources}, Options: options}, object, oldObject) + } + return nil +} + +// HasValidationFunc reports whether a validation function is registered for the +// object's type. Unlike Validate, it distinguishes "no function registered" from +// "function ran and found no errors", which both yield a nil error list. +func (s *Scheme) HasValidationFunc(obj Object) bool { + _, ok := s.validationFuncs[reflect.TypeOf(obj)] + return ok +} + +// Convert will attempt to convert in into out. Both must be pointers. For easy +// testing of conversion functions. Returns an error if the conversion isn't +// possible. You can call this with types that haven't been registered (for example, +// a to test conversion of types that are nested within registered types). The +// context interface is passed to the convertor. Convert also supports Unstructured +// types and will convert them intelligently. +func (s *Scheme) Convert(in, out interface{}, context interface{}) error { + unstructuredIn, okIn := in.(Unstructured) + unstructuredOut, okOut := out.(Unstructured) + switch { + case okIn && okOut: + // converting unstructured input to an unstructured output is a straight copy - unstructured + // is a "smart holder" and the contents are passed by reference between the two objects + unstructuredOut.SetUnstructuredContent(unstructuredIn.UnstructuredContent()) + return nil + + case okOut: + // if the output is an unstructured object, use the standard Go type to unstructured + // conversion. The object must not be internal. + obj, ok := in.(Object) + if !ok { + return fmt.Errorf("unable to convert object type %T to Unstructured, must be a runtime.Object", in) + } + gvks, unversioned, err := s.ObjectKinds(obj) + if err != nil { + return err + } + gvk := gvks[0] + + // if no conversion is necessary, convert immediately + if unversioned || gvk.Version != APIVersionInternal { + content, err := DefaultUnstructuredConverter.ToUnstructured(in) + if err != nil { + return err + } + unstructuredOut.SetUnstructuredContent(content) + unstructuredOut.GetObjectKind().SetGroupVersionKind(gvk) + return nil + } + + // attempt to convert the object to an external version first. + target, ok := context.(GroupVersioner) + if !ok { + return fmt.Errorf("unable to convert the internal object type %T to Unstructured without providing a preferred version to convert to", in) + } + // Convert is implicitly unsafe, so we don't need to perform a safe conversion + versioned, err := s.UnsafeConvertToVersion(obj, target) + if err != nil { + return err + } + content, err := DefaultUnstructuredConverter.ToUnstructured(versioned) + if err != nil { + return err + } + unstructuredOut.SetUnstructuredContent(content) + return nil + + case okIn: + // converting an unstructured object to any type is modeled by first converting + // the input to a versioned type, then running standard conversions + typed, err := s.unstructuredToTyped(unstructuredIn) + if err != nil { + return err + } + in = typed + } + + meta := s.generateConvertMeta(in) + meta.Context = context + return s.converter.Convert(in, out, meta) +} + +// ConvertFieldLabel alters the given field label and value for an kind field selector from +// versioned representation to an unversioned one or returns an error. +func (s *Scheme) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { + conversionFunc, ok := s.fieldLabelConversionFuncs[gvk] + if !ok { + return DefaultMetaV1FieldSelectorConversion(label, value) + } + return conversionFunc(label, value) +} + +// ConvertToVersion attempts to convert an input object to its matching Kind in another +// version within this scheme. Will return an error if the provided version does not +// contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also +// return an error if the conversion does not result in a valid Object being +// returned. Passes target down to the conversion methods as the Context on the scope. +func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) { + return s.convertToVersion(true, in, target) +} + +// UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible, +// but does not guarantee the output object does not share fields with the input object. It attempts to be as +// efficient as possible when doing conversion. +func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) { + return s.convertToVersion(false, in, target) +} + +// convertToVersion handles conversion with an optional copy. +func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) { + var t reflect.Type + + if u, ok := in.(Unstructured); ok { + typed, err := s.unstructuredToTyped(u) + if err != nil { + return nil, err + } + + in = typed + // unstructuredToTyped returns an Object, which must be a pointer to a struct. + t = reflect.TypeOf(in).Elem() + + } else { + // determine the incoming kinds with as few allocations as possible. + t = reflect.TypeOf(in) + if t.Kind() != reflect.Pointer { + return nil, fmt.Errorf("only pointer types may be converted: %v", t) + } + t = t.Elem() + if t.Kind() != reflect.Struct { + return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t) + } + } + + kinds, ok := s.typeToGVK[t] + if !ok || len(kinds) == 0 { + return nil, NewNotRegisteredErrForType(s.schemeName, t) + } + + gvk, ok := target.KindForGroupVersionKinds(kinds) + if !ok { + // try to see if this type is listed as unversioned (for legacy support) + // TODO: when we move to server API versions, we should completely remove the unversioned concept + if unversionedKind, ok := s.unversionedTypes[t]; ok { + if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok { + return copyAndSetTargetKind(copy, in, gvk) + } + return copyAndSetTargetKind(copy, in, unversionedKind) + } + return nil, NewNotRegisteredErrForTarget(s.schemeName, t, target) + } + + // target wants to use the existing type, set kind and return (no conversion necessary) + for _, kind := range kinds { + if gvk == kind { + return copyAndSetTargetKind(copy, in, gvk) + } + } + + // type is unversioned, no conversion necessary + if unversionedKind, ok := s.unversionedTypes[t]; ok { + if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok { + return copyAndSetTargetKind(copy, in, gvk) + } + return copyAndSetTargetKind(copy, in, unversionedKind) + } + + out, err := s.New(gvk) + if err != nil { + return nil, err + } + + if copy { + in = in.DeepCopyObject() + } + + meta := s.generateConvertMeta(in) + meta.Context = target + if err := s.converter.Convert(in, out, meta); err != nil { + return nil, err + } + + setTargetKind(out, gvk) + return out, nil +} + +// unstructuredToTyped attempts to transform an unstructured object to a typed +// object if possible. It will return an error if conversion is not possible, or the versioned +// Go form of the object. Note that this conversion will lose fields. +func (s *Scheme) unstructuredToTyped(in Unstructured) (Object, error) { + // the type must be something we recognize + gvks, _, err := s.ObjectKinds(in) + if err != nil { + return nil, err + } + typed, err := s.New(gvks[0]) + if err != nil { + return nil, err + } + if err := DefaultUnstructuredConverter.FromUnstructured(in.UnstructuredContent(), typed); err != nil { + return nil, fmt.Errorf("unable to convert unstructured object to %v: %v", gvks[0], err) + } + return typed, nil +} + +// generateConvertMeta constructs the meta value we pass to Convert. +func (s *Scheme) generateConvertMeta(in interface{}) *conversion.Meta { + return s.converter.DefaultMeta(reflect.TypeOf(in)) +} + +// copyAndSetTargetKind performs a conditional copy before returning the object, or an error if copy was not successful. +func copyAndSetTargetKind(copy bool, obj Object, kind schema.GroupVersionKind) (Object, error) { + if copy { + obj = obj.DeepCopyObject() + } + setTargetKind(obj, kind) + return obj, nil +} + +// setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version. +func setTargetKind(obj Object, kind schema.GroupVersionKind) { + if kind.Version == APIVersionInternal { + // internal is a special case + // TODO: look at removing the need to special case this + obj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{}) + return + } + obj.GetObjectKind().SetGroupVersionKind(kind) +} + +// SetVersionPriority allows specifying a precise order of priority. All specified versions must be in the same group, +// and the specified order overwrites any previously specified order for this group +func (s *Scheme) SetVersionPriority(versions ...schema.GroupVersion) error { + groups := sets.String{} + order := []string{} + for _, version := range versions { + if len(version.Version) == 0 || version.Version == APIVersionInternal { + return fmt.Errorf("internal versions cannot be prioritized: %v", version) + } + + groups.Insert(version.Group) + order = append(order, version.Version) + } + if len(groups) != 1 { + return fmt.Errorf("must register versions for exactly one group: %v", strings.Join(groups.List(), ", ")) + } + + s.versionPriority[groups.List()[0]] = order + return nil +} + +// PrioritizedVersionsForGroup returns versions for a single group in priority order +func (s *Scheme) PrioritizedVersionsForGroup(group string) []schema.GroupVersion { + ret := []schema.GroupVersion{} + for _, version := range s.versionPriority[group] { + ret = append(ret, schema.GroupVersion{Group: group, Version: version}) + } + for _, observedVersion := range s.observedVersions { + if observedVersion.Group != group { + continue + } + found := false + for _, existing := range ret { + if existing == observedVersion { + found = true + break + } + } + if !found { + ret = append(ret, observedVersion) + } + } + + return ret +} + +// PrioritizedVersionsAllGroups returns all known versions in their priority order. Groups are random, but +// versions for a single group are prioritized +func (s *Scheme) PrioritizedVersionsAllGroups() []schema.GroupVersion { + ret := []schema.GroupVersion{} + for group, versions := range s.versionPriority { + for _, version := range versions { + ret = append(ret, schema.GroupVersion{Group: group, Version: version}) + } + } + for _, observedVersion := range s.observedVersions { + found := false + for _, existing := range ret { + if existing == observedVersion { + found = true + break + } + } + if !found { + ret = append(ret, observedVersion) + } + } + return ret +} + +// PreferredVersionAllGroups returns the most preferred version for every group. +// group ordering is random. +func (s *Scheme) PreferredVersionAllGroups() []schema.GroupVersion { + ret := []schema.GroupVersion{} + for group, versions := range s.versionPriority { + for _, version := range versions { + ret = append(ret, schema.GroupVersion{Group: group, Version: version}) + break + } + } + for _, observedVersion := range s.observedVersions { + found := false + for _, existing := range ret { + if existing.Group == observedVersion.Group { + found = true + break + } + } + if !found { + ret = append(ret, observedVersion) + } + } + + return ret +} + +// IsGroupRegistered returns true if types for the group have been registered with the scheme +func (s *Scheme) IsGroupRegistered(group string) bool { + for _, observedVersion := range s.observedVersions { + if observedVersion.Group == group { + return true + } + } + return false +} + +// IsVersionRegistered returns true if types for the version have been registered with the scheme +func (s *Scheme) IsVersionRegistered(version schema.GroupVersion) bool { + for _, observedVersion := range s.observedVersions { + if observedVersion == version { + return true + } + } + + return false +} + +func (s *Scheme) addObservedVersion(version schema.GroupVersion) { + if len(version.Version) == 0 || version.Version == APIVersionInternal { + return + } + for _, observedVersion := range s.observedVersions { + if observedVersion == version { + return + } + } + + s.observedVersions = append(s.observedVersions, version) +} + +func (s *Scheme) Name() string { + return s.schemeName +} + +// internalPackages are packages that ignored when creating a default reflector name. These packages are in the common +// call chains to NewReflector, so they'd be low entropy names for reflectors +var internalPackages = []string{"k8s.io/apimachinery/pkg/runtime/scheme.go"} + +// ToOpenAPIDefinitionName returns the REST-friendly OpenAPI definition name known type identified by groupVersionKind. +// If the groupVersionKind does not identify a known type, an error is returned. +// The Version field of groupVersionKind is required, and the Group and Kind fields are required for unstructured.Unstructured +// types. If a required field is empty, an error is returned. +// +// The OpenAPI definition name is the canonical name of the type, with the group and version removed. +// For example, the OpenAPI definition name of Pod is `io.k8s.api.core.v1.Pod`. +// +// This respects the util.OpenAPIModelNamer interface and will return the name returned by +// OpenAPIModelName() if it is defined on the type. +// +// A known type that is registered as an unstructured.Unstructured type is treated as a custom resource and +// which has an OpenAPI definition name of the form `.`. +// For example, the OpenAPI definition name of `group: stable.example.com, version: v1, kind: Pod` is +// `com.example.stable.v1.Pod`. +func (s *Scheme) ToOpenAPIDefinitionName(groupVersionKind schema.GroupVersionKind) (string, error) { + if groupVersionKind.Version == "" { // Empty version is not allowed by New() so check it first to avoid a panic. + return "", fmt.Errorf("version is required on all types: %v", groupVersionKind) + } + example, err := s.New(groupVersionKind) + if err != nil { + return "", err + } + + // Use a namer if provided + if namer, ok := example.(util.OpenAPIModelNamer); ok { + return namer.OpenAPIModelName(), nil + } + + if _, ok := example.(Unstructured); ok { + if groupVersionKind.Group == "" || groupVersionKind.Kind == "" { + return "", fmt.Errorf("unable to convert GroupVersionKind with empty fields to unstructured type to an OpenAPI definition name: %v", groupVersionKind) + } + return reverseParts(groupVersionKind.Group) + "." + groupVersionKind.Version + "." + groupVersionKind.Kind, nil + } + rtype := reflect.TypeOf(example).Elem() + name := toOpenAPIDefinitionName(rtype.PkgPath() + "." + rtype.Name()) + return name, nil +} + +// toOpenAPIDefinitionName converts Golang package/type canonical name into REST friendly OpenAPI name. +// Input is expected to be `PkgPath + "." TypeName. +// +// Examples of REST friendly OpenAPI name: +// +// Input: k8s.io/api/core/v1.Pod +// Output: io.k8s.api.core.v1.Pod +// +// Input: k8s.io/api/core/v1 +// Output: io.k8s.api.core.v1 +// +// Input: csi.storage.k8s.io/v1alpha1.CSINodeInfo +// Output: io.k8s.storage.csi.v1alpha1.CSINodeInfo +// +// Note that this is a copy of ToRESTFriendlyName from k8s.io/kube-openapi/pkg/util. It is duplicated here to avoid +// a dependency on kube-openapi. +func toOpenAPIDefinitionName(name string) string { + nameParts := strings.Split(name, "/") + // Reverse first part. e.g., io.k8s... instead of k8s.io... + if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { + nameParts[0] = reverseParts(nameParts[0]) + } + return strings.Join(nameParts, ".") +} + +func reverseParts(dotSeparatedName string) string { + parts := strings.Split(dotSeparatedName, ".") + for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { + parts[i], parts[j] = parts[j], parts[i] + } + return strings.Join(parts, ".") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme_builder.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme_builder.go new file mode 100644 index 0000000000..944db48182 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme_builder.go @@ -0,0 +1,48 @@ +/* +Copyright 2015 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 runtime + +// SchemeBuilder collects functions that add things to a scheme. It's to allow +// code to compile without explicitly referencing generated types. You should +// declare one in each package that will have generated deep copy or conversion +// functions. +type SchemeBuilder []func(*Scheme) error + +// AddToScheme applies all the stored functions to the scheme. A non-nil error +// indicates that one function failed and the attempt was abandoned. +func (sb *SchemeBuilder) AddToScheme(s *Scheme) error { + for _, f := range *sb { + if err := f(s); err != nil { + return err + } + } + return nil +} + +// Register adds a scheme setup function to the list. +func (sb *SchemeBuilder) Register(funcs ...func(*Scheme) error) { + for _, f := range funcs { + *sb = append(*sb, f) + } +} + +// NewSchemeBuilder calls Register for you. +func NewSchemeBuilder(funcs ...func(*Scheme) error) SchemeBuilder { + var sb SchemeBuilder + sb.Register(funcs...) + return sb +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme_test.go new file mode 100644 index 0000000000..5e4fe1e2c9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/scheme_test.go @@ -0,0 +1,1181 @@ +/* +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 runtime_test + +import ( + "context" + "fmt" + "reflect" + "slices" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" + "k8s.io/apimachinery/pkg/util/diff" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +type testConversions struct { + internalToExternalCalls int + externalToInternalCalls int +} + +func (c *testConversions) internalToExternalSimple(in *runtimetesting.InternalSimple, out *runtimetesting.ExternalSimple, scope conversion.Scope) error { + out.TypeMeta = in.TypeMeta + out.TestString = in.TestString + c.internalToExternalCalls++ + return nil +} + +func (c *testConversions) externalToInternalSimple(in *runtimetesting.ExternalSimple, out *runtimetesting.InternalSimple, scope conversion.Scope) error { + out.TypeMeta = in.TypeMeta + out.TestString = in.TestString + c.externalToInternalCalls++ + return nil +} + +func (c *testConversions) registerConversions(s *runtime.Scheme) error { + if err := s.AddConversionFunc((*runtimetesting.InternalSimple)(nil), (*runtimetesting.ExternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { + return c.internalToExternalSimple(a.(*runtimetesting.InternalSimple), b.(*runtimetesting.ExternalSimple), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*runtimetesting.ExternalSimple)(nil), (*runtimetesting.InternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { + return c.externalToInternalSimple(a.(*runtimetesting.ExternalSimple), b.(*runtimetesting.InternalSimple), scope) + }); err != nil { + return err + } + return nil +} + +func TestScheme(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + internalGVK := internalGV.WithKind("Simple") + externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} + externalGVK := externalGV.WithKind("Simple") + + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) + scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) + utilruntime.Must(runtimetesting.RegisterConversions(scheme)) + + // If set, would clear TypeMeta during conversion. + //scheme.AddIgnoredConversionType(&TypeMeta{}, &TypeMeta{}) + + // test that scheme is an ObjectTyper + var _ runtime.ObjectTyper = scheme + + conversions := &testConversions{ + internalToExternalCalls: 0, + externalToInternalCalls: 0, + } + + // Register functions to verify that scope.Meta() gets set correctly. + utilruntime.Must(conversions.registerConversions(scheme)) + + t.Run("Encode, Decode, DecodeInto, and DecodeToVersion", func(t *testing.T) { + simple := &runtimetesting.InternalSimple{ + TestString: "foo", + } + + codecs := serializer.NewCodecFactory(scheme) + codec := codecs.LegacyCodec(externalGV) + info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) + jsonserializer := info.Serializer + + obj := runtime.Object(simple) + data, err := runtime.Encode(codec, obj) + if err != nil { + t.Fatal(err) + } + + obj2, err := runtime.Decode(codec, data) + if err != nil { + t.Fatal(err) + } + if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { + t.Fatalf("Got wrong type") + } + if e, a := simple, obj2; !reflect.DeepEqual(e, a) { + t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) + } + + obj3 := &runtimetesting.InternalSimple{} + if err := runtime.DecodeInto(codec, data, obj3); err != nil { + t.Fatal(err) + } + // clearing TypeMeta is a function of the scheme, which we do not test here (ConvertToVersion + // does not automatically clear TypeMeta anymore). + simple.TypeMeta = runtime.TypeMeta{Kind: "Simple", APIVersion: externalGV.String()} + if e, a := simple, obj3; !reflect.DeepEqual(e, a) { + t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) + } + + obj4, err := runtime.Decode(jsonserializer, data) + if err != nil { + t.Fatal(err) + } + if _, ok := obj4.(*runtimetesting.ExternalSimple); !ok { + t.Fatalf("Got wrong type") + } + }) + t.Run("Convert", func(t *testing.T) { + simple := &runtimetesting.InternalSimple{ + TestString: "foo", + } + + external := &runtimetesting.ExternalSimple{} + if err := scheme.Convert(simple, external, nil); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := simple.TestString, external.TestString; e != a { + t.Errorf("Expected %q, got %q", e, a) + } + }) + t.Run("Convert internal to unstructured", func(t *testing.T) { + simple := &runtimetesting.InternalSimple{ + TestString: "foo", + } + + unstructuredObj := &runtimetesting.Unstructured{} + err := scheme.Convert(simple, unstructuredObj, nil) + if err == nil || !strings.Contains(err.Error(), "to Unstructured without providing a preferred version to convert to") { + t.Fatalf("Unexpected non-error: %v", err) + } + if err := scheme.Convert(simple, unstructuredObj, externalGV); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := simple.TestString, unstructuredObj.Object["testString"].(string); e != a { + t.Errorf("Expected %q, got %q", e, a) + } + if e := unstructuredObj.GetObjectKind().GroupVersionKind(); e != externalGVK { + t.Errorf("Unexpected object kind: %#v", e) + } + if gvks, unversioned, err := scheme.ObjectKinds(unstructuredObj); err != nil || gvks[0] != externalGVK || unversioned { + t.Errorf("Scheme did not recognize unversioned: %v, %#v %t", err, gvks, unversioned) + } + }) + t.Run("Convert external to unstructured", func(t *testing.T) { + unstructuredObj := &runtimetesting.Unstructured{} + external := &runtimetesting.ExternalSimple{ + TestString: "foo", + } + + if err := scheme.Convert(external, unstructuredObj, nil); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := external.TestString, unstructuredObj.Object["testString"].(string); e != a { + t.Errorf("Expected %q, got %q", e, a) + } + if e := unstructuredObj.GetObjectKind().GroupVersionKind(); e != externalGVK { + t.Errorf("Unexpected object kind: %#v", e) + } + }) + t.Run("Convert unstructured to unstructured", func(t *testing.T) { + uIn := &runtimetesting.Unstructured{Object: map[string]interface{}{ + "test": []interface{}{"other", "test"}, + }} + uOut := &runtimetesting.Unstructured{} + if err := scheme.Convert(uIn, uOut, nil); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(uIn.Object, uOut.Object) { + t.Errorf("Unexpected object contents: %#v", uOut.Object) + } + }) + t.Run("Convert unstructured to structured", func(t *testing.T) { + unstructuredObj := &runtimetesting.Unstructured{ + Object: map[string]interface{}{ + "testString": "bla", + }, + } + unstructuredObj.SetGroupVersionKind(externalGV.WithKind("Simple")) + externalOut := &runtimetesting.ExternalSimple{} + if err := scheme.Convert(unstructuredObj, externalOut, nil); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if externalOut.TestString != "bla" { + t.Errorf("Unexpected object contents: %#v", externalOut) + } + }) + t.Run("Encode and Convert should each have caused an increment", func(t *testing.T) { + if e, a := 3, conversions.internalToExternalCalls; e != a { + t.Errorf("Expected %v, got %v", e, a) + } + }) + t.Run("DecodeInto and Decode should each have caused an increment because of a conversion", func(t *testing.T) { + if e, a := 2, conversions.externalToInternalCalls; e != a { + t.Errorf("Expected %v, got %v", e, a) + } + }) + t.Run("Verify that unstructured types must have V and K set", func(t *testing.T) { + emptyObj := &runtimetesting.Unstructured{Object: make(map[string]interface{})} + if _, _, err := scheme.ObjectKinds(emptyObj); !runtime.IsMissingKind(err) { + t.Errorf("unexpected error: %v", err) + } + emptyObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Test"}) + if _, _, err := scheme.ObjectKinds(emptyObj); !runtime.IsMissingVersion(err) { + t.Errorf("unexpected error: %v", err) + } + emptyObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Test", Version: "v1"}) + if _, _, err := scheme.ObjectKinds(emptyObj); err != nil { + t.Errorf("unexpected error: %v", err) + } + }) +} + +func TestBadJSONRejection(t *testing.T) { + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) + jsonserializer := info.Serializer + + badJSONMissingKind := []byte(`{ }`) + if _, err := runtime.Decode(jsonserializer, badJSONMissingKind); err == nil { + t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind) + } + badJSONUnknownType := []byte(`{"kind": "bar"}`) + if _, err1 := runtime.Decode(jsonserializer, badJSONUnknownType); err1 == nil { + t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType) + } +} + +func TestExternalToInternalMapping(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} + + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &runtimetesting.InternalOptionalExtensionType{}) + scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &runtimetesting.ExternalOptionalExtensionType{}) + utilruntime.Must(runtimetesting.RegisterConversions(scheme)) + + codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) + + table := []struct { + obj runtime.Object + encoded string + }{ + { + &runtimetesting.InternalOptionalExtensionType{Extension: nil}, + `{"kind":"OptionalExtensionType","apiVersion":"` + externalGV.String() + `"}`, + }, + } + + for i, item := range table { + gotDecoded, err := runtime.Decode(codec, []byte(item.encoded)) + if err != nil { + t.Errorf("unexpected error '%v' (%v)", err, item.encoded) + } else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) { + t.Errorf("%d: unexpected objects:\n%s", i, diff.ObjectGoPrintSideBySide(e, a)) + } + } +} + +func TestExtensionMapping(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} + + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName(internalGV.WithKind("ExtensionType"), &runtimetesting.InternalExtensionType{}) + scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &runtimetesting.InternalOptionalExtensionType{}) + scheme.AddKnownTypeWithName(externalGV.WithKind("ExtensionType"), &runtimetesting.ExternalExtensionType{}) + scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &runtimetesting.ExternalOptionalExtensionType{}) + + // register external first when the object is the same in both schemes, so ObjectVersionAndKind reports the + // external version. + scheme.AddKnownTypeWithName(externalGV.WithKind("A"), &runtimetesting.ExtensionA{}) + scheme.AddKnownTypeWithName(externalGV.WithKind("B"), &runtimetesting.ExtensionB{}) + scheme.AddKnownTypeWithName(internalGV.WithKind("A"), &runtimetesting.ExtensionA{}) + scheme.AddKnownTypeWithName(internalGV.WithKind("B"), &runtimetesting.ExtensionB{}) + utilruntime.Must(runtimetesting.RegisterConversions(scheme)) + + codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) + + table := []struct { + obj runtime.Object + expected runtime.Object + encoded string + }{ + { + &runtimetesting.InternalExtensionType{ + Extension: runtime.NewEncodable(codec, &runtimetesting.ExtensionA{TestString: "foo"}), + }, + &runtimetesting.InternalExtensionType{ + Extension: &runtime.Unknown{ + Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}`), + ContentType: runtime.ContentTypeJSON, + }, + }, + // apiVersion is set in the serialized object for easier consumption by clients + `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}} +`, + }, { + &runtimetesting.InternalExtensionType{Extension: runtime.NewEncodable(codec, &runtimetesting.ExtensionB{TestString: "bar"})}, + &runtimetesting.InternalExtensionType{ + Extension: &runtime.Unknown{ + Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}`), + ContentType: runtime.ContentTypeJSON, + }, + }, + // apiVersion is set in the serialized object for easier consumption by clients + `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}} +`, + }, { + &runtimetesting.InternalExtensionType{Extension: nil}, + &runtimetesting.InternalExtensionType{ + Extension: nil, + }, + `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":null} +`, + }, + } + + for i, item := range table { + gotEncoded, err := runtime.Encode(codec, item.obj) + if err != nil { + t.Errorf("unexpected error '%v' (%#v)", err, item.obj) + } else if e, a := item.encoded, string(gotEncoded); e != a { + t.Errorf("expected\n%#v\ngot\n%#v\n", e, a) + } + + gotDecoded, err := runtime.Decode(codec, []byte(item.encoded)) + if err != nil { + t.Errorf("unexpected error '%v' (%v)", err, item.encoded) + } else if e, a := item.expected, gotDecoded; !reflect.DeepEqual(e, a) { + t.Errorf("%d: unexpected objects:\n%s", i, diff.ObjectGoPrintSideBySide(e, a)) + } + } +} + +func TestEncode(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + internalGVK := internalGV.WithKind("Simple") + externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} + externalGVK := externalGV.WithKind("Simple") + + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) + scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) + utilruntime.Must(runtimetesting.RegisterConversions(scheme)) + + codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) + + test := &runtimetesting.InternalSimple{ + TestString: "I'm the same", + } + obj := runtime.Object(test) + data, err := runtime.Encode(codec, obj) + obj2, gvk, err2 := codec.Decode(data, nil, nil) + if err != nil || err2 != nil { + t.Fatalf("Failure: '%v' '%v'", err, err2) + } + if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { + t.Fatalf("Got wrong type") + } + if !reflect.DeepEqual(obj2, test) { + t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2) + } + if *gvk != externalGVK { + t.Errorf("unexpected gvk returned by decode: %#v", *gvk) + } +} + +func TestUnversionedTypes(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + internalGVK := internalGV.WithKind("Simple") + externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} + externalGVK := externalGV.WithKind("Simple") + otherGV := schema.GroupVersion{Group: "group", Version: "other"} + + scheme := runtime.NewScheme() + scheme.AddUnversionedTypes(externalGV, &runtimetesting.InternalSimple{}) + scheme.AddKnownTypeWithName(internalGVK, &runtimetesting.InternalSimple{}) + scheme.AddKnownTypeWithName(externalGVK, &runtimetesting.ExternalSimple{}) + scheme.AddKnownTypeWithName(otherGV.WithKind("Simple"), &runtimetesting.ExternalSimple{}) + utilruntime.Must(runtimetesting.RegisterConversions(scheme)) + + codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) + + if unv, ok := scheme.IsUnversioned(&runtimetesting.InternalSimple{}); !unv || !ok { + t.Fatalf("type not unversioned and in scheme: %t %t", unv, ok) + } + + kinds, _, err := scheme.ObjectKinds(&runtimetesting.InternalSimple{}) + if err != nil { + t.Fatal(err) + } + kind := kinds[0] + if kind != externalGV.WithKind("InternalSimple") { + t.Fatalf("unexpected: %#v", kind) + } + + test := &runtimetesting.InternalSimple{ + TestString: "I'm the same", + } + obj := runtime.Object(test) + data, err := runtime.Encode(codec, obj) + if err != nil { + t.Fatal(err) + } + obj2, gvk, err := codec.Decode(data, nil, nil) + if err != nil { + t.Fatal(err) + } + if _, ok := obj2.(*runtimetesting.InternalSimple); !ok { + t.Fatalf("Got wrong type") + } + if !reflect.DeepEqual(obj2, test) { + t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2) + } + // object is serialized as an unversioned object (in the group and version it was defined in) + if *gvk != externalGV.WithKind("InternalSimple") { + t.Errorf("unexpected gvk returned by decode: %#v", *gvk) + } + + // when serialized to a different group, the object is kept in its preferred name + codec = serializer.NewCodecFactory(scheme).LegacyCodec(otherGV) + data, err = runtime.Encode(codec, obj) + if err != nil { + t.Fatal(err) + } + if string(data) != `{"apiVersion":"test.group/testExternal","kind":"InternalSimple","testString":"I'm the same"}`+"\n" { + t.Errorf("unexpected data: %s", data) + } +} + +// Returns a new Scheme set up with the test objects. +func GetTestScheme() *runtime.Scheme { + internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Version: "v1"} + alternateExternalGV := schema.GroupVersion{Group: "custom", Version: "v1"} + alternateInternalGV := schema.GroupVersion{Group: "custom", Version: runtime.APIVersionInternal} + differentExternalGV := schema.GroupVersion{Group: "other", Version: "v2"} + + s := runtime.NewScheme() + // Ordinarily, we wouldn't add TestType2, but because this is a test and + // both types are from the same package, we need to get it into the system + // so that converter will match it with ExternalType2. + s.AddKnownTypes(internalGV, &runtimetesting.TestType1{}, &runtimetesting.TestType2{}, &runtimetesting.ExternalInternalSame{}) + s.AddKnownTypes(externalGV, &runtimetesting.ExternalInternalSame{}) + s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &runtimetesting.ExternalTestType2{}) + s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) + s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(externalGV.WithKind("TestType4"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(alternateInternalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) + s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType5"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(differentExternalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) + s.AddUnversionedTypes(externalGV, &runtimetesting.UnversionedType{}) + utilruntime.Must(runtimetesting.RegisterConversions(s)) + + return s +} + +func TestKnownTypes(t *testing.T) { + s := GetTestScheme() + if len(s.KnownTypes(schema.GroupVersion{Group: "group", Version: "v2"})) != 0 { + t.Errorf("should have no known types for v2") + } + + types := s.KnownTypes(schema.GroupVersion{Version: "v1"}) + for _, s := range []string{"TestType1", "TestType2", "TestType3", "ExternalInternalSame"} { + if _, ok := types[s]; !ok { + t.Errorf("missing type %q", s) + } + } +} + +func TestAddKnownTypesIdemPotent(t *testing.T) { + s := runtime.NewScheme() + + gv := schema.GroupVersion{Group: "foo", Version: "v1"} + s.AddKnownTypes(gv, &runtimetesting.InternalSimple{}) + s.AddKnownTypes(gv, &runtimetesting.InternalSimple{}) + if len(s.KnownTypes(gv)) != 1 { + t.Errorf("expected only one %v type after double registration", gv) + } + if len(s.AllKnownTypes()) != 1 { + t.Errorf("expected only one type after double registration") + } + + s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) + s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) + if len(s.KnownTypes(gv)) != 1 { + t.Errorf("expected only one %v type after double registration with custom name", gv) + } + if len(s.AllKnownTypes()) != 1 { + t.Errorf("expected only one type after double registration with custom name") + } + + s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) + s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) + if len(s.KnownTypes(gv)) != 1 { + t.Errorf("expected only one %v type after double registration with custom name", gv) + } + if len(s.AllKnownTypes()) != 1 { + t.Errorf("expected only one type after double registration with custom name") + } + + kinds, _, err := s.ObjectKinds(&runtimetesting.InternalSimple{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(kinds) != 1 { + t.Errorf("expected only one kind for InternalSimple after double registration") + } +} + +// redefine InternalSimple with the same name, but obviously as a different type than in runtimetesting +type InternalSimple struct { + runtime.TypeMeta `json:""` + TestString string `json:"testString"` +} + +func (s *InternalSimple) DeepCopyObject() runtime.Object { return nil } + +func TestConflictingAddKnownTypes(t *testing.T) { + s := runtime.NewScheme() + gv := schema.GroupVersion{Group: "foo", Version: "v1"} + + panicked := make(chan bool) + go func() { + defer func() { + if recover() != nil { + panicked <- true + } + }() + s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.InternalSimple{}) + s.AddKnownTypeWithName(gv.WithKind("InternalSimple"), &runtimetesting.ExternalSimple{}) + panicked <- false + }() + if !<-panicked { + t.Errorf("Expected AddKnownTypesWithName to panic with conflicting type registrations") + } + + go func() { + defer func() { + if recover() != nil { + panicked <- true + } + }() + + s.AddUnversionedTypes(gv, &runtimetesting.InternalSimple{}) + s.AddUnversionedTypes(gv, &InternalSimple{}) + panicked <- false + }() + if !<-panicked { + t.Errorf("Expected AddUnversionedTypes to panic with conflicting type registrations") + } +} + +func TestConvertToVersionBasic(t *testing.T) { + s := GetTestScheme() + tt := &runtimetesting.TestType1{A: "I'm not a pointer object"} + other, err := s.ConvertToVersion(tt, schema.GroupVersion{Version: "v1"}) + if err != nil { + t.Fatalf("Failure: %v", err) + } + converted, ok := other.(*runtimetesting.ExternalTestType1) + if !ok { + t.Fatalf("Got wrong type: %T", other) + } + if tt.A != converted.A { + t.Fatalf("Failed to convert object correctly: %#v", converted) + } +} + +type testGroupVersioner struct { + target schema.GroupVersionKind + ok bool +} + +func (m testGroupVersioner) KindForGroupVersionKinds(kinds []schema.GroupVersionKind) (schema.GroupVersionKind, bool) { + return m.target, m.ok +} + +func (m testGroupVersioner) Identifier() string { + return "testGroupVersioner" +} + +func TestConvertToVersion(t *testing.T) { + testCases := []struct { + scheme *runtime.Scheme + in runtime.Object + gv runtime.GroupVersioner + same bool + out runtime.Object + errFn func(error) bool + }{ + // errors if the type is not registered in the scheme + { + scheme: GetTestScheme(), + in: &runtimetesting.UnknownType{}, + errFn: func(err error) bool { return err != nil && runtime.IsNotRegisteredError(err) }, + }, + // errors if the group versioner returns no target + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: testGroupVersioner{}, + errFn: func(err error) bool { + return err != nil && strings.Contains(err.Error(), "is not suitable for converting") + }, + }, + // converts to internal + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: schema.GroupVersion{Version: runtime.APIVersionInternal}, + out: &runtimetesting.TestType1{A: "test"}, + }, + // converts from unstructured to internal + { + scheme: GetTestScheme(), + in: &runtimetesting.Unstructured{Object: map[string]interface{}{ + "apiVersion": "custom/v1", + "kind": "TestType3", + "A": "test", + }}, + gv: schema.GroupVersion{Version: runtime.APIVersionInternal}, + out: &runtimetesting.TestType1{A: "test"}, + }, + // converts from unstructured to external + { + scheme: GetTestScheme(), + in: &runtimetesting.Unstructured{Object: map[string]interface{}{ + "apiVersion": "custom/v1", + "kind": "TestType3", + "A": "test", + }}, + gv: schema.GroupVersion{Group: "custom", Version: "v1"}, + out: &runtimetesting.ExternalTestType1{MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, A: "test"}, + }, + // prefers the best match + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: schema.GroupVersions{{Version: runtime.APIVersionInternal}, {Version: "v1"}}, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, + A: "test", + }, + }, + // unversioned type returned as-is + { + scheme: GetTestScheme(), + in: &runtimetesting.UnversionedType{A: "test"}, + gv: schema.GroupVersions{{Version: "v1"}}, + same: true, + out: &runtimetesting.UnversionedType{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, + A: "test", + }, + }, + // unversioned type returned when not included in the target types + { + scheme: GetTestScheme(), + in: &runtimetesting.UnversionedType{A: "test"}, + gv: schema.GroupVersions{{Group: "other", Version: "v2"}}, + same: true, + out: &runtimetesting.UnversionedType{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "UnversionedType"}, + A: "test", + }, + }, + // detected as already being in the target version + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: schema.GroupVersions{{Version: "v1"}}, + same: true, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, + A: "test", + }, + }, + // detected as already being in the first target version + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: schema.GroupVersions{{Version: "v1"}, {Version: runtime.APIVersionInternal}}, + same: true, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, + A: "test", + }, + }, + // detected as already being in the first target version + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: schema.GroupVersions{{Version: "v1"}, {Version: runtime.APIVersionInternal}}, + same: true, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, + A: "test", + }, + }, + // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (1/3): different kind + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Version: "v1"}}, + same: true, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, + A: "test", + }, + }, + // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (2/3): different gv + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType3", Group: "custom", Version: "v1"}}, + same: true, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType3"}, + A: "test", + }, + }, + // the external type is registered in multiple groups, versions, and kinds, and can be targeted to all of them (3/3): different gvk + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Group: "custom", Version: "v1", Kind: "TestType5"}}, + same: true, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, + A: "test", + }, + }, + // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, + A: "test", + }, + }, + // multi group versioner recognizes multiple groups and forces the output to a particular version, copies because version differs + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "other", Version: "v2"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "other/v2", ObjectKind: "TestType1"}, + A: "test", + }, + }, + // multi group versioner is unable to find a match when kind AND group don't match (there is no TestType1 kind in group "other", and no kind "TestType5" in the default group) + { + scheme: GetTestScheme(), + in: &runtimetesting.TestType1{A: "test"}, + gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "custom", Version: "v1"}, schema.GroupKind{Group: "other"}, schema.GroupKind{Kind: "TestType5"}), + errFn: func(err error) bool { + return err != nil && strings.Contains(err.Error(), "is not suitable for converting") + }, + }, + // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}, schema.GroupKind{Kind: "TestType1"}), + same: true, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, + A: "test", + }, + }, + // multi group versioner recognizes multiple groups and forces the output to a particular version, performs no copy + { + scheme: GetTestScheme(), + in: &runtimetesting.ExternalTestType1{A: "test"}, + gv: runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: "", Version: "v1"}, schema.GroupKind{Kind: "TestType1"}, schema.GroupKind{Group: "custom", Kind: "TestType3"}), + same: true, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType1"}, + A: "test", + }, + }, + // group versioner can choose a particular target kind for a given input when kind is the same across group versions + { + scheme: GetTestScheme(), + in: &runtimetesting.TestType1{A: "test"}, + gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Version: "v1", Kind: "TestType3"}}, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "v1", ObjectKind: "TestType3"}, + A: "test", + }, + }, + // group versioner can choose a different kind + { + scheme: GetTestScheme(), + in: &runtimetesting.TestType1{A: "test"}, + gv: testGroupVersioner{ok: true, target: schema.GroupVersionKind{Kind: "TestType5", Group: "custom", Version: "v1"}}, + out: &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{APIVersion: "custom/v1", ObjectKind: "TestType5"}, + A: "test", + }, + }, + } + for i, test := range testCases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + original := test.in.DeepCopyObject() + out, err := test.scheme.ConvertToVersion(test.in, test.gv) + switch { + case test.errFn != nil: + if !test.errFn(err) { + t.Fatalf("unexpected error: %v", err) + } + return + case err != nil: + t.Fatalf("unexpected error: %v", err) + } + if out == test.in { + t.Fatalf("ConvertToVersion should always copy out: %#v", out) + } + + if test.same { + if !reflect.DeepEqual(original, test.in) { + t.Fatalf("unexpected mutation of input: %s", cmp.Diff(original, test.in)) + } + if !reflect.DeepEqual(out, test.out) { + t.Fatalf("unexpected out: %s", cmp.Diff(out, test.out)) + } + unsafe, err := test.scheme.UnsafeConvertToVersion(test.in, test.gv) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(unsafe, test.out) { + t.Fatalf("unexpected unsafe: %s", cmp.Diff(unsafe, test.out)) + } + if unsafe != test.in { + t.Fatalf("UnsafeConvertToVersion should return same object: %#v", unsafe) + } + return + } + if !reflect.DeepEqual(out, test.out) { + t.Fatalf("unexpected out: %s", cmp.Diff(out, test.out)) + } + }) + } +} + +func TestConvert(t *testing.T) { + testCases := []struct { + scheme *runtime.Scheme + in runtime.Object + into runtime.Object + gv runtime.GroupVersioner + out runtime.Object + errFn func(error) bool + }{ + // converts from internal to unstructured, given a target version + { + scheme: GetTestScheme(), + in: &runtimetesting.TestType1{A: "test"}, + into: &runtimetesting.Unstructured{}, + out: &runtimetesting.Unstructured{Object: map[string]interface{}{ + "myVersionKey": "custom/v1", + "myKindKey": "TestType3", + "A": "test", + }}, + gv: schema.GroupVersion{Group: "custom", Version: "v1"}, + }, + } + for i, test := range testCases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + err := test.scheme.Convert(test.in, test.into, test.gv) + switch { + case test.errFn != nil: + if !test.errFn(err) { + t.Fatalf("unexpected error: %v", err) + } + return + case err != nil: + t.Fatalf("unexpected error: %v", err) + return + } + + if !reflect.DeepEqual(test.into, test.out) { + t.Fatalf("unexpected out: %s", cmp.Diff(test.into, test.out)) + } + }) + } +} + +func TestMetaValues(t *testing.T) { + internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Group: "test.group", Version: "externalVersion"} + + s := runtime.NewScheme() + s.AddKnownTypeWithName(internalGV.WithKind("Simple"), &runtimetesting.InternalSimple{}) + s.AddKnownTypeWithName(externalGV.WithKind("Simple"), &runtimetesting.ExternalSimple{}) + utilruntime.Must(runtimetesting.RegisterConversions(s)) + + conversions := &testConversions{ + internalToExternalCalls: 0, + externalToInternalCalls: 0, + } + + // Register functions to verify that scope.Meta() gets set correctly. + utilruntime.Must(conversions.registerConversions(s)) + + simple := &runtimetesting.InternalSimple{ + TestString: "foo", + } + + out, err := s.ConvertToVersion(simple, externalGV) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + internal, err := s.ConvertToVersion(out, internalGV) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if e, a := simple, internal; !reflect.DeepEqual(e, a) { + t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) + } + + if e, a := 1, conversions.internalToExternalCalls; e != a { + t.Errorf("Expected %v, got %v", e, a) + } + if e, a := 1, conversions.externalToInternalCalls; e != a { + t.Errorf("Expected %v, got %v", e, a) + } +} + +func TestMetaValuesUnregisteredConvert(t *testing.T) { + type InternalSimple struct { + Version string `json:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty"` + TestString string `json:"testString"` + } + type ExternalSimple struct { + Version string `json:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty"` + TestString string `json:"testString"` + } + s := runtime.NewScheme() + // We deliberately don't register the types. + + internalToExternalCalls := 0 + + // Register functions to verify that scope.Meta() gets set correctly. + convertSimple := func(in *InternalSimple, out *ExternalSimple, scope conversion.Scope) error { + out.TestString = in.TestString + internalToExternalCalls++ + return nil + } + if err := s.AddConversionFunc((*InternalSimple)(nil), (*ExternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertSimple(a.(*InternalSimple), b.(*ExternalSimple), scope) + }); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + simple := &InternalSimple{TestString: "foo"} + external := &ExternalSimple{} + if err := s.Convert(simple, external, nil); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := simple.TestString, external.TestString; e != a { + t.Errorf("Expected %v, got %v", e, a) + } + + // Verify that our conversion handler got called. + if e, a := 1, internalToExternalCalls; e != a { + t.Errorf("Expected %v, got %v", e, a) + } +} + +func TestRegisterValidate(t *testing.T) { + invalidValue := field.Invalid(field.NewPath("testString"), "", "Invalid value").WithOrigin("invalid-value") + invalidLength := field.Invalid(field.NewPath("testString"), "", "Invalid length").WithOrigin("invalid-length") + invalidStatusErr := field.Invalid(field.NewPath("testString"), "", "Invalid condition").WithOrigin("invalid-condition") + invalidIfOptionErr := field.Invalid(field.NewPath("testString"), "", "Invalid when option is set").WithOrigin("invalid-when-option-set") + + testCases := []struct { + name string + object runtime.Object + oldObject runtime.Object + subresource []string + options map[string]bool + expected field.ErrorList + }{ + { + name: "single error", + object: &TestType1{}, + options: map[string]bool{"option1": false}, + expected: field.ErrorList{invalidValue}, + }, + { + name: "multiple errors", + object: &TestType2{}, + expected: field.ErrorList{invalidValue, invalidLength}, + }, + { + name: "update error", + object: &TestType2{}, + oldObject: &TestType2{}, + expected: field.ErrorList{invalidLength}, + }, + { + name: "options error", + object: &TestType1{}, + options: map[string]bool{"option1": true}, + expected: field.ErrorList{invalidIfOptionErr}, + }, + { + name: "subresource error", + object: &TestType1{}, + subresource: []string{"status"}, + options: map[string]bool{"option1": false}, + expected: field.ErrorList{invalidStatusErr}, + }, + } + + s := runtime.NewScheme() + ctx := context.Background() + + // register multiple types for testing to ensure registration is working as expected + s.AddValidationFunc(&TestType1{}, func(ctx context.Context, op operation.Operation, object, oldObject interface{}) field.ErrorList { + if enabled, exists := op.HasOption("option1"); enabled || !exists { + return field.ErrorList{invalidIfOptionErr} + } + if slices.Equal(op.Request.Subresources, []string{"status"}) { + return field.ErrorList{invalidStatusErr} + } + return field.ErrorList{invalidValue} + }) + + s.AddValidationFunc(&TestType2{}, func(ctx context.Context, op operation.Operation, object, oldObject interface{}) field.ErrorList { + if oldObject != nil { + return field.ErrorList{invalidLength} + } + return field.ErrorList{invalidValue, invalidLength} + }) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var results field.ErrorList + if tc.oldObject == nil { + results = s.Validate(ctx, tc.options, tc.object, tc.subresource...) + } else { + results = s.ValidateUpdate(ctx, tc.options, tc.object, tc.oldObject, tc.subresource...) + } + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin() + matcher.Test(t, tc.expected, results) + }) + } +} + +type TestType1 struct { + Version string `json:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty"` + TestString string `json:"testString"` +} + +func (TestType1) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +func (TestType1) DeepCopyObject() runtime.Object { return nil } + +type TestType2 struct { + Version string `json:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty"` + TestString string `json:"testString"` +} + +func (TestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +func (TestType2) DeepCopyObject() runtime.Object { return nil } + +func TestToOpenAPIDefinitionName(t *testing.T) { + testCases := []struct { + name string + registerGvk *schema.GroupVersionKind // defaults to gvk unless set + registerObj runtime.Object + gvk schema.GroupVersionKind + out string + wantErr error + }{ + { + name: "unstructured type", + registerObj: &unstructured.Unstructured{}, + gvk: schema.GroupVersionKind{Group: "stable.example.com", Version: "v1", Kind: "CronTab"}, + out: "com.example.stable.v1.CronTab", + }, + { + name: "unregistered type: empty group", + registerObj: &unstructured.Unstructured{}, + gvk: schema.GroupVersionKind{Version: "v1", Kind: "CronTab"}, + wantErr: fmt.Errorf("unable to convert GroupVersionKind with empty fields to unstructured type to an OpenAPI definition name: %v", schema.GroupVersionKind{Version: "v1", Kind: "CronTab"}), + }, + { + name: "unregistered type: empty version", + registerObj: &unstructured.Unstructured{}, + registerGvk: &schema.GroupVersionKind{Group: "stable.example.com", Version: "v1", Kind: "CronTab"}, + gvk: schema.GroupVersionKind{Group: "stable.example.com", Kind: "CronTab"}, + wantErr: fmt.Errorf("version is required on all types: %v", schema.GroupVersionKind{Group: "stable.example.com", Kind: "CronTab"}), + }, + { + name: "unregistered type: empty kind", + registerObj: &unstructured.Unstructured{}, + gvk: schema.GroupVersionKind{Group: "stable.example.com", Version: "v1"}, + wantErr: fmt.Errorf("unable to convert GroupVersionKind with empty fields to unstructured type to an OpenAPI definition name: %v", schema.GroupVersionKind{Group: "stable.example.com", Version: "v1"}), + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + if test.registerGvk == nil { + test.registerGvk = &test.gvk + } + + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName(*test.registerGvk, test.registerObj) + utilruntime.Must(runtimetesting.RegisterConversions(scheme)) + + out, err := scheme.ToOpenAPIDefinitionName(test.gvk) + if test.wantErr != nil { + if err == nil || err.Error() != test.wantErr.Error() { + t.Errorf("expected error: %v but got %v", test.wantErr, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if out != test.out { + t.Errorf("expected %s, got %s", test.out, out) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor.go new file mode 100644 index 0000000000..e5730e3c50 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor.go @@ -0,0 +1,402 @@ +/* +Copyright 2024 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 cbor + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "io" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" + util "k8s.io/apimachinery/pkg/util/runtime" + + "github.com/fxamacker/cbor/v2" +) + +type metaFactory interface { + // Interpret should return the version and kind of the wire-format of the object. + Interpret(data []byte) (*schema.GroupVersionKind, error) +} + +type defaultMetaFactory struct{} + +func (mf *defaultMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) { + var tm metav1.TypeMeta + // The input is expected to include additional map keys besides apiVersion and kind, so use + // lax mode for decoding into TypeMeta. + if err := modes.DecodeLax.Unmarshal(data, &tm); err != nil { + return nil, fmt.Errorf("unable to determine group/version/kind: %w", err) + } + actual := tm.GetObjectKind().GroupVersionKind() + return &actual, nil +} + +type Serializer interface { + runtime.Serializer + runtime.NondeterministicEncoder + recognizer.RecognizingDecoder + + // NewSerializer returns a value of this interface type rather than exporting the serializer + // type and returning one of those because the zero value of serializer isn't ready to + // use. Users aren't intended to implement cbor.Serializer themselves, and this unexported + // interface method is here to prevent that (https://go.dev/blog/module-compatibility). + private() +} + +var _ Serializer = &serializer{} + +type options struct { + strict bool + transcode bool + streamingCollectionsEncoding bool +} + +type Option func(*options) + +// Strict configures a serializer to return a strict decoding error when it encounters map keys that +// do not correspond to a field in the target object of a decode operation. This option is disabled +// by default. +func Strict(s bool) Option { + return func(opts *options) { + opts.strict = s + } +} + +// Transcode configures a serializer to transcode the "raw" bytes of a decoded runtime.RawExtension +// or metav1.FieldsV1 object to JSON. This is enabled by default to support existing programs that +// depend on the assumption that objects of either type contain valid JSON. +func Transcode(s bool) Option { + return func(opts *options) { + opts.transcode = s + } +} + +// StreamingCollectionsEncoding is used for testing purposes only. +func StreamingCollectionsEncoding(s bool) Option { + return func(opts *options) { + opts.streamingCollectionsEncoding = s + } +} + +type serializer struct { + metaFactory metaFactory + creater runtime.ObjectCreater + typer runtime.ObjectTyper + options options +} + +func (serializer) private() {} + +// NewSerializer creates and returns a serializer configured with the provided options. The default +// options are equivalent to explicitly passing Strict(false) and Transcode(true). +func NewSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper, options ...Option) Serializer { + return newSerializer(&defaultMetaFactory{}, creater, typer, options...) +} + +func newSerializer(metaFactory metaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, options ...Option) *serializer { + s := &serializer{ + metaFactory: metaFactory, + creater: creater, + typer: typer, + } + s.options.transcode = true + s.options.streamingCollectionsEncoding = true + for _, o := range options { + o(&s.options) + } + return s +} + +func (s *serializer) Identifier() runtime.Identifier { + return "cbor" +} + +// Encode writes a CBOR representation of the given object. +// +// Because the CBOR data item written by a call to Encode is always enclosed in the "self-described +// CBOR" tag, its encoded form always has the prefix 0xd9d9f7. This prefix is suitable for use as a +// "magic number" for distinguishing encoded CBOR from other protocols. +// +// The default serialization behavior for any given object replicates the behavior of the JSON +// serializer as far as it is necessary to allow the CBOR serializer to be used as a drop-in +// replacement for the JSON serializer, with limited exceptions. For example, the distinction +// between integers and floating-point numbers is preserved in CBOR due to its distinct +// representations for each type. +// +// Objects implementing runtime.Unstructured will have their unstructured content encoded rather +// than following the default behavior for their dynamic type. +func (s *serializer) Encode(obj runtime.Object, w io.Writer) error { + return s.encode(modes.Encode, obj, w) +} + +func (s *serializer) EncodeNondeterministic(obj runtime.Object, w io.Writer) error { + return s.encode(modes.EncodeNondeterministic, obj, w) +} + +func (s *serializer) encode(mode modes.EncMode, obj runtime.Object, w io.Writer) error { + if _, err := w.Write(selfDescribedCBOR); err != nil { + return err + } + + if s.options.streamingCollectionsEncoding { + ok, err := streamEncodeCollections(obj, w, mode) + if err != nil { + return err + } + if ok { + return nil + } + } + + var v interface{} = obj + if u, ok := obj.(runtime.Unstructured); ok { + v = u.UnstructuredContent() + } + + return mode.MarshalTo(v, w) +} + +// gvkWithDefaults returns group kind and version defaulting from provided default +func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind { + if len(actual.Kind) == 0 { + actual.Kind = defaultGVK.Kind + } + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = defaultGVK.Group + actual.Version = defaultGVK.Version + } + if len(actual.Version) == 0 && actual.Group == defaultGVK.Group { + actual.Version = defaultGVK.Version + } + return actual +} + +// diagnose returns the diagnostic encoding of a well-formed CBOR data item. +func diagnose(data []byte) string { + diag, err := modes.Diagnostic.Diagnose(data) + if err != nil { + // Since the input must already be well-formed CBOR, converting it to diagnostic + // notation should not fail. + util.HandleError(err) + + return hex.EncodeToString(data) + } + return diag +} + +// unmarshal unmarshals CBOR data from the provided byte slice into a Go object. If the decoder is +// configured to report strict errors, the first error return value may be a non-nil strict decoding +// error. If the last error return value is non-nil, then the unmarshal failed entirely and the +// state of the destination object should not be relied on. +func (s *serializer) unmarshal(data []byte, into interface{}) (strict, lax error) { + if u, ok := into.(runtime.Unstructured); ok { + var content map[string]interface{} + defer func() { + switch u := u.(type) { + case *unstructured.UnstructuredList: + // UnstructuredList's implementation of SetUnstructuredContent + // produces different objects than those produced by a decode using + // UnstructuredJSONScheme: + // + // 1. SetUnstructuredContent retains the "items" key in the list's + // Object field. It is omitted from Object when decoding with + // UnstructuredJSONScheme. + // 2. SetUnstructuredContent does not populate "apiVersion" and + // "kind" on each entry of its Items + // field. UnstructuredJSONScheme does, inferring the singular + // Kind from the list Kind. + // 3. SetUnstructuredContent ignores entries of "items" that are + // not JSON objects or are objects without + // "kind". UnstructuredJSONScheme returns an error in either + // case. + // + // UnstructuredJSONScheme's behavior is replicated here. + var items []interface{} + if uncast, present := content["items"]; present { + var cast bool + items, cast = uncast.([]interface{}) + if !cast { + strict, lax = nil, fmt.Errorf("items field of UnstructuredList must be encoded as an array or null if present") + return + } + } + apiVersion, _ := content["apiVersion"].(string) + kind, _ := content["kind"].(string) + kind = strings.TrimSuffix(kind, "List") + var unstructureds []unstructured.Unstructured + if len(items) > 0 { + unstructureds = make([]unstructured.Unstructured, len(items)) + } + for i := range items { + object, cast := items[i].(map[string]interface{}) + if !cast { + strict, lax = nil, fmt.Errorf("elements of the items field of UnstructuredList must be encoded as a map") + return + } + + // As in UnstructuredJSONScheme, only set the heuristic + // singular GVK when both "apiVersion" and "kind" are either + // missing, non-string, or empty. + object["apiVersion"], _ = object["apiVersion"].(string) + object["kind"], _ = object["kind"].(string) + if object["apiVersion"] == "" && object["kind"] == "" { + object["apiVersion"] = apiVersion + object["kind"] = kind + } + + if object["kind"] == "" { + strict, lax = nil, runtime.NewMissingKindErr(diagnose(data)) + return + } + if object["apiVersion"] == "" { + strict, lax = nil, runtime.NewMissingVersionErr(diagnose(data)) + return + } + + unstructureds[i].Object = object + } + delete(content, "items") + u.Object = content + u.Items = unstructureds + default: + u.SetUnstructuredContent(content) + } + }() + into = &content + } + + if !s.options.strict { + return nil, modes.DecodeLax.Unmarshal(data, into) + } + + err := modes.Decode.Unmarshal(data, into) + // TODO: UnknownFieldError is ambiguous. It only provides the index of the first problematic + // map entry encountered and does not indicate which map the index refers to. + var unknownField *cbor.UnknownFieldError + if errors.As(err, &unknownField) { + // Unlike JSON, there are no strict errors in CBOR for duplicate map keys. CBOR maps + // with duplicate keys are considered invalid according to the spec and are rejected + // entirely. + return runtime.NewStrictDecodingError([]error{unknownField}), modes.DecodeLax.Unmarshal(data, into) + } + return nil, err +} + +func (s *serializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + // A preliminary pass over the input to obtain the actual GVK is redundant on a successful + // decode into Unstructured. + if _, ok := into.(runtime.Unstructured); ok { + if _, unmarshalErr := s.unmarshal(data, into); unmarshalErr != nil { + actual, interpretErr := s.metaFactory.Interpret(data) + if interpretErr != nil { + return nil, nil, interpretErr + } + + if gvk != nil { + *actual = gvkWithDefaults(*actual, *gvk) + } + + return nil, actual, unmarshalErr + } + + actual := into.GetObjectKind().GroupVersionKind() + if len(actual.Kind) == 0 { + return nil, &actual, runtime.NewMissingKindErr(diagnose(data)) + } + if len(actual.Version) == 0 { + return nil, &actual, runtime.NewMissingVersionErr(diagnose(data)) + } + + return into, &actual, nil + } + + actual, err := s.metaFactory.Interpret(data) + if err != nil { + return nil, nil, err + } + + if gvk != nil { + *actual = gvkWithDefaults(*actual, *gvk) + } + + if into != nil { + types, _, err := s.typer.ObjectKinds(into) + if err != nil { + return nil, actual, err + } + *actual = gvkWithDefaults(*actual, types[0]) + } + + if len(actual.Kind) == 0 { + return nil, actual, runtime.NewMissingKindErr(diagnose(data)) + } + if len(actual.Version) == 0 { + return nil, actual, runtime.NewMissingVersionErr(diagnose(data)) + } + + obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into) + if err != nil { + return nil, actual, err + } + + strict, err := s.unmarshal(data, obj) + if err != nil { + return nil, actual, err + } + + if s.options.transcode { + if err := transcodeRawTypes(obj); err != nil { + return nil, actual, err + } + } + + return obj, actual, strict +} + +// selfDescribedCBOR is the CBOR encoding of the head of tag number 55799. This tag, specified in +// RFC 8949 Section 3.4.6 "Self-Described CBOR", encloses all output from the encoder, has no +// special semantics, and is used as a magic number to recognize CBOR-encoded data items. +// +// See https://www.rfc-editor.org/rfc/rfc8949.html#name-self-described-cbor. +var selfDescribedCBOR = []byte{0xd9, 0xd9, 0xf7} + +func (s *serializer) RecognizesData(data []byte) (ok, unknown bool, err error) { + return bytes.HasPrefix(data, selfDescribedCBOR), false, nil +} + +// NewSerializerInfo returns a default SerializerInfo for CBOR using the given creater and typer. +func NewSerializerInfo(creater runtime.ObjectCreater, typer runtime.ObjectTyper) runtime.SerializerInfo { + return runtime.SerializerInfo{ + MediaType: "application/cbor", + MediaTypeType: "application", + MediaTypeSubType: "cbor", + Serializer: NewSerializer(creater, typer), + StrictSerializer: NewSerializer(creater, typer, Strict(true)), + StreamSerializer: &runtime.StreamSerializerInfo{ + Framer: NewFramer(), + Serializer: NewSerializer(creater, typer, Transcode(false)), + }, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor_test.go new file mode 100644 index 0000000000..dcb59130c0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/cbor_test.go @@ -0,0 +1,874 @@ +/* +Copyright 2024 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. +*/ + +// The tests in this package focus on the correctness of its implementation of +// runtime.Serializer. The specific behavior of marshaling Go values to CBOR bytes and back is +// tested in the ./internal/modes package, which is used both by the Serializer implementation and +// the package-scoped Marshal/Unmarshal functions in the ./direct package. +package cbor + +import ( + "bytes" + "encoding/hex" + "errors" + "io" + "reflect" + "strconv" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" + + "github.com/google/go-cmp/cmp" +) + +func TestRecognizesData(t *testing.T) { + for _, tc := range []struct { + in []byte + recognizes bool + }{ + { + in: nil, + recognizes: false, + }, + { + in: []byte{}, + recognizes: false, + }, + { + in: []byte{0xd9}, + recognizes: false, + }, + { + in: []byte{0xd9, 0xd9}, + recognizes: false, + }, + { + in: []byte{0xd9, 0xd9, 0xf7}, + recognizes: true, + }, + { + in: []byte{0xff, 0xff, 0xff}, + recognizes: false, + }, + { + in: []byte{0xd9, 0xd9, 0xf7, 0x01, 0x02, 0x03}, + recognizes: true, + }, + { + in: []byte{0xff, 0xff, 0xff, 0x01, 0x02, 0x03}, + recognizes: false, + }, + } { + t.Run(hex.EncodeToString(tc.in), func(t *testing.T) { + s := NewSerializer(nil, nil) + recognizes, unknown, err := s.RecognizesData(tc.in) + if recognizes != tc.recognizes { + t.Errorf("expected recognized to be %t, got %t", tc.recognizes, recognizes) + } + if unknown { + t.Error("expected unknown to be false, got true") + } + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }) + } +} + +type stubWriter struct { + n int + err error +} + +func (w stubWriter) Write([]byte) (int, error) { + return w.n, w.err +} + +// anyObject wraps arbitrary concrete values to be encoded or decoded. +type anyObject struct { + Value interface{} +} + +func (p anyObject) GetObjectKind() schema.ObjectKind { + return schema.EmptyObjectKind +} + +func (anyObject) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +func (p anyObject) MarshalCBOR() ([]byte, error) { + return modes.Encode.Marshal(p.Value) +} + +func (p *anyObject) UnmarshalCBOR(in []byte) error { + return modes.Decode.Unmarshal(in, &p.Value) +} + +type structWithRawFields struct { + FieldsV1 metav1.FieldsV1 `json:"f"` + FieldsV1Pointer *metav1.FieldsV1 `json:"fp"` + RawExtension runtime.RawExtension `json:"r"` + RawExtensionPointer *runtime.RawExtension `json:"rp"` +} + +func (structWithRawFields) GetObjectKind() schema.ObjectKind { + return schema.EmptyObjectKind +} + +func (structWithRawFields) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +type structWithEmbeddedMetas struct { + metav1.TypeMeta `json:""` + metav1.ObjectMeta `json:"metadata,omitempty"` +} + +func (structWithEmbeddedMetas) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +func TestEncode(t *testing.T) { + for _, tc := range []struct { + name string + in runtime.Object + assertOnWriter func() (io.Writer, func(*testing.T)) + assertOnError func(*testing.T, error) + }{ + { + name: "io error writing self described cbor tag", + assertOnWriter: func() (io.Writer, func(*testing.T)) { + return stubWriter{err: io.ErrShortWrite}, func(*testing.T) {} + }, + assertOnError: func(t *testing.T, err error) { + if !errors.Is(err, io.ErrShortWrite) { + t.Errorf("expected io.ErrShortWrite, got: %v", err) + } + }, + }, + { + name: "output enclosed by self-described CBOR tag", + in: anyObject{}, + assertOnWriter: func() (io.Writer, func(*testing.T)) { + var b bytes.Buffer + return &b, func(t *testing.T) { + if !bytes.HasPrefix(b.Bytes(), []byte{0xd9, 0xd9, 0xf7}) { + t.Errorf("expected output to have prefix 0xd9d9f7: 0x%x", b.Bytes()) + } + } + }, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "unstructuredlist", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "apiVersion": "v", + "kind": "kList", + }, + Items: []unstructured.Unstructured{ + {Object: map[string]interface{}{"foo": int64(1)}}, + {Object: map[string]interface{}{"foo": int64(2)}}, + }, + }, + assertOnWriter: func() (io.Writer, func(t *testing.T)) { + var b bytes.Buffer + return &b, func(t *testing.T) { + // {'kind': 'kList', 'items': [{'foo': 1}, {'foo': 2}], 'apiVersion': 'v'} + if diff := cmp.Diff(b.Bytes(), []byte("\xd9\xd9\xf7\xa3\x44kind\x45kList\x45items\x82\xa1\x43foo\x01\xa1\x43foo\x02\x4aapiVersion\x41v")); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + } + }, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "text marshaler", + in: &textMarshalerObject{}, + assertOnWriter: func() (io.Writer, func(*testing.T)) { + var b bytes.Buffer + return &b, func(t *testing.T) { + if diff := cmp.Diff(b.Bytes(), []byte{0xd9, 0xd9, 0xf7, 0x64, 't', 'e', 's', 't'}); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + } + }, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "text marshaler within unstructured content", + in: &unstructured.Unstructured{ + Object: map[string]interface{}{"": textMarshalerObject{}}, + }, + assertOnWriter: func() (io.Writer, func(*testing.T)) { + var b bytes.Buffer + return &b, func(t *testing.T) { + if diff := cmp.Diff(b.Bytes(), []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x40, 0x64, 't', 'e', 's', 't'}); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + } + }, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + s := NewSerializer(nil, nil) + w, assertOnWriter := tc.assertOnWriter() + err := s.Encode(tc.in, w) + tc.assertOnError(t, err) + assertOnWriter(t) + }) + } +} + +func TestDecode(t *testing.T) { + for _, tc := range []struct { + name string + options []Option + data []byte + gvk *schema.GroupVersionKind + metaFactory metaFactory + typer runtime.ObjectTyper + creater runtime.ObjectCreater + into runtime.Object + expectedObj runtime.Object + expectedGVK *schema.GroupVersionKind + assertOnError func(*testing.T, error) + }{ + { + name: "self-described cbor tag accepted", + data: []byte("\xd9\xd9\xf7\xa3\x4aapiVersion\x41v\x44kind\x41k\x48metadata\xa1\x44name\x43foo"), // 55799({'apiVersion': 'v', 'kind': 'k', 'metadata': {'name': 'foo'}}) + gvk: &schema.GroupVersionKind{}, + metaFactory: &defaultMetaFactory{}, + typer: stubTyper{gvks: []schema.GroupVersionKind{{Version: "v", Kind: "k"}}}, + into: &metav1.PartialObjectMetadata{}, + expectedObj: &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{APIVersion: "v", Kind: "k"}, + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + }, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "k"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "error determining gvk", + metaFactory: stubMetaFactory{err: errors.New("test")}, + assertOnError: func(t *testing.T, err error) { + if err == nil || err.Error() != "test" { + t.Errorf("expected error \"test\", got: %v", err) + } + }, + }, + { + name: "typer does not recognize into", + gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + typer: notRegisteredTyper{}, + into: &anyObject{}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsNotRegisteredError(err) { + t.Errorf("expected NotRegisteredError, got: %v", err) + } + }, + }, + { + name: "gvk from type of into", + data: []byte{0xf6}, + gvk: &schema.GroupVersionKind{}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}}, + into: &anyObject{}, + expectedObj: &anyObject{}, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "raw types transcoded", + data: []byte{0xa4, 0x41, 'f', 0xa1, 0x41, 'a', 0x01, 0x42, 'f', 'p', 0xa1, 0x41, 'z', 0x02, 0x41, 'r', 0xa1, 0x41, 'b', 0x03, 0x42, 'r', 'p', 0xa1, 0x41, 'y', 0x04}, + gvk: &schema.GroupVersionKind{}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}}, + into: &structWithRawFields{}, + expectedObj: &structWithRawFields{ + FieldsV1: *metav1.NewFieldsV1(`{"a":1}`), + FieldsV1Pointer: metav1.NewFieldsV1(`{"z":2}`), + RawExtension: runtime.RawExtension{Raw: []byte(`{"b":3}`)}, + RawExtensionPointer: &runtime.RawExtension{Raw: []byte(`{"y":4}`)}, + }, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "raw types not transcoded", + options: []Option{Transcode(false)}, + data: []byte{0xa4, 0x41, 'f', 0xa1, 0x41, 'a', 0x01, 0x42, 'f', 'p', 0xa1, 0x41, 'z', 0x02, 0x41, 'r', 0xa1, 0x41, 'b', 0x03, 0x42, 'r', 'p', 0xa1, 0x41, 'y', 0x04}, + gvk: &schema.GroupVersionKind{}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}}, + into: &structWithRawFields{}, + expectedObj: &structWithRawFields{ + FieldsV1: *metav1.NewFieldsV1(string([]byte{0xa1, 0x41, 'a', 0x01})), + FieldsV1Pointer: metav1.NewFieldsV1(string([]byte{0xa1, 0x41, 'z', 0x02})), + // RawExtension's UnmarshalCBOR ensures the self-described CBOR tag + // is present in the result so that there is never any ambiguity in + // distinguishing CBOR from JSON or Protobuf. It is unnecessary for + // FieldsV1 to do the same because the initial byte is always + // sufficient to distinguish a valid JSON-encoded FieldsV1 from a + // valid CBOR-encoded FieldsV1. + RawExtension: runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x41, 'b', 0x03}}, + RawExtensionPointer: &runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0xa1, 0x41, 'y', 0x04}}, + }, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "object with embedded typemeta and objectmeta", + data: []byte("\xa2\x48metadata\xa1\x44name\x43foo\x44spec\xa0"), // {"metadata": {"name": "foo"}} + gvk: &schema.GroupVersionKind{}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}}, + into: &structWithEmbeddedMetas{}, + expectedObj: &structWithEmbeddedMetas{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + }, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "strict mode strict error", + options: []Option{Strict(true)}, + data: []byte{0xa1, 0x61, 'z', 0x01}, // {'z': 1} + gvk: &schema.GroupVersionKind{}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}}, + into: &metav1.PartialObjectMetadata{}, + expectedObj: &metav1.PartialObjectMetadata{}, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsStrictDecodingError(err) { + t.Errorf("expected StrictDecodingError, got: %v", err) + } + }, + }, + { + name: "no strict mode no strict error", + data: []byte{0xa1, 0x61, 'z', 0x01}, // {'z': 1} + gvk: &schema.GroupVersionKind{}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + typer: stubTyper{gvks: []schema.GroupVersionKind{{Group: "x", Version: "y", Kind: "z"}}}, + into: &metav1.PartialObjectMetadata{}, + expectedObj: &metav1.PartialObjectMetadata{}, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "unknown error from typer on into", + gvk: &schema.GroupVersionKind{}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + typer: stubTyper{err: errors.New("test")}, + into: &anyObject{}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{}, + assertOnError: func(t *testing.T, err error) { + if err == nil || err.Error() != "test" { + t.Errorf("expected error \"test\", got: %v", err) + } + }, + }, + { + name: "missing kind", + gvk: &schema.GroupVersionKind{Version: "v"}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Version: "v"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsMissingKind(err) { + t.Errorf("expected MissingKind, got: %v", err) + } + }, + }, + { + name: "missing version", + gvk: &schema.GroupVersionKind{Kind: "k"}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Kind: "k"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsMissingVersion(err) { + t.Errorf("expected MissingVersion, got: %v", err) + } + }, + }, + { + name: "creater error", + gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + creater: stubCreater{err: errors.New("test")}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if err == nil || err.Error() != "test" { + t.Errorf("expected error \"test\", got: %v", err) + } + }, + }, + { + name: "unmarshal error", + data: nil, // EOF + gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + creater: stubCreater{obj: &anyObject{}}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if !errors.Is(err, io.EOF) { + t.Errorf("expected EOF, got: %v", err) + } + }, + }, + { + name: "strict mode unmarshal error", + options: []Option{Strict(true)}, + data: nil, // EOF + gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + creater: stubCreater{obj: &anyObject{}}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if !errors.Is(err, io.EOF) { + t.Errorf("expected EOF, got: %v", err) + } + }, + }, + { + name: "into unstructured unmarshal error", + data: nil, // EOF + gvk: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + metaFactory: stubMetaFactory{gvk: &schema.GroupVersionKind{}}, + into: &unstructured.Unstructured{}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Group: "x", Version: "y", Kind: "z"}, + assertOnError: func(t *testing.T, err error) { + if !errors.Is(err, io.EOF) { + t.Errorf("expected EOF, got: %v", err) + } + }, + }, + { + name: "into unstructured missing kind", + data: []byte("\xa1\x6aapiVersion\x61v"), + into: &unstructured.Unstructured{}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Version: "v"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsMissingKind(err) { + t.Errorf("expected MissingKind, got: %v", err) + } + }, + }, + { + name: "into unstructured missing version", + data: []byte("\xa1\x64kind\x61k"), + into: &unstructured.Unstructured{}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Kind: "k"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsMissingVersion(err) { + t.Errorf("expected MissingVersion, got: %v", err) + } + }, + }, + { + name: "into unstructured", + data: []byte("\xa2\x6aapiVersion\x61v\x64kind\x61k"), + into: &unstructured.Unstructured{}, + expectedObj: &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "v", + "kind": "k", + }}, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "k"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "using unstructured creater", + data: []byte("\xa2\x6aapiVersion\x61v\x64kind\x61k"), + metaFactory: &defaultMetaFactory{}, + creater: stubCreater{obj: &unstructured.Unstructured{}}, + expectedObj: &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "v", + "kind": "k", + }}, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "k"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "into unstructuredlist missing kind", + data: []byte("\xa1\x6aapiVersion\x61v"), + into: &unstructured.UnstructuredList{}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Version: "v"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsMissingKind(err) { + t.Errorf("expected MissingKind, got: %v", err) + } + }, + }, + { + name: "into unstructuredlist missing version", + data: []byte("\xa1\x64kind\x65kList"), + into: &unstructured.UnstructuredList{}, + expectedObj: nil, + expectedGVK: &schema.GroupVersionKind{Kind: "kList"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsMissingVersion(err) { + t.Errorf("expected MissingVersion, got: %v", err) + } + }, + }, + { + name: "into unstructuredlist empty", + data: []byte("\xa2\x6aapiVersion\x61v\x64kind\x65kList"), + into: &unstructured.UnstructuredList{}, + expectedObj: &unstructured.UnstructuredList{Object: map[string]interface{}{ + "apiVersion": "v", + "kind": "kList", + }}, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "kList"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "into unstructuredlist nonempty", + data: []byte("\xa3\x6aapiVersion\x61v\x64kind\x65kList\x65items\x82\xa1\x63foo\x01\xa1\x63foo\x02"), // {"apiVersion": "v", "kind": "kList", "items": [{"foo": 1}, {"foo": 2}]} + into: &unstructured.UnstructuredList{}, + expectedObj: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "apiVersion": "v", + "kind": "kList", + }, + Items: []unstructured.Unstructured{ + {Object: map[string]interface{}{"apiVersion": "v", "kind": "k", "foo": int64(1)}}, + {Object: map[string]interface{}{"apiVersion": "v", "kind": "k", "foo": int64(2)}}, + }, + }, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "kList"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "into unstructuredlist item gvk present", + data: []byte("\xa3\x6aapiVersion\x61v\x64kind\x65kList\x65items\x81\xa2\x6aapiVersion\x62vv\x64kind\x62kk"), // {"apiVersion": "v", "kind": "kList", "items": [{"apiVersion": "vv", "kind": "kk"}]} + into: &unstructured.UnstructuredList{}, + expectedObj: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "apiVersion": "v", + "kind": "kList", + }, + Items: []unstructured.Unstructured{ + {Object: map[string]interface{}{"apiVersion": "vv", "kind": "kk"}}, + }, + }, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "kList"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + { + name: "into unstructuredlist item missing kind", + data: []byte("\xa3\x6aapiVersion\x61v\x64kind\x65kList\x65items\x81\xa1\x6aapiVersion\x62vv"), // {"apiVersion": "v", "kind": "kList", "items": [{"apiVersion": "vv"}]} + metaFactory: &defaultMetaFactory{}, + into: &unstructured.UnstructuredList{}, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "kList"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsMissingKind(err) { + t.Errorf("expected MissingVersion, got: %v", err) + } + }, + }, + { + name: "into unstructuredlist item missing version", + data: []byte("\xa3\x6aapiVersion\x61v\x64kind\x65kList\x65items\x81\xa1\x64kind\x62kk"), // {"apiVersion": "v", "kind": "kList", "items": [{"kind": "kk"}]} + metaFactory: &defaultMetaFactory{}, + into: &unstructured.UnstructuredList{}, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "kList"}, + assertOnError: func(t *testing.T, err error) { + if !runtime.IsMissingVersion(err) { + t.Errorf("expected MissingVersion, got: %v", err) + } + }, + }, + { + name: "using unstructuredlist creater", + data: []byte("\xa2\x6aapiVersion\x61v\x64kind\x65kList"), + metaFactory: &defaultMetaFactory{}, + creater: stubCreater{obj: &unstructured.UnstructuredList{}}, + expectedObj: &unstructured.UnstructuredList{Object: map[string]interface{}{ + "apiVersion": "v", + "kind": "kList", + }}, + expectedGVK: &schema.GroupVersionKind{Version: "v", Kind: "kList"}, + assertOnError: func(t *testing.T, err error) { + if err != nil { + t.Errorf("expected nil error, got: %v", err) + } + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + s := newSerializer(tc.metaFactory, tc.creater, tc.typer, tc.options...) + + actualObj, actualGVK, err := s.Decode(tc.data, tc.gvk, tc.into) + tc.assertOnError(t, err) + + if !reflect.DeepEqual(tc.expectedObj, actualObj) { + t.Error(cmp.Diff(tc.expectedObj, actualObj)) + } + + if diff := cmp.Diff(tc.expectedGVK, actualGVK); diff != "" { + t.Error(diff) + } + }) + } +} + +type textMarshalerObject struct{} + +func (p textMarshalerObject) GetObjectKind() schema.ObjectKind { + return schema.EmptyObjectKind +} + +func (textMarshalerObject) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +func (textMarshalerObject) MarshalText() ([]byte, error) { + return []byte("test"), nil +} + +func TestMetaFactoryInterpret(t *testing.T) { + mf := &defaultMetaFactory{} + _, err := mf.Interpret(nil) + if err == nil { + t.Error("expected non-nil error") + } + gvk, err := mf.Interpret([]byte("\xa2\x6aapiVersion\x63a/b\x64kind\x61c")) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if diff := cmp.Diff(&schema.GroupVersionKind{Group: "a", Version: "b", Kind: "c"}, gvk); diff != "" { + t.Error(diff) + } +} + +type stubTyper struct { + gvks []schema.GroupVersionKind + unversioned bool + err error +} + +func (t stubTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { + return t.gvks, t.unversioned, t.err +} + +func (stubTyper) Recognizes(schema.GroupVersionKind) bool { + return false +} + +type stubCreater struct { + obj runtime.Object + err error +} + +func (c stubCreater) New(gvk schema.GroupVersionKind) (runtime.Object, error) { + return c.obj, c.err +} + +type notRegisteredTyper struct{} + +func (notRegisteredTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { + return nil, false, runtime.NewNotRegisteredErrForType("test", reflect.TypeOf(obj)) +} + +func (notRegisteredTyper) Recognizes(schema.GroupVersionKind) bool { + return false +} + +type stubMetaFactory struct { + gvk *schema.GroupVersionKind + err error +} + +func (mf stubMetaFactory) Interpret([]byte) (*schema.GroupVersionKind, error) { + return mf.gvk, mf.err +} + +type oneMapField struct { + metav1.TypeMeta `json:""` + Map map[string]interface{} `json:"map"` +} + +func (o oneMapField) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +func (o oneMapField) GetObjectKind() schema.ObjectKind { + panic("unimplemented") +} + +type eightStringFields struct { + metav1.TypeMeta `json:""` + A string `json:"1"` + B string `json:"2"` + C string `json:"3"` + D string `json:"4"` + E string `json:"5"` + F string `json:"6"` + G string `json:"7"` + H string `json:"8"` +} + +func (o eightStringFields) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +func (o eightStringFields) GetObjectKind() schema.ObjectKind { + panic("unimplemented") +} + +// TestEncodeNondeterministic tests that repeated encodings of multi-field structs and maps do not +// encode to precisely the same bytes when repeatedly encoded with EncodeNondeterministic. When +// using EncodeNondeterministic, the order of items in CBOR maps should be intentionally shuffled to +// prevent applications from inadvertently depending on encoding determinism. All permutations do +// not necessarily have equal probability. +func TestEncodeNondeterministic(t *testing.T) { + for _, tc := range []struct { + name string + input runtime.Object + }{ + { + name: "map", + input: func() runtime.Object { + m := map[string]interface{}{} + for i := 1; i <= 8; i++ { + m[strconv.Itoa(i)] = strconv.Itoa(i) + + } + return oneMapField{Map: m} + }(), + }, + { + name: "struct", + input: eightStringFields{ + TypeMeta: metav1.TypeMeta{}, + A: "1", + B: "2", + C: "3", + D: "4", + E: "5", + F: "6", + G: "7", + H: "8", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var b bytes.Buffer + e := NewSerializer(nil, nil) + + if err := e.EncodeNondeterministic(tc.input, &b); err != nil { + t.Fatal(err) + } + first := b.String() + + const Trials = 128 + for trial := 0; trial < Trials; trial++ { + b.Reset() + if err := e.EncodeNondeterministic(tc.input, &b); err != nil { + t.Fatal(err) + } + + if !bytes.Equal([]byte(first), b.Bytes()) { + return + } + } + t.Fatalf("nondeterministic encode produced the same bytes on %d consecutive calls: %s", Trials, first) + }) + } + +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/collections.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/collections.go new file mode 100644 index 0000000000..5aa0fb7ca4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/collections.go @@ -0,0 +1,328 @@ +/* +Copyright 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 cbor + +import ( + "encoding/json" + "fmt" + "io" + "maps" + "math/rand" + "reflect" + "slices" + "sort" + + "k8s.io/apimachinery/pkg/conversion" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" + + "github.com/fxamacker/cbor/v2" +) + +func streamEncodeCollections(obj runtime.Object, w io.Writer, mode modes.EncMode) (bool, error) { + list, ok := obj.(*unstructured.UnstructuredList) + if ok { + return true, streamingEncodeUnstructuredList(w, list, mode) + } + if _, ok := obj.(cbor.Marshaler); ok { + return false, nil + } + if _, ok := obj.(json.Marshaler); ok { + return false, nil + } + typeMeta, listMeta, items, err := getListMeta(obj) + if err == nil { + return true, streamingEncodeList(w, typeMeta, listMeta, items, mode) + } + return false, nil +} + +// getListMeta implements list extraction logic for cbor stream serialization. +func getListMeta(list runtime.Object) (metav1.TypeMeta, metav1.ListMeta, []interface{}, error) { + listValue, err := conversion.EnforcePtr(list) + if err != nil { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, err + } + listType := listValue.Type() + if listType.NumField() != 3 { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected ListType to have 3 fields") + } + // The streaming encoder reproduces the field names and layout implied by the + // json struct tags (kind, apiVersion, metadata, items). The CBOR encoder, + // however, gives a "cbor" struct tag precedence over "json", so a cbor tag on + // any of these fields could rename a key, change its options (e.g. keyasint), + // or un-inline the embedded TypeMeta -- diverging from the streamed output. + // Refuse to stream such a type and fall back to the general encoder, which + // honors the cbor tag correctly. + for i := 0; i < listType.NumField(); i++ { + if _, ok := listType.Field(i).Tag.Lookup("cbor"); ok { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected list field %d to have no cbor tag", i) + } + } + // TypeMeta + typeMeta, ok := listValue.Field(0).Interface().(metav1.TypeMeta) + if !ok { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected TypeMeta field to have TypeMeta type") + } + if !listType.Field(0).Anonymous { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected TypeMeta json field tag to be embedded`) + } + if jsonTag, jsonTagExists := listType.Field(0).Tag.Lookup("json"); !jsonTagExists { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected TypeMeta json field tag`) + } else if jsonTag != "" && jsonTag != ",inline" { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected TypeMeta json field tag to be "" or ",inline"`) + } + // ListMeta + listMeta, ok := listValue.Field(1).Interface().(metav1.ListMeta) + if !ok { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected ListMeta field to have ListMeta type") + } + if listType.Field(1).Tag.Get("json") != "metadata,omitempty" { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected ListMeta json field tag to be "metadata,omitempty"`) + } + // Items + if listType.Field(2).Tag.Get("json") != "items" { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected Items json field tag to be "items"`) + } + items, err := getListItems(listValue.Field(2)) + if err != nil { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, err + } + return typeMeta, listMeta, items, nil +} + +// getListItems returns the elements of a list's Items field as addressable +// values. Marshaling each element directly (rather than routing through +// meta.ExtractList) preserves the element's own type and custom marshaler, so +// that e.g. a metav1.List whose Items are runtime.RawExtension holding CBOR +// bytes are encoded verbatim by runtime.RawExtension.MarshalCBOR instead of +// being flattened into a runtime.Unknown (which has no MarshalCBOR and would be +// misencoded as a struct or fail JSON transcoding). +// +// A nil Items slice returns a nil result, distinct from an empty non-nil slice, +// so the encoder can emit CBOR null rather than an empty array. +func getListItems(itemsValue reflect.Value) ([]interface{}, error) { + if itemsValue.Kind() != reflect.Slice { + return nil, fmt.Errorf("expected Items field to be a slice, got %s", itemsValue.Kind()) + } + if itemsValue.IsNil() { + return nil, nil + } + items := make([]interface{}, itemsValue.Len()) + for i := range items { + items[i] = itemsValue.Index(i).Addr().Interface() + } + return items, nil +} + +type cborMapEntry struct { + key string + write func() error +} + +func streamingEncodeList(w io.Writer, typeMeta metav1.TypeMeta, listMeta metav1.ListMeta, items []interface{}, mode modes.EncMode) error { + var entries []cborMapEntry + + if typeMeta.Kind != "" { + entries = append(entries, cborMapEntry{ + key: "kind", + write: func() error { + return encodeKeyValuePair(w, "kind", typeMeta.Kind, mode) + }, + }) + } + entries = append(entries, cborMapEntry{ + key: "items", + write: func() error { + if err := mode.MarshalTo("items", w); err != nil { + return err + } + if items == nil { + _, err := w.Write([]byte{0xf6}) // CBOR null + return err + } + if err := writeArrayHead(w, len(items)); err != nil { + return err + } + for _, item := range items { + if err := mode.MarshalTo(item, w); err != nil { + return err + } + } + return nil + }, + }) + entries = append(entries, cborMapEntry{ + key: "metadata", + write: func() error { + return encodeKeyValuePair(w, "metadata", listMeta, mode) + }, + }) + if typeMeta.APIVersion != "" { + entries = append(entries, cborMapEntry{ + key: "apiVersion", + write: func() error { + return encodeKeyValuePair(w, "apiVersion", typeMeta.APIVersion, mode) + }, + }) + } + + // entries is built in a fixed order (kind, items, metadata, apiVersion). + // Unlike streamingEncodeUnstructuredList, whose keys come in Go's randomized + // map-iteration order, there is no inherent randomness here, so for + // nondeterministic modes (SortFastShuffle) we rotate the encoding for-loop + // by a random initial offset. + start := 0 + if !mode.IsDeterministic() && len(entries) > 0 { + start = rand.Intn(len(entries)) + } + + if err := writeMapHead(w, len(entries)); err != nil { + return err + } + + for i := 0; i < len(entries); i++ { + entry := entries[(start+i)%len(entries)] + if err := entry.write(); err != nil { + return err + } + } + return nil +} + +func streamingEncodeUnstructuredList(w io.Writer, list *unstructured.UnstructuredList, mode modes.EncMode) error { + keys := slices.Collect(maps.Keys(list.Object)) + if _, exists := list.Object["items"]; !exists { + keys = append(keys, "items") + } + // keys starts in Go's randomized map-iteration order. For deterministic + // modes (SortBytewiseLexical) we sort it: shorter lengths come first, then + // lexicographic by content. For nondeterministic modes (SortFastShuffle) we + // leave the map-iteration order as-is, which already varies from call to + // call (analogous to what SortFastShuffle does for structs), so no explicit + // shuffling is needed. + if mode.IsDeterministic() { + sort.Slice(keys, func(i, j int) bool { + if len(keys[i]) != len(keys[j]) { + return len(keys[i]) < len(keys[j]) + } + return keys[i] < keys[j] + }) + } + + if err := writeMapHead(w, len(keys)); err != nil { + return err + } + + for _, key := range keys { + if err := mode.MarshalTo(key, w); err != nil { + return err + } + if key == "items" { + if err := writeArrayHead(w, len(list.Items)); err != nil { + return err + } + for _, item := range list.Items { + if err := mode.MarshalTo(item.Object, w); err != nil { + return err + } + } + } else { + if err := mode.MarshalTo(list.Object[key], w); err != nil { + return err + } + } + } + return nil +} + +func encodeKeyValuePair(w io.Writer, key string, value interface{}, mode modes.EncMode) error { + if err := mode.MarshalTo(key, w); err != nil { + return err + } + if err := mode.MarshalTo(value, w); err != nil { + return err + } + return nil +} + +// CBOR major type prefix bytes (the type in the high 3 bits, additional info +// zeroed), following RFC 8949 Section 3.1. +const ( + cborTypeArray byte = 0x80 // major type 4 + cborTypeMap byte = 0xa0 // major type 5 +) + +// writeMapHead writes a CBOR map header for a map with n entries. +func writeMapHead(w io.Writer, n int) error { + return writeCollectionHead(w, cborTypeMap, int64(n)) +} + +// writeArrayHead writes a CBOR array header for an array with n elements. +func writeArrayHead(w io.Writer, n int) error { + return writeCollectionHead(w, cborTypeArray, int64(n)) +} + +// writeCollectionHead writes a CBOR collection (array or map) header encoding +// the number of elements n, following RFC 8949 Section 3 additional info rules: +// +// - base: the prefix byte for the collection type. +// For maps: cborTypeMap (0xa0), for arrays: cborTypeArray (0x80). +// +// The extended form prefixes are derived from base using bitwise OR: +// - base|24: 1-byte length follows (additional info 24) +// - base|25: 2-byte length follows (additional info 25) +// - base|26: 4-byte length follows (additional info 26) +// - base|27: 8-byte length follows (additional info 27) +// +// Encoding table (map example, base=0xa0): +// +// n <= 23: 1 byte — 0xa0|n +// n <= 0xFF: 2 bytes — 0xb8 (0xa0|24), n +// n <= 0xFFFF: 3 bytes — 0xb9 (0xa0|25), n>>8, n +// n <= 0xFFFFFFFF: 5 bytes — 0xba (0xa0|26), n>>24..n +// n > 0xFFFFFFFF: 9 bytes — 0xbb (0xa0|27), n>>56..n +func writeCollectionHead(w io.Writer, base byte, n int64) error { + switch { + case n <= 23: + // Additional info 0–23: length is encoded directly in the low 5 bits. + _, err := w.Write([]byte{base + byte(n)}) + return err + case n <= 0xFF: + // Additional info 24: one additional byte carries the length. + _, err := w.Write([]byte{base | 24, byte(n)}) + return err + case n <= 0xFFFF: + // Additional info 25: two additional bytes carry the length (big-endian). + _, err := w.Write([]byte{base | 25, byte(n >> 8), byte(n)}) + return err + case n <= 0xFFFFFFFF: + // Additional info 26: four additional bytes carry the length (big-endian). + _, err := w.Write([]byte{base | 26, byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)}) + return err + default: + // Additional info 27: eight additional bytes carry the length (big-endian). + _, err := w.Write([]byte{ + base | 27, byte(n >> 56), byte(n >> 48), byte(n >> 40), byte(n >> 32), byte(n >> 24), byte(n >> 16), + byte(n >> 8), byte(n), + }) + return err + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/collections_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/collections_test.go new file mode 100644 index 0000000000..9b205f58e8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/collections_test.go @@ -0,0 +1,1316 @@ +/* +Copyright 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 cbor + +import ( + "bytes" + "fmt" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + + "sigs.k8s.io/randfill" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" +) + +// TestStreamingCollectionsEncoding verifies that streaming encoding produces +// output identical to normal non-streaming encoding, and that the streaming +// encoder actually uses multiple Write calls (not just buffering everything). +func TestStreamingCollectionsEncoding(t *testing.T) { + var buf writeCountingBuffer + var remainingItems int64 = 1 + for _, tc := range []struct { + name string + in runtime.Object + cannotStream bool + }{ + // Preserving the distinction between integers and floating-point numbers + { + name: "Struct with floats", + in: &StructWithFloatsList{ + Items: []StructWithFloats{ + { + Int: 1, + Float32: float32(1), + Float64: 1.1, + }, + }, + }, + }, + { + name: "Unstructured object float", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "int": 1, + "float32": float32(1), + "float64": 1.1, + }, + }, + }, + { + name: "Unstructured items float", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "int": 1, + "float32": float32(1), + "float64": 1.1, + }, + }, + }, + }, + }, + // Handling structs with duplicate field names (JSON tag names) without producing duplicate keys in the encoded output + { + name: "StructWithDuplicatedTags", + in: &StructWithDuplicatedTagsList{ + Items: []StructWithDuplicatedTags{ + { + Key1: "key1", + Key2: "key2", + }, + }, + }, + }, + // Encoding Go strings containing invalid UTF-8 sequences without error + { + name: "UnstructuredList object invalid UTF-8", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "key": "\x80", // first byte is a continuation byte + }, + }, + }, + { + name: "UnstructuredList items invalid UTF-8", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "key": "\x80", + }, + }, + }, + }, + }, + // Preserving the distinction between absent, present-but-null, and present-and-empty states for slices and maps + { + name: "CarpList items nil", + in: &testapigroupv1.CarpList{ + Items: nil, + }, + }, + { + name: "CarpList slice nil", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Status: testapigroupv1.CarpStatus{ + Conditions: nil, + }, + }, + }, + }, + }, + { + name: "CarpList map nil", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Spec: testapigroupv1.CarpSpec{ + NodeSelector: nil, + }, + }, + }, + }, + }, + { + name: "UnstructuredList items nil", + in: &unstructured.UnstructuredList{ + Items: nil, + }, + }, + { + name: "UnstructuredList items slice nil", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "slice": ([]string)(nil), + }, + }, + }, + }, + }, + { + name: "UnstructuredList items map nil", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "map": (map[string]string)(nil), + }, + }, + }, + }, + }, + { + name: "UnstructuredList object nil", + in: &unstructured.UnstructuredList{ + Object: nil, + }, + }, + { + name: "UnstructuredList object slice nil", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "slice": ([]string)(nil), + }, + }, + }, + { + name: "UnstructuredList object map nil", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "map": (map[string]string)(nil), + }, + }, + }, + { + name: "CarpList items empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{}, + }, + }, + { + name: "CarpList slice empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Status: testapigroupv1.CarpStatus{ + Conditions: []testapigroupv1.CarpCondition{}, + }, + }, + }, + }, + }, + { + name: "CarpList map empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Spec: testapigroupv1.CarpSpec{ + NodeSelector: map[string]string{}, + }, + }, + }, + }, + }, + { + name: "UnstructuredList items empty", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{}, + }, + }, + { + name: "UnstructuredList items slice empty", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "slice": []string{}, + }, + }, + }, + }, + }, + { + name: "UnstructuredList items map empty", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "map": map[string]string{}, + }, + }, + }, + }, + }, + { + name: "UnstructuredList object empty", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{}, + }, + }, + { + name: "UnstructuredList object slice empty", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "slice": []string{}, + }, + }, + }, + { + name: "UnstructuredList object map empty", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "map": map[string]string{}, + }, + }, + }, + // Handling structs implementing json.Marshaler method + { + name: "List with json.Marshaler cannot be streamed", + in: &ListWithMarshalJSONList{}, + cannotStream: true, + }, + { + name: "Struct with json.Marshaler", + in: &StructWithMarshalJSONList{ + Items: []StructWithMarshalJSON{ + {}, + }, + }, + }, + // Handling structs implementing cbor.Marshaler but NOT json.Marshaler + { + name: "List with cbor.Marshaler cannot be streamed", + in: &ListWithMarshalCBORList{}, + cannotStream: true, + }, + { + name: "Struct with cbor.Marshaler", + in: &StructWithMarshalCBORList{ + Items: []StructWithMarshalCBOR{ + {}, + }, + }, + }, + // Handling structs implementing both json.Marshaler and cbor.Marshaler + { + name: "List with json.Marshaler and cbor.Marshaler cannot be streamed", + in: &ListWithBothMarshalersList{}, + cannotStream: true, + }, + { + name: "Struct with json.Marshaler and cbor.Marshaler", + in: &StructWithBothMarshalersList{ + Items: []StructWithBothMarshalers{ + {}, + }, + }, + }, + // Handling raw bytes. + { + name: "Struct with raw bytes", + in: &StructWithRawBytesList{ + Items: []StructWithRawBytes{ + { + Slice: []byte{0x01, 0x02, 0x03}, + Array: [3]byte{0x01, 0x02, 0x03}, + }, + }, + }, + }, + { + name: "UnstructuredList object raw bytes", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "slice": []byte{0x01, 0x02, 0x03}, + "array": [3]byte{0x01, 0x02, 0x03}, + }, + }, + }, + { + name: "UnstructuredList items raw bytes", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "slice": []byte{0x01, 0x02, 0x03}, + "array": [3]byte{0x01, 0x02, 0x03}, + }, + }, + }, + }, + }, + // Other scenarios: + { + name: "List just kind", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + }, + }, + }, + { + name: "List just apiVersion", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + }, + }, + }, + { + name: "List no elements", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + }, + Items: []testapigroupv1.Carp{}, + }, + }, + { + name: "List one element with continue", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + Continue: "abc", + RemainingItemCount: &remainingItems, + }, + Items: []testapigroupv1.Carp{ + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "default", + }, + }, + }, + }, + }, + { + name: "List two elements", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + }, + Items: []testapigroupv1.Carp{ + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "default", + }, + }, + { + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod2", + Namespace: "default2", + }, + }, + }, + }, + }, + { + name: "List with extra field cannot be streamed", + in: &ListWithAdditionalFields{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + }, + Items: []testapigroupv1.Carp{}, + }, + cannotStream: true, + }, + { + // A cbor struct tag takes precedence over json in the CBOR encoder, so + // the streaming encoder (which follows the json tags) must not claim + // this type; it falls back to the general encoder, which renames the + // items key to "elements". + name: "List with cbor tag cannot be streamed", + in: &ListWithCBORTagList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + Items: []testapigroupv1.Carp{ + {ObjectMeta: metav1.ObjectMeta{Name: "pod"}}, + }, + }, + cannotStream: true, + }, + { + name: "Not a collection cannot be streamed", + in: &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + }, + cannotStream: true, + }, + { + name: "UnstructuredList empty", + in: &unstructured.UnstructuredList{}, + }, + { + name: "UnstructuredList just kind", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"kind": "List"}, + }, + }, + { + name: "UnstructuredList just apiVersion", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"apiVersion": "v1"}, + }, + }, + { + name: "UnstructuredList no elements", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{"resourceVersion": "2345"}, + }, + Items: []unstructured.Unstructured{}, + }, + }, + { + name: "UnstructuredList one element with continue", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{ + "resourceVersion": "2345", + "continue": "abc", + "remainingItemCount": "1", + }, + }, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "pod", + "namespace": "default", + }, + }, + }, + }, + }, + }, + { + name: "UnstructuredList two elements", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{ + "resourceVersion": "2345", + }, + }, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "pod", + "namespace": "default", + }, + }, + }, + { + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "pod2", + "namespace": "default", + }, + }, + }, + }, + }, + }, + { + name: "UnstructuredList conflict on items", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "items": []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "name": "pod", + }, + }, + }, + }, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "name": "pod2", + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + buf.Reset() + s := NewSerializer(nil, nil, StreamingCollectionsEncoding(true)) + if err := s.Encode(tc.in, &buf); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var normalBuf bytes.Buffer + normalS := NewSerializer(nil, nil, StreamingCollectionsEncoding(false)) + if err := normalS.Encode(tc.in, &normalBuf); err != nil { + t.Fatalf("normal encode error: %v", err) + } + + if diff := cmp.Diff(buf.Bytes(), normalBuf.Bytes()); diff != "" { + t.Errorf("streaming and normal encoding differ:\n%s", diff) + } + + expectStreaming := !tc.cannotStream + if expectStreaming && buf.writeCount <= 2 { + t.Errorf("expected streaming but Write was called only: %d", buf.writeCount) + } + if !expectStreaming && buf.writeCount > 2 { + t.Errorf("expected non-streaming but Write was called more than once: %d", buf.writeCount) + } + }) + } +} + +type StructWithFloatsList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithFloats `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (l *StructWithFloatsList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithFloats struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Int int + Float32 float32 + Float64 float64 +} + +func (s *StructWithFloats) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithDuplicatedTagsList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithDuplicatedTags `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (l *StructWithDuplicatedTagsList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithDuplicatedTags struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Key1 string `json:"key"` + Key2 string `json:"key"` //nolint:govet +} + +func (s *StructWithDuplicatedTags) DeepCopyObject() runtime.Object { + return nil +} + +type ListWithMarshalJSONList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []string `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (l *ListWithMarshalJSONList) DeepCopyObject() runtime.Object { + return nil +} + +func (l *ListWithMarshalJSONList) MarshalJSON() ([]byte, error) { + return []byte(`"marshallJSON"`), nil +} + +type StructWithMarshalJSONList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithMarshalJSON `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (s *StructWithMarshalJSONList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithMarshalJSON struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` +} + +func (l *StructWithMarshalJSON) DeepCopyObject() runtime.Object { + return nil +} + +func (l *StructWithMarshalJSON) MarshalJSON() ([]byte, error) { + return []byte(`"marshallJSON"`), nil +} + +type ListWithMarshalCBORList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []string `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (l *ListWithMarshalCBORList) DeepCopyObject() runtime.Object { + return nil +} + +func (l *ListWithMarshalCBORList) MarshalCBOR() ([]byte, error) { + return []byte("\x6bmarshalCBOR"), nil +} + +type StructWithMarshalCBORList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithMarshalCBOR `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (s *StructWithMarshalCBORList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithMarshalCBOR struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` +} + +func (l *StructWithMarshalCBOR) DeepCopyObject() runtime.Object { + return nil +} + +func (l *StructWithMarshalCBOR) MarshalCBOR() ([]byte, error) { + return []byte("\x6bmarshalCBOR"), nil +} + +type ListWithBothMarshalersList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []string `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (l *ListWithBothMarshalersList) DeepCopyObject() runtime.Object { + return nil +} + +func (l *ListWithBothMarshalersList) MarshalJSON() ([]byte, error) { + return []byte(`"marshalJSON"`), nil +} + +func (l *ListWithBothMarshalersList) MarshalCBOR() ([]byte, error) { + return []byte("\x6bmarshalCBOR"), nil +} + +type StructWithBothMarshalersList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithBothMarshalers `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (s *StructWithBothMarshalersList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithBothMarshalers struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` +} + +func (l *StructWithBothMarshalers) DeepCopyObject() runtime.Object { + return nil +} + +func (l *StructWithBothMarshalers) MarshalJSON() ([]byte, error) { + return []byte(`"marshalJSON"`), nil +} + +func (l *StructWithBothMarshalers) MarshalCBOR() ([]byte, error) { + return []byte("\x6bmarshalCBOR"), nil +} + +type StructWithRawBytesList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithRawBytes `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (s *StructWithRawBytesList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithRawBytes struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Slice []byte + Array [3]byte +} + +func (s *StructWithRawBytes) DeepCopyObject() runtime.Object { + return nil +} + +type ListWithAdditionalFields struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []testapigroupv1.Carp `json:"items" protobuf:"bytes,2,rep,name=items"` + AdditionalField int +} + +func (s *ListWithAdditionalFields) DeepCopyObject() runtime.Object { + return nil +} + +// ListWithCBORTagList has a valid json-tagged shape but a cbor struct tag on +// Items that renames its key. Because the CBOR encoder prefers the cbor tag over +// json, getListMeta must refuse to stream this type. +type ListWithCBORTagList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []testapigroupv1.Carp `json:"items" cbor:"elements" protobuf:"bytes,2,rep,name=items"` +} + +func (s *ListWithCBORTagList) DeepCopyObject() runtime.Object { + return nil +} + +type writeCountingBuffer struct { + writeCount int + bytes.Buffer +} + +func (b *writeCountingBuffer) Write(data []byte) (int, error) { + b.writeCount++ + return b.Buffer.Write(data) +} + +func (b *writeCountingBuffer) Reset() { + b.writeCount = 0 + b.Buffer.Reset() +} + +func TestFuzzCollectionsEncoding(t *testing.T) { + disableFuzzFieldsV1 := func(field *metav1.FieldsV1, c randfill.Continue) {} + fuzzUnstructuredList := func(list *unstructured.UnstructuredList, c randfill.Continue) { + list.Object = map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + c.String(0): c.String(0), + c.String(0): int64(c.Intn(1000000)), // Limit to int64 range + c.String(0): c.Bool(), + "metadata": map[string]interface{}{ + "resourceVersion": fmt.Sprintf("%d", c.Intn(1000000)), // String format + "continue": c.String(0), + "remainingItemCount": fmt.Sprintf("%d", c.Intn(1000000)), // String format + c.String(0): c.String(0), + }, + } + c.Fill(&list.Items) + } + fuzzMap := func(kvs map[string]interface{}, c randfill.Continue) { + kvs[c.String(0)] = c.Bool() + kvs[c.String(0)] = int64(c.Intn(1000000)) // Limit to int64 range + kvs[c.String(0)] = c.String(0) + } + f := randfill.New().Funcs(disableFuzzFieldsV1, fuzzUnstructuredList, fuzzMap) + streamingSerializer := NewSerializer(nil, nil) + normalSerializer := NewSerializer(nil, nil, StreamingCollectionsEncoding(false)) + + for _, tc := range []struct { + name string + newObj func() runtime.Object + }{ + {name: "CarpList", newObj: func() runtime.Object { return &testapigroupv1.CarpList{} }}, + {name: "UnstructuredList", newObj: func() runtime.Object { return &unstructured.UnstructuredList{} }}, + } { + t.Run(tc.name, func(t *testing.T) { + var streamingBuf writeCountingBuffer + var normalBuf, ndetBuf bytes.Buffer + for i := range 1000 { + obj := tc.newObj() + f.Fill(obj) + + // Non-streaming, deterministic encode as the reference for all comparisons. + normalBuf.Reset() + if err := normalSerializer.Encode(obj, &normalBuf); err != nil { + t.Fatalf("trial %d: normal encode error: %v", i, err) + } + + // Streaming deterministic encode must match the reference byte-for-byte. + streamingBuf.Reset() + if err := streamingSerializer.Encode(obj, &streamingBuf); err != nil { + t.Fatalf("trial %d: streaming encode error: %v", i, err) + } + if diff := cmp.Diff(normalBuf.Bytes(), streamingBuf.Bytes()); diff != "" { + t.Logf("normal: %x", normalBuf.Bytes()) + t.Logf("streaming: %x", streamingBuf.Bytes()) + t.Fatalf("trial %d: streaming and non-streaming differ:\n%s", i, diff) + } + if streamingBuf.writeCount <= 2 { + t.Errorf("trial %d: expected streaming encoding to use more than 2 writes, got %d", i, streamingBuf.writeCount) + } + + // Streaming nondeterministic encode must decode to the same value. + ndetBuf.Reset() + if err := streamingSerializer.EncodeNondeterministic(obj, &ndetBuf); err != nil { + t.Fatalf("trial %d: nondeterministic encode error: %v", i, err) + } + + var detObj, ndetObj interface{} + if err := modes.Decode.Unmarshal(normalBuf.Bytes(), &detObj); err != nil { + t.Fatalf("trial %d: decode deterministic: %v", i, err) + } + if err := modes.Decode.Unmarshal(ndetBuf.Bytes(), &ndetObj); err != nil { + t.Fatalf("trial %d: decode nondeterministic: %v", i, err) + } + if diff := cmp.Diff(detObj, ndetObj); diff != "" { + t.Errorf("trial %d: semantic mismatch between deterministic and nondeterministic:\n%s", i, diff) + } + + detTyped := reflect.New(reflect.TypeOf(obj).Elem()).Interface() + ndetTyped := reflect.New(reflect.TypeOf(obj).Elem()).Interface() + if err := modes.Decode.Unmarshal(normalBuf.Bytes(), detTyped); err != nil { + t.Fatalf("trial %d: decode deterministic into %T: %v", i, obj, err) + } + if err := modes.Decode.Unmarshal(ndetBuf.Bytes(), ndetTyped); err != nil { + t.Fatalf("trial %d: decode nondeterministic into %T: %v", i, obj, err) + } + if !apiequality.Semantic.DeepEqual(detTyped, ndetTyped) { + t.Errorf("trial %d: typed %T mismatch between deterministic and nondeterministic encodings", i, obj) + } + } + }) + } +} + +// mustCBORSelfDescribed encodes v to CBOR and prepends the self-described CBOR +// tag (0xd9d9f7), matching how runtime.RawExtension stores CBOR-content bytes. +func mustCBORSelfDescribed(t *testing.T, v interface{}) []byte { + t.Helper() + data, err := modes.Encode.Marshal(v) + if err != nil { + t.Fatalf("failed to marshal %#v to CBOR: %v", v, err) + } + return append([]byte{0xd9, 0xd9, 0xf7}, data...) +} + +// TestStreamEncodeListRawExtension is a regression test for streaming encoding of +// a metav1.List whose Items are runtime.RawExtension. Streaming encoding must be +// byte-identical to non-streaming encoding, and must round-trip. +// +// Previously the streaming path routed items through meta.ExtractList, which +// flattens a RawExtension holding raw bytes into a runtime.Unknown (dropping its +// content type). runtime.Unknown implements MarshalJSON but not MarshalCBOR, so +// CBOR-content bytes were either misencoded as a struct or, worse, failed while +// the encoder attempted to transcode them from JSON. The streaming path now +// marshals each RawExtension directly, honoring runtime.RawExtension.MarshalCBOR. +func TestStreamEncodeListRawExtension(t *testing.T) { + streamingSerializer := NewSerializer(nil, nil) + normalSerializer := NewSerializer(nil, nil, StreamingCollectionsEncoding(false)) + + for _, tc := range []struct { + name string + // nonEmpty indicates Items encodes as a non-empty array (should stream). + nonEmpty bool + // roundTrips indicates decoding the output reproduces the input exactly. + // Only holds for CBOR-content RawExtension: decoding normalizes JSON bytes + // and populated Objects into CBOR Raw bytes. + roundTrips bool + items []runtime.RawExtension + }{ + { + name: "RawExtension with CBOR bytes", + nonEmpty: true, + roundTrips: true, + items: []runtime.RawExtension{ + {Raw: mustCBORSelfDescribed(t, map[string]interface{}{"foo": "bar"})}, + {Raw: mustCBORSelfDescribed(t, map[string]interface{}{"baz": int64(1)})}, + }, + }, + { + name: "RawExtension with JSON bytes", + nonEmpty: true, + items: []runtime.RawExtension{ + {Raw: []byte(`{"foo":"bar"}`)}, + }, + }, + { + name: "RawExtension with Object", + nonEmpty: true, + items: []runtime.RawExtension{ + {Object: &testapigroupv1.Carp{ObjectMeta: metav1.ObjectMeta{Name: "carp"}}}, + }, + }, + { + name: "RawExtension items empty", + items: []runtime.RawExtension{}, + }, + { + name: "RawExtension items nil", + items: nil, + }, + } { + t.Run(tc.name, func(t *testing.T) { + in := &metav1.List{ + TypeMeta: metav1.TypeMeta{Kind: "List", APIVersion: "v1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "42"}, + Items: tc.items, + } + + var streamingBuf writeCountingBuffer + if err := streamingSerializer.Encode(in, &streamingBuf); err != nil { + t.Fatalf("streaming encode error: %v", err) + } + + var normalBuf bytes.Buffer + if err := normalSerializer.Encode(in, &normalBuf); err != nil { + t.Fatalf("normal encode error: %v", err) + } + + if diff := cmp.Diff(normalBuf.Bytes(), streamingBuf.Bytes()); diff != "" { + t.Logf("normal: %x", normalBuf.Bytes()) + t.Logf("streaming: %x", streamingBuf.Bytes()) + t.Errorf("streaming output differs from normal encoding:\n%s", diff) + } + + // Non-empty lists should exercise the streaming path (more than the + // map-head + items-key writes). + if tc.nonEmpty && streamingBuf.writeCount <= 2 { + t.Errorf("expected streaming encoding to use more than 2 writes, got %d", streamingBuf.writeCount) + } + + // Round-trip: decoding the streamed bytes must reproduce the input + // for CBOR-content items (see roundTrips doc above). + if tc.roundTrips { + out := &metav1.List{} + if err := modes.Decode.Unmarshal(streamingBuf.Bytes(), out); err != nil { + t.Fatalf("decode error: %v", err) + } + if !apiequality.Semantic.DeepEqual(in, out) { + t.Errorf("round-trip mismatch:\n%s", cmp.Diff(in, out)) + } + } + }) + } +} + +// TestStreamEncodeCollectionsDeterministic verifies that the streaming serializer +// produces output identical to the normal non-streaming serializer. +func TestStreamEncodeCollectionsDeterministic(t *testing.T) { + streamingSerializer := NewSerializer(nil, nil) + normalSerializer := NewSerializer(nil, nil, StreamingCollectionsEncoding(false)) + + for _, tc := range []struct { + name string + in runtime.Object + }{ + { + name: "CarpList with all top-level fields", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "CarpList", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "42", + }, + Items: []testapigroupv1.Carp{ + {ObjectMeta: metav1.ObjectMeta{Name: "a"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "b"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "c"}}, + }, + }, + }, + { + name: "UnstructuredList with all top-level fields", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + "metadata": map[string]interface{}{"resourceVersion": "42"}, + }, + Items: []unstructured.Unstructured{ + {Object: map[string]interface{}{"name": "a"}}, + {Object: map[string]interface{}{"name": "b"}}, + {Object: map[string]interface{}{"name": "b"}}, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + // Encode with streaming enabled. + var streamingBuf writeCountingBuffer + if err := streamingSerializer.Encode(tc.in, &streamingBuf); err != nil { + t.Fatalf("streaming encode error: %v", err) + } + + // Encode with normal non-streaming encoder. + var normalBuf bytes.Buffer + if err := normalSerializer.Encode(tc.in, &normalBuf); err != nil { + t.Fatalf("normal encode error: %v", err) + } + + // Output must be identical. + if diff := cmp.Diff(normalBuf.Bytes(), streamingBuf.Bytes()); diff != "" { + t.Logf("normal: %x", normalBuf.Bytes()) + t.Logf("streaming: %x", streamingBuf.Bytes()) + t.Errorf("streaming output differs from normal encoding:\n%s", diff) + } + + if streamingBuf.writeCount <= 2 { + t.Errorf("expected streaming encoding to use more than 2 writes, got %d", streamingBuf.writeCount) + } + }) + } +} + +// TestStreamEncodeCollectionsNondeterministic verifies that the streaming serializer's +// EncodeNondeterministic method: +// 1. Semantic correctness: the output decodes to the same object as deterministic encoding. +// 2. Non-idempotence: across multiple trials the key order is observed to vary +// (probabilistic; uses a multi-key object to make the probability of flake negligible). +func TestStreamEncodeCollectionsNondeterministic(t *testing.T) { + streamingSerializer := NewSerializer(nil, nil) + normalSerializer := NewSerializer(nil, nil, StreamingCollectionsEncoding(false)) + + // A list with kind+apiVersion+metadata+items = 4 keys. + // With SortFastShuffle the number of possible orderings is 4! = 24. + // Over 200 trials the probability of seeing only 1 unique ordering is (1/24)^199 ≈ 0. + const nTrials = 200 + + for _, tc := range []struct { + name string + in runtime.Object + }{ + { + name: "CarpList", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "CarpList", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "42", + }, + Items: []testapigroupv1.Carp{ + {ObjectMeta: metav1.ObjectMeta{Name: "a"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "b"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "c"}}, + }, + }, + }, + { + name: "UnstructuredList", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + "metadata": map[string]interface{}{"resourceVersion": "42"}, + }, + Items: []unstructured.Unstructured{ + {Object: map[string]interface{}{"name": "a"}}, + {Object: map[string]interface{}{"name": "b"}}, + {Object: map[string]interface{}{"name": "c"}}, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + // Encode once with non-streaming, deterministic mode as reference for semantic equality. + var detBuf bytes.Buffer + if err := normalSerializer.Encode(tc.in, &detBuf); err != nil { + t.Fatalf("deterministic encode error: %v", err) + } + var detObj map[string]interface{} + if err := modes.Decode.Unmarshal(detBuf.Bytes(), &detObj); err != nil { + t.Fatalf("decode deterministic output: %v", err) + } + detTyped := reflect.New(reflect.TypeOf(tc.in).Elem()).Interface() + if err := modes.Decode.Unmarshal(detBuf.Bytes(), detTyped); err != nil { + t.Fatalf("decode deterministic output into %T: %v", tc.in, err) + } + + // Run nTrials of nondeterministic encoding, stopping early once we + // observe a second distinct byte sequence. + var firstEncoding string + for i := range nTrials { + var buf writeCountingBuffer + if err := streamingSerializer.EncodeNondeterministic(tc.in, &buf); err != nil { + t.Fatalf("trial %d: nondeterministic encode error: %v", i, err) + } + payload := buf.Bytes() + + // Semantic correctness: decoded value must equal the deterministic reference. + var ndetObj map[string]interface{} + if err := modes.Decode.Unmarshal(payload, &ndetObj); err != nil { + t.Fatalf("trial %d: decode nondeterministic output: %v", i, err) + } + if diff := cmp.Diff(detObj, ndetObj); diff != "" { + t.Errorf("trial %d: semantic mismatch between deterministic and nondeterministic:\n%s", i, diff) + } + + ndetTyped := reflect.New(reflect.TypeOf(tc.in).Elem()).Interface() + if err := modes.Decode.Unmarshal(payload, ndetTyped); err != nil { + t.Fatalf("trial %d: decode nondeterministic output into %T: %v", i, tc.in, err) + } + if !apiequality.Semantic.DeepEqual(detTyped, ndetTyped) { + t.Errorf("trial %d: typed %T mismatch between deterministic and nondeterministic encodings", i, tc.in) + } + + if buf.writeCount <= 2 { + t.Errorf("trial %d: expected streaming encoding to use more than 2 writes, got %d", i, buf.writeCount) + } + + enc := string(payload) + if i == 0 { + firstEncoding = enc + } else if enc != firstEncoding { + return + } + } + + t.Errorf("nondeterministic encoding produced only 1 unique byte sequence over %d trials; expected varied output", nTrials) + }) + } +} + +// TestCollectionHeadMatchesLibrary verifies that writeArrayHead and writeMapHead +// produce byte-identical output to the fxamacker/cbor library's own head encoding +// at every length-encoding width boundary (and its neighbors) reachable by +// allocating a real collection. The library encodes an n-element array/map with a +// leading definite-length head, so our streamed head must equal that prefix. +// +// The reachable sizes exercise the 1-, 2-, 3-, and 5-byte argument forms (the last +// at n=65536). The 8-byte form (n > 2^32) cannot be produced by allocation and is +// covered against explicit RFC 8949 bytes by TestWriteCollectionHeadBoundaries. +func TestCollectionHeadMatchesLibrary(t *testing.T) { + // Width-class boundaries and neighbors: + // n <= 23 -> 1-byte head + // 24 <= n <= 255 -> 2-byte head + // 256 <= n <= 65535 -> 3-byte head + // 65536 <= n -> 5-byte head + sizes := []int{0, 1, 23, 24, 25, 255, 256, 257, 65535, 65536, 65537} + + assertPrefix := func(t *testing.T, kind string, n int, head, lib []byte) { + t.Helper() + end := min(len(head), len(lib)) + if !bytes.Equal(head, lib[:end]) { + t.Errorf("%s head mismatch for n=%d:\n ours: % x\n library: % x", kind, n, head, lib[:min(len(lib), len(head)+4)]) + } + } + + for _, n := range sizes { + t.Run(fmt.Sprintf("array/%d", n), func(t *testing.T) { + var head bytes.Buffer + if err := writeArrayHead(&head, n); err != nil { + t.Fatalf("writeArrayHead(%d): %v", n, err) + } + lib, err := modes.Encode.Marshal(make([]bool, n)) + if err != nil { + t.Fatalf("library marshal []bool len %d: %v", n, err) + } + assertPrefix(t, "array", n, head.Bytes(), lib) + }) + t.Run(fmt.Sprintf("map/%d", n), func(t *testing.T) { + var head bytes.Buffer + if err := writeMapHead(&head, n); err != nil { + t.Fatalf("writeMapHead(%d): %v", n, err) + } + m := make(map[int64]bool, n) + for i := range n { + m[int64(i)] = false + } + lib, err := modes.Encode.Marshal(m) + if err != nil { + t.Fatalf("library marshal map len %d: %v", n, err) + } + assertPrefix(t, "map", n, head.Bytes(), lib) + }) + } +} + +// TestWriteCollectionHeadBoundaries checks writeCollectionHead against explicit +// RFC 8949 Section 3 expected bytes at every additional-information width boundary +// and its neighbors, for both the array (0x80) and map (0xa0) major types. Unlike +// TestCollectionHeadMatchesLibrary, this calls writeCollectionHead directly, so it +// can cover the 8-byte argument form (n > 2^32) that no allocatable collection can +// reach. +func TestWriteCollectionHeadBoundaries(t *testing.T) { + for _, tc := range []struct { + n int64 + array []byte + mp []byte + }{ + {n: 0, array: []byte{0x80}, mp: []byte{0xa0}}, + {n: 1, array: []byte{0x81}, mp: []byte{0xa1}}, + {n: 23, array: []byte{0x97}, mp: []byte{0xb7}}, // max 1-byte + {n: 24, array: []byte{0x98, 0x18}, mp: []byte{0xb8, 0x18}}, // min 2-byte + {n: 255, array: []byte{0x98, 0xff}, mp: []byte{0xb8, 0xff}}, // max 2-byte + {n: 256, array: []byte{0x99, 0x01, 0x00}, mp: []byte{0xb9, 0x01, 0x00}}, // min 3-byte + {n: 65535, array: []byte{0x99, 0xff, 0xff}, mp: []byte{0xb9, 0xff, 0xff}}, // max 3-byte + {n: 65536, array: []byte{0x9a, 0x00, 0x01, 0x00, 0x00}, mp: []byte{0xba, 0x00, 0x01, 0x00, 0x00}}, // min 5-byte + {n: 4294967295, array: []byte{0x9a, 0xff, 0xff, 0xff, 0xff}, mp: []byte{0xba, 0xff, 0xff, 0xff, 0xff}}, // max 5-byte (2^32-1) + {n: 4294967296, array: []byte{0x9b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}, mp: []byte{0xbb, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}}, // min 9-byte (2^32) + {n: 1099511627775, array: []byte{0x9b, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, mp: []byte{0xbb, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}}, // 0xFFFFFFFFFF + } { + t.Run(fmt.Sprintf("array/%d", tc.n), func(t *testing.T) { + var buf bytes.Buffer + if err := writeCollectionHead(&buf, cborTypeArray, tc.n); err != nil { + t.Fatalf("writeCollectionHead: %v", err) + } + if !bytes.Equal(buf.Bytes(), tc.array) { + t.Errorf("n=%d: got % x, want % x", tc.n, buf.Bytes(), tc.array) + } + }) + t.Run(fmt.Sprintf("map/%d", tc.n), func(t *testing.T) { + var buf bytes.Buffer + if err := writeCollectionHead(&buf, cborTypeMap, tc.n); err != nil { + t.Fatalf("writeCollectionHead: %v", err) + } + if !bytes.Equal(buf.Bytes(), tc.mp) { + t.Errorf("n=%d: got % x, want % x", tc.n, buf.Bytes(), tc.mp) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct/direct.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct/direct.go new file mode 100644 index 0000000000..945dc47c14 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct/direct.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 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 direct provides functions for marshaling and unmarshaling between arbitrary Go values and +// CBOR data, with behavior that is compatible with that of the CBOR serializer. In particular, +// types that implement cbor.Marshaler and cbor.Unmarshaler should use these functions. +package direct + +import ( + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" +) + +// Marshal serializes a value to CBOR. If there is more than one way to encode the value, it will +// make the same choice as the CBOR implementation of runtime.Serializer. +func Marshal(src any) ([]byte, error) { + return modes.Encode.Marshal(src) +} + +// Unmarshal deserializes from CBOR into an addressable value. If there is more than one way to +// unmarshal a value, it will make the same choice as the CBOR implementation of runtime.Serializer. +func Unmarshal(src []byte, dst any) error { + return modes.Decode.Unmarshal(src, dst) +} + +// Diagnose accepts well-formed CBOR bytes and returns a string representing the same data item in +// human-readable diagnostic notation (RFC 8949 Section 8). The diagnostic notation is not meant to +// be parsed. +func Diagnose(src []byte) (string, error) { + return modes.Diagnostic.Diagnose(src) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/framer.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/framer.go new file mode 100644 index 0000000000..28a733c673 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/framer.go @@ -0,0 +1,90 @@ +/* +Copyright 2024 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 cbor + +import ( + "io" + + "k8s.io/apimachinery/pkg/runtime" + + "github.com/fxamacker/cbor/v2" +) + +// NewFramer returns a runtime.Framer based on RFC 8742 CBOR Sequences. Each frame contains exactly +// one encoded CBOR data item. +func NewFramer() runtime.Framer { + return framer{} +} + +var _ runtime.Framer = framer{} + +type framer struct{} + +func (framer) NewFrameReader(rc io.ReadCloser) io.ReadCloser { + return &frameReader{ + decoder: cbor.NewDecoder(rc), + closer: rc, + } +} + +func (framer) NewFrameWriter(w io.Writer) io.Writer { + // Each data item in a CBOR sequence is self-delimiting (like JSON objects). + return w +} + +type frameReader struct { + decoder *cbor.Decoder + closer io.Closer + + overflow []byte +} + +func (fr *frameReader) Read(dst []byte) (int, error) { + if len(fr.overflow) > 0 { + // We read a frame that was too large for the destination slice in a previous call + // to Read and have bytes left over. + n := copy(dst, fr.overflow) + if n < len(fr.overflow) { + fr.overflow = fr.overflow[n:] + return n, io.ErrShortBuffer + } + fr.overflow = nil + return n, nil + } + + // The Reader contract allows implementations to use all of dst[0:len(dst)] as scratch + // space, even if n < len(dst), but it does not allow implementations to use + // dst[len(dst):cap(dst)]. Slicing it up-front allows us to append to it without worrying + // about overwriting dst[len(dst):cap(dst)]. + m := cbor.RawMessage(dst[0:0:len(dst)]) + if err := fr.decoder.Decode(&m); err != nil { + return 0, err + } + + if len(m) > len(dst) { + // The frame was too big, m has a newly-allocated underlying array to accommodate + // it. + fr.overflow = m[len(dst):] + return copy(dst, m), io.ErrShortBuffer + } + + return len(m), nil +} + +func (fr *frameReader) Close() error { + return fr.closer.Close() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/framer_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/framer_test.go new file mode 100644 index 0000000000..05676a1a8d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/framer_test.go @@ -0,0 +1,147 @@ +/* +Copyright 2024 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 cbor_test + +import ( + "bytes" + "errors" + "io" + "testing" + + "k8s.io/apimachinery/pkg/runtime/serializer/cbor" + + "github.com/google/go-cmp/cmp" +) + +// TestFrameReaderReadError tests that the frame reader does not resume after encountering a +// well-formedness error in the input stream. According to RFC 8742 Section 2.8: "[...] if any data +// item in the sequence is not well formed, it is not possible to reliably decode the rest of the +// sequence." +func TestFrameReaderReadError(t *testing.T) { + input := []byte{ + 0xff, // ill-formed initial break + 0xa0, // followed by well-formed empty map + } + fr := cbor.NewFramer().NewFrameReader(io.NopCloser(bytes.NewReader(input))) + for i := 0; i < 3; i++ { + n, err := fr.Read(nil) + if err == nil || errors.Is(err, io.ErrShortBuffer) { + t.Fatalf("expected a non-nil error other than io.ErrShortBuffer, got: %v", err) + } + if n != 0 { + t.Fatalf("expected 0 bytes read on error, got %d", n) + } + } +} + +func TestFrameReaderRead(t *testing.T) { + type ChunkedFrame [][]byte + + for _, tc := range []struct { + Name string + Frames []ChunkedFrame + }{ + { + Name: "consecutive frames", + Frames: []ChunkedFrame{ + [][]byte{{0xa0}}, + [][]byte{{0xa0}}, + }, + }, + { + Name: "zero-length destination buffer", + Frames: []ChunkedFrame{ + [][]byte{{}, {0xa0}}, + }, + }, + { + Name: "overflow", + Frames: []ChunkedFrame{ + [][]byte{ + {0x43}, + {'x'}, + {'y', 'z'}, + }, + [][]byte{ + {0xa1, 0x43, 'f', 'o', 'o'}, + {'b'}, + {'a', 'r'}, + }, + }, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + var concatenation []byte + for _, f := range tc.Frames { + for _, c := range f { + concatenation = append(concatenation, c...) + } + } + + fr := cbor.NewFramer().NewFrameReader(io.NopCloser(bytes.NewReader(concatenation))) + + for _, frame := range tc.Frames { + var want, got []byte + for i, chunk := range frame { + dst := make([]byte, len(chunk), 2*len(chunk)) + for i := len(dst); i < cap(dst); i++ { + dst[:cap(dst)][i] = 0xff + } + n, err := fr.Read(dst) + if n != len(chunk) { + t.Errorf("expected %d bytes read, got %d", len(chunk), n) + } + if i == len(frame)-1 && err != nil { + t.Errorf("unexpected non-nil error on last read of frame: %v", err) + } else if i < len(frame)-1 && !errors.Is(err, io.ErrShortBuffer) { + t.Errorf("expected io.ErrShortBuffer on all but the last read of a frame, got: %v", err) + } + for i := len(dst); i < cap(dst); i++ { + if dst[:cap(dst)][i] != 0xff { + t.Errorf("read mutated underlying array beyond slice length: %#v", dst[len(dst):cap(dst)]) + break + } + } + want = append(want, chunk...) + got = append(got, dst...) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("reassembled frame differs:\n%s", diff) + } + } + }) + } +} + +type fakeReadCloser struct { + err error +} + +func (rc fakeReadCloser) Read(_ []byte) (int, error) { + return 0, nil +} + +func (rc fakeReadCloser) Close() error { + return rc.err +} + +func TestFrameReaderClose(t *testing.T) { + want := errors.New("test") + if got := cbor.NewFramer().NewFrameReader(fakeReadCloser{err: want}).Close(); !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/appendixa_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/appendixa_test.go new file mode 100644 index 0000000000..e434b666c8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/appendixa_test.go @@ -0,0 +1,601 @@ +/* +Copyright 2024 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 modes_test + +import ( + "encoding/hex" + "fmt" + "math" + "testing" + + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" + + "github.com/google/go-cmp/cmp" +) + +// TestAppendixA roundtrips the examples of encoded CBOR data items in RFC 8949 Appendix A. For +// completeness, all examples from the appendix are included, even those those that are rejected by +// this decoder or are re-encoded to a different sequence of CBOR bytes (with explanation). +func TestAppendixA(t *testing.T) { + hex := func(h string) []byte { + b, err := hex.DecodeString(h) + if err != nil { + t.Fatal(err) + } + return b + } + + eq := conversion.EqualitiesOrDie( + // NaN float64 values are always inequal and have multiple representations. RFC 8949 + // Section 4.2.2 recommends protocols not supporting NaN payloads or signaling NaNs + // choose a single representation for all NaN values. For the purposes of this test, + // all NaN representations are equivalent. + func(a float64, b float64) bool { + if math.IsNaN(a) && math.IsNaN(b) { + return true + } + return math.Float64bits(a) == math.Float64bits(b) + }, + ) + + const ( + reasonArrayFixedLength = "indefinite-length arrays are re-encoded with fixed length" + reasonByteString = "strings are encoded as the byte string major type" + reasonMapFixedLength = "indefinite-length maps are re-encoded with fixed length" + reasonMapSorted = "map entries are sorted" + reasonStringFixedLength = "indefinite-length strings are re-encoded with fixed length" + reasonTagIgnored = "unrecognized tag numbers are ignored" + reasonTimeToInterface = "times decode to interface{} as RFC3339 timestamps for JSON interoperability" + ) + + for _, tc := range []struct { + example []byte // example data item + decoded interface{} + reject string // reason the decoder rejects the example + encoded []byte // re-encoded object (only if different from example encoding) + reasons []string // reasons for re-encode difference + }{ + { + example: hex("00"), + decoded: int64(0), + }, + { + example: hex("01"), + decoded: int64(1), + }, + { + example: hex("0a"), + decoded: int64(10), + }, + { + example: hex("17"), + decoded: int64(23), + }, + { + example: hex("1818"), + decoded: int64(24), + }, + { + example: hex("1819"), + decoded: int64(25), + }, + { + example: hex("1864"), + decoded: int64(100), + }, + { + example: hex("1903e8"), + decoded: int64(1000), + }, + { + example: hex("1a000f4240"), + decoded: int64(1000000), + }, + { + example: hex("1b000000e8d4a51000"), + decoded: int64(1000000000000), + }, + { + example: hex("1bffffffffffffffff"), + reject: "2^64-1 overflows int64 and falling back to float64 (as with JSON) loses distinction between float and integer", + }, + { + example: hex("c249010000000000000000"), + reject: "decoding tagged positive bigint value to interface{} can't reproduce this value without losing distinction between float and integer", + }, + { + example: hex("3bffffffffffffffff"), + reject: "-2^64-1 overflows int64 and falling back to float64 (as with JSON) loses distinction between float and integer", + }, + { + example: hex("c349010000000000000000"), + reject: "-18446744073709551617 overflows int64 and falling back to float64 (as with JSON) loses distinction between float and integer", + }, + { + example: hex("20"), + decoded: int64(-1), + }, + { + example: hex("29"), + decoded: int64(-10), + }, + { + example: hex("3863"), + decoded: int64(-100), + }, + { + example: hex("3903e7"), + decoded: int64(-1000), + }, + { + example: hex("f90000"), + decoded: 0.0, + }, + { + example: hex("f98000"), + decoded: math.Copysign(0, -1), + }, + { + example: hex("f93c00"), + decoded: 1.0, + }, + { + example: hex("fb3ff199999999999a"), + decoded: 1.1, + }, + { + example: hex("f93e00"), + decoded: 1.5, + }, + { + example: hex("f97bff"), + decoded: 65504.0, + }, + { + example: hex("fa47c35000"), + decoded: 100000.0, + }, + { + example: hex("fa7f7fffff"), + decoded: 3.4028234663852886e+38, + }, + { + example: hex("fb7e37e43c8800759c"), + decoded: 1.0e+300, + }, + { + example: hex("f90001"), + decoded: 5.960464477539063e-8, + }, + { + example: hex("f90400"), + decoded: 0.00006103515625, + }, + { + example: hex("f9c400"), + decoded: -4.0, + }, + { + example: hex("fbc010666666666666"), + decoded: -4.1, + }, + { + example: hex("f97c00"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("f97e00"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("f9fc00"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("fa7f800000"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("fa7fc00000"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("faff800000"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("fb7ff0000000000000"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("fb7ff8000000000000"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("fbfff0000000000000"), + reject: "floating-point NaN and infinities are not accepted", + }, + { + example: hex("f4"), + decoded: false, + }, + { + example: hex("f5"), + decoded: true, + }, + { + example: hex("f6"), + decoded: nil, + }, + { + example: hex("f7"), + reject: "only simple values false, true, and null have a clear analog", + }, + { + example: hex("f0"), + reject: "only simple values false, true, and null have a clear analog", + }, + { + example: hex("f8ff"), + reject: "only simple values false, true, and null have a clear analog", + }, + { + example: hex("c074323031332d30332d32315432303a30343a30305a"), + decoded: "2013-03-21T20:04:00Z", + encoded: hex("54323031332d30332d32315432303a30343a30305a"), + reasons: []string{ + reasonByteString, + reasonTimeToInterface, + }, + }, + { + example: hex("c11a514b67b0"), + decoded: "2013-03-21T20:04:00Z", + encoded: hex("54323031332d30332d32315432303a30343a30305a"), + reasons: []string{ + reasonByteString, + reasonTimeToInterface, + }, + }, + { + example: hex("c1fb41d452d9ec200000"), + decoded: "2013-03-21T20:04:00.5Z", + encoded: hex("56323031332d30332d32315432303a30343a30302e355a"), + reasons: []string{ + reasonByteString, + reasonTimeToInterface, + }, + }, + { + example: hex("d74401020304"), // 23(h'01020304') + decoded: "01020304", + encoded: hex("483031303230333034"), // '01020304' + reasons: []string{ + "decoding a byte string enclosed in an expected later encoding tag into an interface{} value automatically converts to the specified encoding for JSON interoperability", + }, + }, + { + example: hex("d818456449455446"), + decoded: "dIETF", + encoded: hex("456449455446"), + reasons: []string{ + reasonTagIgnored, + }, + }, + { + example: hex("d82076687474703a2f2f7777772e6578616d706c652e636f6d"), + decoded: "http://www.example.com", + encoded: hex("56687474703a2f2f7777772e6578616d706c652e636f6d"), + reasons: []string{ + reasonByteString, + reasonTagIgnored, + }, + }, + { + example: hex("40"), + decoded: "", + }, + { + example: hex("4401020304"), + decoded: "\x01\x02\x03\x04", + }, + { + example: hex("60"), + decoded: "", + encoded: hex("40"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("6161"), + decoded: "a", + encoded: hex("4161"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("6449455446"), + decoded: "IETF", + encoded: hex("4449455446"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("62225c"), + decoded: "\"\\", + encoded: hex("42225c"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("62c3bc"), + decoded: "ü", + encoded: hex("42c3bc"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("63e6b0b4"), + decoded: "水", + encoded: hex("43e6b0b4"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("64f0908591"), + decoded: "𐅑", + encoded: hex("44f0908591"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("80"), + decoded: []interface{}{}, + }, + { + example: hex("83010203"), + decoded: []interface{}{int64(1), int64(2), int64(3)}, + }, + { + example: hex("8301820203820405"), + decoded: []interface{}{int64(1), []interface{}{int64(2), int64(3)}, []interface{}{int64(4), int64(5)}}, + }, + { + example: hex("98190102030405060708090a0b0c0d0e0f101112131415161718181819"), + decoded: []interface{}{int64(1), int64(2), int64(3), int64(4), int64(5), int64(6), int64(7), int64(8), int64(9), int64(10), int64(11), int64(12), int64(13), int64(14), int64(15), int64(16), int64(17), int64(18), int64(19), int64(20), int64(21), int64(22), int64(23), int64(24), int64(25)}, + }, + { + example: hex("a0"), + decoded: map[string]interface{}{}, + }, + { + example: hex("a201020304"), + reject: "integer map keys don't correspond with field names or unstructured keys", + }, + { + example: hex("a26161016162820203"), + decoded: map[string]interface{}{ + "a": int64(1), + "b": []interface{}{int64(2), int64(3)}, + }, + encoded: hex("a24161014162820203"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("826161a161626163"), + decoded: []interface{}{ + "a", + map[string]interface{}{"b": "c"}, + }, + encoded: hex("824161a141624163"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("a56161614161626142616361436164614461656145"), + decoded: map[string]interface{}{ + "a": "A", + "b": "B", + "c": "C", + "d": "D", + "e": "E", + }, + encoded: hex("a54161414141624142416341434164414441654145"), + reasons: []string{ + reasonByteString, + }, + }, + { + example: hex("5f42010243030405ff"), + decoded: "\x01\x02\x03\x04\x05", + encoded: hex("450102030405"), + reasons: []string{ + reasonStringFixedLength, + }, + }, + { + example: hex("7f657374726561646d696e67ff"), + decoded: "streaming", + encoded: hex("4973747265616d696e67"), + reasons: []string{ + reasonByteString, + reasonStringFixedLength, + }, + }, + { + example: hex("9fff"), + decoded: []interface{}{}, + encoded: hex("80"), + reasons: []string{ + reasonArrayFixedLength, + }, + }, + { + example: hex("9f018202039f0405ffff"), + decoded: []interface{}{ + int64(1), + []interface{}{int64(2), int64(3)}, + []interface{}{int64(4), int64(5)}, + }, + encoded: hex("8301820203820405"), + reasons: []string{ + reasonArrayFixedLength, + }, + }, + { + example: hex("9f01820203820405ff"), + decoded: []interface{}{ + int64(1), + []interface{}{int64(2), int64(3)}, + []interface{}{int64(4), int64(5)}, + }, + encoded: hex("8301820203820405"), + reasons: []string{ + reasonArrayFixedLength, + }, + }, + { + example: hex("83018202039f0405ff"), + decoded: []interface{}{ + int64(1), + []interface{}{int64(2), int64(3)}, + []interface{}{int64(4), int64(5)}, + }, + encoded: hex("8301820203820405"), + reasons: []string{ + reasonArrayFixedLength, + }, + }, + { + example: hex("83019f0203ff820405"), + decoded: []interface{}{ + int64(1), + []interface{}{int64(2), int64(3)}, + []interface{}{int64(4), int64(5)}, + }, + encoded: hex("8301820203820405"), + reasons: []string{ + reasonArrayFixedLength, + }, + }, + { + example: hex("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"), + decoded: []interface{}{ + int64(1), int64(2), int64(3), int64(4), int64(5), + int64(6), int64(7), int64(8), int64(9), int64(10), + int64(11), int64(12), int64(13), int64(14), int64(15), + int64(16), int64(17), int64(18), int64(19), int64(20), + int64(21), int64(22), int64(23), int64(24), int64(25), + }, + encoded: hex("98190102030405060708090a0b0c0d0e0f101112131415161718181819"), + reasons: []string{ + reasonArrayFixedLength, + }, + }, + { + example: hex("bf61610161629f0203ffff"), + decoded: map[string]interface{}{ + "a": int64(1), + "b": []interface{}{int64(2), int64(3)}, + }, + encoded: hex("a24161014162820203"), + reasons: []string{ + reasonArrayFixedLength, + reasonByteString, + reasonMapFixedLength, + }, + }, + { + example: hex("826161bf61626163ff"), + decoded: []interface{}{"a", map[string]interface{}{"b": "c"}}, + encoded: hex("824161a141624163"), + reasons: []string{ + reasonByteString, + reasonMapFixedLength, + }, + }, + { + example: hex("bf6346756ef563416d7421ff"), + decoded: map[string]interface{}{ + "Amt": int64(-2), + "Fun": true, + }, + encoded: hex("a243416d74214346756ef5"), + reasons: []string{ + reasonByteString, + reasonMapFixedLength, + reasonMapSorted, + }, + }, + } { + t.Run(fmt.Sprintf("%x", tc.example), func(t *testing.T) { + var decoded interface{} + err := modes.Decode.Unmarshal(tc.example, &decoded) + if err != nil { + if tc.reject != "" { + t.Logf("expected decode error (%s) occurred: %v", tc.reject, err) + return + } + t.Fatalf("unexpected decode error: %v", err) + } else if tc.reject != "" { + t.Fatalf("expected decode error (%v) did not occur", tc.reject) + } + + if !eq.DeepEqual(tc.decoded, decoded) { + t.Fatal(cmp.Diff(tc.decoded, decoded)) + } + + actual, err := modes.Encode.Marshal(decoded) + if err != nil { + t.Fatal(err) + } + + expected := tc.example + if tc.encoded != nil { + expected = tc.encoded + if len(tc.reasons) == 0 { + t.Fatal("invalid test case: missing reasons for difference between the example encoding and the actual encoding") + } + diff := cmp.Diff(tc.example, tc.encoded) + if diff == "" { + t.Fatal("invalid test case: no difference between the example encoding and the expected re-encoding") + } + t.Logf("expecting the following differences from the example encoding on re-encode:\n%s", diff) + t.Logf("reasons for encoding differences:") + for _, reason := range tc.reasons { + t.Logf("- %s", reason) + } + + } + + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("re-encoded object differs from expected:\n%s", diff) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers.go new file mode 100644 index 0000000000..f14cbd6b58 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 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 modes + +import ( + "bytes" + "sync" +) + +var buffers = BufferProvider{p: new(sync.Pool)} + +type buffer struct { + bytes.Buffer +} + +type pool interface { + Get() interface{} + Put(interface{}) +} + +type BufferProvider struct { + p pool +} + +func (b *BufferProvider) Get() *buffer { + if buf, ok := b.p.Get().(*buffer); ok { + return buf + } + return &buffer{} +} + +func (b *BufferProvider) Put(buf *buffer) { + if buf.Cap() > 3*1024*1024 /* Default MaxRequestBodyBytes */ { + // Objects in a sync.Pool are assumed to be fungible. This is not a good assumption + // for pools of *bytes.Buffer because a *bytes.Buffer's underlying array grows as + // needed to accommodate writes. In Kubernetes, apiservers tend to encode "small" + // objects very frequently and much larger objects (especially large lists) only + // occasionally. Under steady load, pooled buffers tend to be borrowed frequently + // enough to prevent them from being released. Over time, each buffer is used to + // encode a large object and its capacity increases accordingly. The result is that + // practically all buffers in the pool retain much more capacity than needed to + // encode most objects. + + // As a basic mitigation for the worst case, buffers with more capacity than the + // default max request body size are never returned to the pool. + // TODO: Optimize for higher buffer utilization. + return + } + buf.Reset() + b.p.Put(buf) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers_test.go new file mode 100644 index 0000000000..dcc619b199 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/buffers_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 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 modes + +import ( + "testing" +) + +type mockPool struct { + v interface{} +} + +func (*mockPool) Get() interface{} { + return nil +} + +func (p *mockPool) Put(v interface{}) { + p.v = v +} + +func TestBufferProviderPut(t *testing.T) { + { + p := new(mockPool) + bp := &BufferProvider{p: p} + small := new(buffer) + small.Grow(3 * 1024 * 1024) + small.WriteString("hello world") + bp.Put(small) + if p.v != small { + t.Errorf("expected buf with capacity %d to be returned to pool", small.Cap()) + } + if small.Len() != 0 { + t.Errorf("expected buf to be reset before returning to pool") + } + } + + { + p := new(mockPool) + bp := &BufferProvider{p: p} + big := new(buffer) + big.Grow(3*1024*1024 + 1) + bp.Put(big) + if p.v != nil { + t.Errorf("expected buf with capacity %d not to be returned to pool", big.Cap()) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode.go new file mode 100644 index 0000000000..4a7055c10f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode.go @@ -0,0 +1,184 @@ +/* +Copyright 2024 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 modes + +import ( + "reflect" + + "github.com/fxamacker/cbor/v2" +) + +var simpleValues *cbor.SimpleValueRegistry = func() *cbor.SimpleValueRegistry { + var opts []func(*cbor.SimpleValueRegistry) error + for sv := 0; sv <= 255; sv++ { + // Reject simple values 0-19, 23, and 32-255. The simple values 24-31 are reserved + // and considered ill-formed by the CBOR specification. We only accept false (20), + // true (21), and null (22). + switch sv { + case 20: // false + case 21: // true + case 22: // null + case 24, 25, 26, 27, 28, 29, 30, 31: // reserved + default: + opts = append(opts, cbor.WithRejectedSimpleValue(cbor.SimpleValue(sv))) + } + } + simpleValues, err := cbor.NewSimpleValueRegistryFromDefaults(opts...) + if err != nil { + panic(err) + } + return simpleValues +}() + +// decode is the basis for the Decode mode, with no JSONUnmarshalerTranscoder +// configured. TranscodeToJSON uses this directly rather than Decode to avoid an initialization +// cycle between the two. Everything else should use one of the exported DecModes. +var decode cbor.DecMode = func() cbor.DecMode { + decode, err := cbor.DecOptions{ + // Maps with duplicate keys are well-formed but invalid according to the CBOR spec + // and never acceptable. Unlike the JSON serializer, inputs containing duplicate map + // keys are rejected outright and not surfaced as a strict decoding error. + DupMapKey: cbor.DupMapKeyEnforcedAPF, + + // For JSON parity, decoding an RFC3339 string into time.Time needs to be accepted + // with or without tagging. If a tag number is present, it must be valid. + TimeTag: cbor.DecTagOptional, + + // MaxNestedLevels is set to the same value used in the JSON implementation. + MaxNestedLevels: 10000, + + // MaxArrayElements is set to the maximum allowed by the cbor library. We rely on + // the library initial wellformedness scan and on the api max request limit to + // prevent preallocating very large slices during decoding. + MaxArrayElements: 2147483647, + + // MaxMapPairs specifies the maximum number of key-value pairs allowed in a map. + // We selected this value as it is large enough so that in practice the API server + // decoder will always hit the request body limit before the limit here is reached. + MaxMapPairs: 2097152, + + // Indefinite-length sequences aren't produced by this serializer, but other + // implementations can. + IndefLength: cbor.IndefLengthAllowed, + + // Accept inputs that contain CBOR tags. + TagsMd: cbor.TagsAllowed, + + // Decode type 0 (unsigned integer) as int64. + // TODO: IntDecConvertSignedOrFail errors on overflow, JSON will try to fall back to float64. + IntDec: cbor.IntDecConvertSignedOrFail, + + // Disable producing map[cbor.ByteString]interface{}, which is not acceptable for + // decodes into interface{}. + MapKeyByteString: cbor.MapKeyByteStringForbidden, + + // Error on map keys that don't map to a field in the destination struct. + ExtraReturnErrors: cbor.ExtraDecErrorUnknownField, + + // Decode maps into concrete type map[string]interface{} when the destination is an + // interface{}. + DefaultMapType: reflect.TypeOf(map[string]interface{}(nil)), + + // A CBOR text string whose content is not a valid UTF-8 sequence is well-formed but + // invalid according to the CBOR spec. Reject invalid inputs. Encoders are + // responsible for ensuring that all text strings they produce contain valid UTF-8 + // sequences and may use the byte string major type to encode strings that have not + // been validated. + UTF8: cbor.UTF8RejectInvalid, + + // Never make a case-insensitive match between a map key and a struct field. + FieldNameMatching: cbor.FieldNameMatchingCaseSensitive, + + // Produce string concrete values when decoding a CBOR byte string into interface{}. + DefaultByteStringType: reflect.TypeOf(""), + + // Allow CBOR byte strings to be decoded into string destination values. If a byte + // string is enclosed in an "expected later encoding" tag + // (https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.5.2), then the text + // encoding indicated by that tag (e.g. base64) will be applied to the contents of + // the byte string. + ByteStringToString: cbor.ByteStringToStringAllowedWithExpectedLaterEncoding, + + // Allow CBOR byte strings to match struct fields when appearing as a map key. + FieldNameByteString: cbor.FieldNameByteStringAllowed, + + // When decoding an unrecognized tag to interface{}, return the decoded tag content + // instead of the default, a cbor.Tag representing a (number, content) pair. + UnrecognizedTagToAny: cbor.UnrecognizedTagContentToAny, + + // Decode time tags to interface{} as strings containing RFC 3339 timestamps. + TimeTagToAny: cbor.TimeTagToRFC3339Nano, + + // For parity with JSON, strings can be decoded into time.Time if they are RFC 3339 + // timestamps. + ByteStringToTime: cbor.ByteStringToTimeAllowed, + + // Reject NaN and infinite floating-point values since they don't have a JSON + // representation (RFC 8259 Section 6). + NaN: cbor.NaNDecodeForbidden, + Inf: cbor.InfDecodeForbidden, + + // When unmarshaling a byte string into a []byte, assume that the byte string + // contains base64-encoded bytes, unless explicitly counterindicated by an "expected + // later encoding" tag. This is consistent with the because of unmarshaling a JSON + // text into a []byte. + ByteStringExpectedFormat: cbor.ByteStringExpectedBase64, + + // Reject the arbitrary-precision integer tags because they can't be faithfully + // roundtripped through the allowable Unstructured types. + BignumTag: cbor.BignumTagForbidden, + + // Reject anything other than the simple values true, false, and null. + SimpleValues: simpleValues, + + // Disable default recognition of types implementing encoding.BinaryUnmarshaler, + // which is not recognized for JSON decoding. + BinaryUnmarshaler: cbor.BinaryUnmarshalerNone, + + // Marshal types that implement encoding.TextMarshaler by calling their MarshalText + // method and encoding the result to a CBOR text string. + TextUnmarshaler: cbor.TextUnmarshalerTextString, + }.DecMode() + if err != nil { + panic(err) + } + return decode +}() + +var Decode cbor.DecMode = func() cbor.DecMode { + opts := decode.DecOptions() + // When decoding into a value of a type that implements json.Unmarshaler (and does not + // implement cbor.Unmarshaler), transcode the input to JSON and pass it to the value's + // UnmarshalJSON method. + opts.JSONUnmarshalerTranscoder = TranscodeFunc(TranscodeToJSON) + dm, err := opts.DecMode() + if err != nil { + panic(err) + } + return dm +}() + +// DecodeLax is derived from Decode, but does not complain about unknown fields in the input. +var DecodeLax cbor.DecMode = func() cbor.DecMode { + opts := Decode.DecOptions() + opts.ExtraReturnErrors &^= cbor.ExtraDecErrorUnknownField // clear bit + dm, err := opts.DecMode() + if err != nil { + panic(err) + } + return dm +}() diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode_test.go new file mode 100644 index 0000000000..e605981800 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/decode_test.go @@ -0,0 +1,851 @@ +/* +Copyright 2024 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 modes_test + +import ( + "encoding/hex" + "fmt" + "math" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" + + "github.com/fxamacker/cbor/v2" + "github.com/google/go-cmp/cmp" +) + +type int64BinaryUnmarshaler int64 + +func (i *int64BinaryUnmarshaler) UnmarshalBinary(_ []byte) error { + return nil +} + +func TestDecode(t *testing.T) { + hex := func(h string) []byte { + b, err := hex.DecodeString(h) + if err != nil { + t.Fatal(err) + } + return b + } + + type test struct { + name string + modes []cbor.DecMode // most tests should run for all modes + in []byte + into interface{} // prototype for concrete destination type. if nil, decode into empty interface value. + want interface{} + assertOnError func(t *testing.T, e error) + } + + // Test cases are grouped by the kind of the CBOR data item being decoded, as enumerated in + // https://www.rfc-editor.org/rfc/rfc8949.html#section-2. + group := func(t *testing.T, name string, tests []test) { + t.Run(name, func(t *testing.T) { + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + decModes := test.modes + if len(decModes) == 0 { + decModes = allDecModes + } + + for _, decMode := range decModes { + modeName, ok := decModeNames[decMode] + if !ok { + t.Fatal("test case configured to run against unrecognized mode") + } + + t.Run(fmt.Sprintf("%s/mode=%s", test.name, modeName), func(t *testing.T) { + var dst reflect.Value + if test.into == nil { + var i interface{} + dst = reflect.ValueOf(&i) + } else { + dst = reflect.New(reflect.TypeOf(test.into)) + } + err := decMode.Unmarshal(test.in, dst.Interface()) + test.assertOnError(t, err) + if err == nil { + if diff := cmp.Diff(test.want, dst.Elem().Interface()); diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } + } + }) + } + }) + } + }) + } + + group(t, "unsigned integer", []test{ + { + name: "unsigned integer decodes to interface{} as int64", + in: hex("0a"), // 10 + want: int64(10), + assertOnError: assertNilError, + }, + { + name: "int64 minimum positive value", + in: hex("00"), // 0 + want: int64(0), + assertOnError: assertNilError, + }, + { + name: "int64 max positive value", + in: hex("1b7fffffffffffffff"), // 9223372036854775807 + want: int64(9223372036854775807), + assertOnError: assertNilError, + }, + { + name: "max positive integer value supported by cbor: 2^64 - 1", + in: hex("1bffffffffffffffff"), // 18446744073709551615 + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnmarshalTypeError) { + if e == nil { + t.Error("expected non-nil error") + } else if want := "cbor: cannot unmarshal positive integer into Go value of type int64 (18446744073709551615 overflows Go's int64)"; want != e.Error() { + t.Errorf("want error %q, got %q", want, e.Error()) + } + }), + }, + }) + + group(t, "negative integer", []test{ + { + name: "int64 max negative value", + in: hex("20"), // -1 + want: int64(-1), + assertOnError: assertNilError, + }, + { + name: "int64 min negative value", + in: hex("3b7fffffffffffffff"), // -9223372036854775808 + want: int64(-9223372036854775808), + assertOnError: assertNilError, + }, + { + name: "min negative integer value supported by cbor: -2^64", + in: hex("3bffffffffffffffff"), // -18446744073709551616 + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnmarshalTypeError) { + if e == nil { + t.Error("expected non-nil error") + } else if want := "cbor: cannot unmarshal negative integer into Go value of type int64 (-18446744073709551616 overflows Go's int64)"; want != e.Error() { + t.Errorf("want error %q, got %q", want, e.Error()) + } + }), + }, + }) + + group(t, "byte string", []test{ + { + name: "empty byte string", + in: hex("40"), // '' + want: "", + assertOnError: assertNilError, + }, + { + name: "byte string into []byte assumes base64", + in: []byte("\x48AQIDBA=="), // 'AQIDBA==' + into: []byte{}, + want: []byte{0x01, 0x02, 0x03, 0x04}, + assertOnError: assertNilError, + }, + { + name: "byte string into []byte errors on invalid base64", + in: hex("41ff"), // h'ff' + into: []byte{}, + assertOnError: assertErrorMessage("cbor: failed to decode base64 from byte string: illegal base64 data at input byte 0"), + }, + { + name: "empty byte string into []byte assumes base64", + in: hex("40"), // '' + into: []byte{}, + want: []byte{}, + assertOnError: assertNilError, + }, + { + name: "byte string with expected encoding tag into []byte does not convert", + in: hex("d64401020304"), // 22(h'01020304') + into: []byte{}, + want: []byte{0x01, 0x02, 0x03, 0x04}, + assertOnError: assertNilError, + }, + { + name: "byte string with expected encoding tag into string converts", + in: hex("d64401020304"), // 22(h'01020304') + into: "", + want: "AQIDBA==", + assertOnError: assertNilError, + }, + { + name: "byte string with expected encoding tag into interface{} converts", + in: hex("d64401020304"), // 22(h'01020304') + want: "AQIDBA==", + assertOnError: assertNilError, + }, + { + name: "into non-string type implementing BinaryUnmarshaler", + in: hex("40"), // '' + into: int64BinaryUnmarshaler(7), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnmarshalTypeError) { + want := &cbor.UnmarshalTypeError{ + CBORType: "byte string", + GoType: reflect.TypeFor[int64BinaryUnmarshaler]().String(), + } + if e.CBORType != want.CBORType || e.GoType != want.GoType { + t.Errorf("expected %q, got %q", want, e) + } + }), + }, + { + name: "text unmarshaler", + in: hex("4161"), + into: &RoundtrippableText{}, + want: &RoundtrippableText{Text: "a"}, + assertOnError: assertNilError, + }, + { + name: "json unmarshaler", + in: hex("4161"), + into: &RoundtrippableJSON{}, + want: &RoundtrippableJSON{Raw: `"a"`}, + assertOnError: assertNilError, + }, + }) + + group(t, "text string", []test{ + { + name: "reject text string containing invalid utf-8 sequence", + in: hex("6180"), // text string beginning with continuation byte 0x80 + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.SemanticError) { + const expected = "cbor: invalid UTF-8 string" + if msg := e.Error(); msg != expected { + t.Errorf("expected %v, got %v", expected, msg) + } + }), + }, + { + name: "indefinite-length text string", + in: hex("7f616161626163ff"), // (_ "a", "b", "c") + want: "abc", + assertOnError: assertNilError, + }, + { + name: "empty text string", + in: hex("60"), // "" + want: "", + assertOnError: assertNilError, + }, + { + name: "text unmarshaler", + in: hex("6161"), + into: &RoundtrippableText{}, + want: &RoundtrippableText{Text: "a"}, + assertOnError: assertNilError, + }, + { + name: "json unmarshaler", + in: hex("6161"), + into: &RoundtrippableJSON{}, + want: &RoundtrippableJSON{Raw: `"a"`}, + assertOnError: assertNilError, + }, + }) + + group(t, "array", []test{ + { + name: "nested indefinite-length array", + in: hex("9f9f8080ff9f8080ffff"), // [_ [_ [] []] [_ [][]]] + want: []interface{}{ + []interface{}{[]interface{}{}, []interface{}{}}, + []interface{}{[]interface{}{}, []interface{}{}}, + }, + assertOnError: assertNilError, + }, + }) + + group(t, "map", []test{ + { + name: "reject duplicate negative int keys into struct", + modes: []cbor.DecMode{modes.DecodeLax}, + in: hex("a220012002"), // {-1: 1, -1: 2} + into: struct{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: int64(-1), Index: 1}), + }, + { + name: "reject duplicate negative int keys into map", + in: hex("a220012002"), // {-1: 1, -1: 2} + into: map[int64]interface{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: int64(-1), Index: 1}), + }, + { + name: "reject duplicate positive int keys into struct", + modes: []cbor.DecMode{modes.DecodeLax}, + in: hex("a201010102"), // {1: 1, 1: 2} + into: struct{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: int64(1), Index: 1}), + }, + { + name: "reject duplicate positive int keys into map", + in: hex("a201010102"), // {1: 1, 1: 2} + into: map[int64]interface{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: int64(1), Index: 1}), + }, + { + name: "reject duplicate text string keys into struct", + in: hex("a2614101614102"), // {"A": 1, "A": 2} + into: struct { + A int `json:"A"` + }{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("A"), Index: 1}), + }, + { + name: "reject duplicate text string keys into map", + in: hex("a2614101614102"), // {"A": 1, "A": 2} + into: map[string]interface{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("A"), Index: 1}), + }, + { + name: "reject duplicate byte string keys into map", + in: hex("a2414101414102"), // {'A': 1, 'A': 2} + into: map[string]interface{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("A"), Index: 1}), + }, + { + name: "reject duplicate byte string keys into struct", + in: hex("a2414101414102"), // {'A': 1, 'A': 2} + into: struct { + A int `json:"A"` + }{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("A"), Index: 1}), + }, + { + name: "reject duplicate byte string and text string keys into map", + in: hex("a2414101614102"), // {'A': 1, "A": 2} + into: map[string]interface{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("A"), Index: 1}), + }, + { + name: "reject duplicate byte string and text string keys into struct", + in: hex("a2414101614102"), // {'A': 1, "A": 2} + into: struct { + A int `json:"A"` + }{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("A"), Index: 1}), + }, + { + name: "reject two identical indefinite-length byte string keys split into chunks differently into struct", + in: hex("a25f426865436c6c6fff015f416844656c6c6fff02"), // {(_ 'he', 'llo'): 1, (_ 'h', 'ello'): 2} + into: struct { + Hello int `json:"hello"` + }{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("hello"), Index: 1}), + }, + { + name: "reject two identical indefinite-length byte string keys split into chunks differently into map", + in: hex("a25f426865436c6c6fff015f416844656c6c6fff02"), // {(_ 'he', 'llo'): 1, (_ 'h', 'ello'): 2} + into: map[string]interface{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("hello"), Index: 1}), + }, + { + name: "reject two identical indefinite-length text string keys split into chunks differently into struct", + in: hex("a27f626865636c6c6fff017f616864656c6c6fff02"), // {(_ "he", "llo"): 1, (_ "h", "ello"): 2} + into: struct { + Hello int `json:"hello"` + }{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("hello"), Index: 1}), + }, + { + name: "reject two identical indefinite-length text string keys split into chunks differently into map", + modes: []cbor.DecMode{modes.DecodeLax}, + in: hex("a27f626865636c6c6fff017f616864656c6c6fff02"), // {(_ "he", "llo"): 1, (_ "h", "ello"): 2} + into: map[string]interface{}{}, + assertOnError: assertIdenticalError(&cbor.DupMapKeyError{Key: string("hello"), Index: 1}), + }, + { + name: "case-insensitive match treated as unknown field", + modes: []cbor.DecMode{modes.Decode}, + in: hex("a1614101"), // {"A": 1} + into: struct { + A int `json:"a"` + }{}, + assertOnError: assertIdenticalError(&cbor.UnknownFieldError{Index: 0}), + }, + { + name: "case-insensitive match ignored in lax mode", + modes: []cbor.DecMode{modes.DecodeLax}, + in: hex("a1614101"), // {"A": 1} + into: struct { + A int `json:"a"` + }{}, + want: struct { + A int `json:"a"` + }{ + A: 0, + }, + assertOnError: assertNilError, + }, + { + name: "case-insensitive match after exact match treated as unknown field", + modes: []cbor.DecMode{modes.Decode}, + in: hex("a2616101614102"), // {"a": 1, "A": 2} + into: struct { + A int `json:"a"` + }{}, + want: struct { + A int `json:"a"` + }{ + A: 1, + }, + assertOnError: assertIdenticalError(&cbor.UnknownFieldError{Index: 1}), + }, + { + name: "case-insensitive match after exact match ignored in lax mode", + modes: []cbor.DecMode{modes.DecodeLax}, + in: hex("a2616101614102"), // {"a": 1, "A": 2} + into: struct { + A int `json:"a"` + }{}, + want: struct { + A int `json:"a"` + }{ + A: 1, + }, + assertOnError: assertNilError, + }, + { + name: "case-insensitive match before exact match treated as unknown field", + modes: []cbor.DecMode{modes.Decode}, + in: hex("a2614101616102"), // {"A": 1, "a": 2} + into: struct { + A int `json:"a"` + }{}, + assertOnError: assertIdenticalError(&cbor.UnknownFieldError{Index: 0}), + }, + { + name: "case-insensitive match before exact match ignored in lax mode", + modes: []cbor.DecMode{modes.DecodeLax}, + in: hex("a2614101616102"), // {"A": 1, "a": 2} + into: struct { + A int `json:"a"` + }{}, + want: struct { + A int `json:"a"` + }{ + A: 2, + }, + assertOnError: assertNilError, + }, + { + name: "unknown field error", + modes: []cbor.DecMode{modes.Decode}, + in: hex("a1616101"), // {"a": 1} + into: struct{}{}, + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnknownFieldError) { + if e.Index != 0 { + t.Errorf("expected %#v, got %#v", &cbor.UnknownFieldError{Index: 0}, e) + } + }), + }, + { + name: "no unknown field error in lax mode", + modes: []cbor.DecMode{modes.DecodeLax}, + in: hex("a1616101"), // {"a": 1} + into: struct{}{}, + want: struct{}{}, + assertOnError: assertNilError, + }, + { + name: "nested indefinite-length map", + in: hex("bf6141bf616101616202ff6142bf616901616a02ffff"), // {_ "A": {_ "a": 1, "b": 2}, "B": {_ "i": 1, "j": 2}} + want: map[string]interface{}{ + "A": map[string]interface{}{"a": int64(1), "b": int64(2)}, + "B": map[string]interface{}{"i": int64(1), "j": int64(2)}, + }, + assertOnError: assertNilError, + }, + { + name: "map with non-string key types", + in: hex("a1fb40091eb851eb851f63706965"), // {3.14: "pie"} + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnmarshalTypeError) { + if e.CBORType != "primitives" || e.GoType != "string" { + t.Errorf("expected %q, got %q", &cbor.UnmarshalTypeError{CBORType: "primitives", GoType: "string"}, e) + } + }), + }, + { + name: "map with byte string key", + in: hex("a143abcdef187b"), // {h'abcdef': 123} + want: map[string]interface{}{"\xab\xcd\xef": int64(123)}, + assertOnError: assertNilError, + }, + { + name: "map with text string key", + in: hex("a143414243187b"), // {"ABC": 123} + want: map[string]interface{}{"ABC": int64(123)}, + assertOnError: assertNilError, + }, + { + name: "map with mixed string key types", + in: hex("a243abcdef187b43414243187c"), // {h'abcdef': 123, "ABC": 124} + want: map[string]interface{}{"\xab\xcd\xef": int64(123), "ABC": int64(124)}, + assertOnError: assertNilError, + }, + }) + + group(t, "floating-point number", []test{ + { + name: "half precision infinity", + in: hex("f97c00"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point infinity"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "single precision infinity", + in: hex("fa7f800000"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point infinity"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "double precision infinity", + in: hex("fb7ff0000000000000"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point infinity"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "half precision negative infinity", + in: hex("f9fc00"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point infinity"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "single precision negative infinity", + in: hex("faff800000"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point infinity"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "double precision negative infinity", + in: hex("fbfff0000000000000"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point infinity"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "half precision NaN", + in: hex("f97e00"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point NaN"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "single precision NaN", + in: hex("fa7fc00000"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point NaN"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "double precision NaN", + in: hex("fb7ff8000000000000"), + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point NaN"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "smallest nonzero float64", + in: hex("fb0000000000000001"), + want: float64(math.SmallestNonzeroFloat64), + assertOnError: assertNilError, + }, + { + name: "max float64 value", + in: hex("fb7fefffffffffffff"), + want: float64(math.MaxFloat64), + assertOnError: assertNilError, + }, + { + name: "max float32 value as double precision", + in: hex("fb47efffffe0000000"), + want: float64(math.MaxFloat32), + assertOnError: assertNilError, + }, + { + name: "max float32 value as single precision", + in: hex("fa7f7fffff"), + want: float64(math.MaxFloat32), + assertOnError: assertNilError, + }, + { + name: "half precision", + in: hex("f94200"), + want: float64(3), + assertOnError: assertNilError, + }, + { + name: "double precision without fractional component", + in: hex("fb4000000000000000"), + want: float64(2), + assertOnError: assertNilError, + }, + { + name: "single precision without fractional component", + in: hex("fa40000000"), + want: float64(2), + assertOnError: assertNilError, + }, + { + name: "half precision without fractional component", + in: hex("f94000"), + want: float64(2), + assertOnError: assertNilError, + }, + }) + + group(t, "simple value", append([]test{ + { + name: "simple value 20", + in: hex("f4"), // false + want: false, + assertOnError: assertNilError, + }, + { + name: "simple value 21", + in: hex("f5"), // true + want: true, + assertOnError: assertNilError, + }, + { + name: "simple value 22", + in: hex("f6"), // nil + want: nil, + assertOnError: assertNilError, + }, + { + name: "simple value 23", + in: hex("f7"), // undefined + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "simple value 23 is not recognized"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + }, func() (generated []test) { + // Generate test cases for all simple values (0 to 255) because the number of possible simple values is fixed and small. + for i := 0; i <= 255; i++ { + each := test{ + name: fmt.Sprintf("simple value %d", i), + } + if i <= 23 { + each.in = []byte{byte(0xe0) | byte(i)} + } else { + // larger simple values encode to two bytes + each.in = []byte{byte(0xe0) | byte(24), byte(i)} + } + switch i { + case 20, 21, 22, 23: // recognized values with explicit cases + continue + case 24, 25, 26, 27, 28, 29, 30, 31: // reserved + // these are considered not well-formed + each.assertOnError = assertOnConcreteError(func(t *testing.T, e *cbor.SyntaxError) { + if e == nil { + t.Error("expected non-nil error") + } else if want := fmt.Sprintf("cbor: invalid simple value %d for type primitives", i); want != e.Error() { + t.Errorf("want error %q, got %q", want, e.Error()) + } + }) + default: + // reject all unrecognized simple values + each.assertOnError = assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: fmt.Sprintf("simple value %d is not recognized", i)}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }) + } + generated = append(generated, each) + } + return + }()...)) + + t.Run("tag", func(t *testing.T) { + group(t, "rfc3339 time", []test{ + { + name: "tag 0 RFC3339 text string", + in: hex("c074323030362d30312d30325431353a30343a30355a"), // 0("2006-01-02T15:04:05Z") + want: "2006-01-02T15:04:05Z", + assertOnError: assertNilError, + }, + { + name: "tag 0 byte string", + in: hex("c054323030362d30312d30325431353a30343a30355a"), // 0('2006-01-02T15:04:05Z') + want: "2006-01-02T15:04:05Z", + assertOnError: assertErrorMessage("cbor: tag number 0 must be followed by text string, got byte string"), + }, + { + name: "tag 0 non-RFC3339 text string", + in: hex("c06474657874"), // 0("text") + assertOnError: assertErrorMessage(`cbor: cannot set text for time.Time: parsing time "text" as "2006-01-02T15:04:05Z07:00": cannot parse "text" as "2006"`), + }, + }) + + group(t, "epoch time", []test{ + { + name: "tag 1 timestamp unsigned integer", + in: hex("c11a43b940e5"), // 1(1136214245) + want: "2006-01-02T15:04:05Z", + assertOnError: assertNilError, + }, + { + name: "tag 1 with float16 value", + in: hex("c1f93c00"), // 1(1.0_1) + want: "1970-01-01T00:00:01Z", + assertOnError: assertNilError, + }, + { + name: "tag 1 with float32 value", + in: hex("c1fa3f800000"), // 1(1.0_2) + want: "1970-01-01T00:00:01Z", + assertOnError: assertNilError, + }, + { + name: "tag 1 with float64 value", + in: hex("c1fb3ff0000000000000"), // 1(1.0_3) + want: "1970-01-01T00:00:01Z", + assertOnError: assertNilError, + }, + { + name: "tag 1 with a five digit year", + in: hex("c11b0000003afff44181"), // 1(253402300801) + want: "10000-01-01T00:00:01Z", + assertOnError: assertErrorMessage("cbor: decoded time cannot be represented in RFC3339 format with sub-second precision: Time.MarshalText: year outside of range [0,9999]"), + }, + { + name: "tag 1 with a negative integer value", + in: hex("c120"), // 1(-1) + want: "1969-12-31T23:59:59Z", + assertOnError: assertNilError, + }, + { + name: "tag 1 with a negative float16 value", + in: hex("c1f9bc00"), // 1(-1.0_1) + want: "1969-12-31T23:59:59Z", + assertOnError: assertNilError, + }, + { + name: "tag 1 with a negative float32 value", + in: hex("c1fabf800000"), // 1(-1.0_2) + want: "1969-12-31T23:59:59Z", + assertOnError: assertNilError, + }, + { + name: "tag 1 with a negative float64 value", + in: hex("c1fbbff0000000000000"), // 1(-1.0_3) + want: "1969-12-31T23:59:59Z", + assertOnError: assertNilError, + }, + { + name: "tag 1 with a positive infinity", + in: hex("c1f97c00"), // 1(Infinity) + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point infinity"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "tag 1 with a negative infinity", + in: hex("c1f9fc00"), // 1(-Infinity) + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point infinity"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + { + name: "tag 1 with NaN", + in: hex("c1f97e00"), // 1(NaN) + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "primitives", Message: "floating-point NaN"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + }) + + group(t, "unsigned bignum", []test{ + { + name: "rejected", + in: hex("c249010000000000000000"), // 2(18446744073709551616) + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "tag", Message: "bignum"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + }) + + group(t, "negative bignum", []test{ + { + name: "rejected", + in: hex("c349010000000000000000"), // 3(-18446744073709551617) + assertOnError: assertOnConcreteError(func(t *testing.T, e *cbor.UnacceptableDataItemError) { + if diff := cmp.Diff(&cbor.UnacceptableDataItemError{CBORType: "tag", Message: "bignum"}, e); diff != "" { + t.Errorf("unexpected error diff:\n%s", diff) + } + }), + }, + }) + + group(t, "unrecognized", []test{ + { + name: "decimal fraction", + in: hex("c48221196ab3"), // 4([-2, 27315]) + want: []interface{}{int64(-2), int64(27315)}, + assertOnError: assertNilError, + }, + { + name: "bigfloat", + in: hex("c5822003"), // 5([-1, 3]) + want: []interface{}{int64(-1), int64(3)}, + assertOnError: assertNilError, + }, + }) + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/diagnostic.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/diagnostic.go new file mode 100644 index 0000000000..61f3f145f5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/diagnostic.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 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 modes + +import ( + "github.com/fxamacker/cbor/v2" +) + +var Diagnostic cbor.DiagMode = func() cbor.DiagMode { + opts := Decode.DecOptions() + diagnostic, err := cbor.DiagOptions{ + ByteStringText: true, + + MaxNestedLevels: opts.MaxNestedLevels, + MaxArrayElements: opts.MaxArrayElements, + MaxMapPairs: opts.MaxMapPairs, + }.DiagMode() + if err != nil { + panic(err) + } + return diagnostic +}() diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode.go new file mode 100644 index 0000000000..3287dfd31c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode.go @@ -0,0 +1,185 @@ +/* +Copyright 2024 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 modes + +import ( + "io" + + "github.com/fxamacker/cbor/v2" +) + +// encode is the basis for the Encode mode, with no JSONMarshalerTranscoder +// configured. TranscodeFromJSON uses this directly rather than Encode to avoid an initialization +// cycle between the two. Everything else should use one of the exported EncModes. +var encode = EncMode{ + delegate: func() cbor.UserBufferEncMode { + encode, err := cbor.EncOptions{ + // Map keys need to be sorted to have deterministic output, and this is the order + // defined in RFC 8949 4.2.1 "Core Deterministic Encoding Requirements". + Sort: cbor.SortBytewiseLexical, + + // CBOR supports distinct types for IEEE-754 float16, float32, and float64. Store + // floats in the smallest width that preserves value so that equivalent float32 and + // float64 values encode to identical bytes, as they do in a JSON + // encoding. Satisfies one of the "Core Deterministic Encoding Requirements". + ShortestFloat: cbor.ShortestFloat16, + + // Error on attempt to encode NaN and infinite values. This is what the JSON + // serializer does. + NaNConvert: cbor.NaNConvertReject, + InfConvert: cbor.InfConvertReject, + + // Error on attempt to encode math/big.Int values, which can't be faithfully + // roundtripped through Unstructured in general (the dynamic numeric types allowed + // in Unstructured are limited to float64 and int64). + BigIntConvert: cbor.BigIntConvertReject, + + // MarshalJSON for time.Time writes RFC3339 with nanos. + Time: cbor.TimeRFC3339Nano, + + // The decoder must be able to accept RFC3339 strings with or without tag 0 (e.g. by + // the end of time.Time -> JSON -> Unstructured -> CBOR, the CBOR encoder has no + // reliable way of knowing that a particular string originated from serializing a + // time.Time), so producing tag 0 has little use. + TimeTag: cbor.EncTagNone, + + // Indefinite-length items have multiple encodings and aren't being used anyway, so + // disable to avoid an opportunity for nondeterminism. + IndefLength: cbor.IndefLengthForbidden, + + // Preserve distinction between nil and empty for slices and maps. + NilContainers: cbor.NilContainerAsNull, + + // OK to produce tags. + TagsMd: cbor.TagsAllowed, + + // Use the same definition of "empty" as encoding/json. + OmitEmpty: cbor.OmitEmptyGoValue, + + // The CBOR types text string and byte string are structurally equivalent, with the + // semantic difference that a text string whose content is an invalid UTF-8 sequence + // is itself invalid. We reject all invalid text strings at decode time and do not + // validate or sanitize all Go strings at encode time. Encoding Go strings to the + // byte string type is comparable to the existing Protobuf behavior and cheaply + // ensures that the output is valid CBOR. + String: cbor.StringToByteString, + + // Encode struct field names to the byte string type rather than the text string + // type. + FieldName: cbor.FieldNameToByteString, + + // Marshal Go byte arrays to CBOR arrays of integers (as in JSON) instead of byte + // strings. + ByteArray: cbor.ByteArrayToArray, + + // Marshal []byte to CBOR byte string enclosed in tag 22 (expected later base64 + // encoding, https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.5.2), to + // interoperate with the existing JSON behavior. This indicates to the decoder that, + // when decoding into a string (or unstructured), the resulting value should be the + // base64 encoding of the original bytes. No base64 encoding or decoding needs to be + // performed for []byte-to-CBOR-to-[]byte roundtrips. + ByteSliceLaterFormat: cbor.ByteSliceLaterFormatBase64, + + // Disable default recognition of types implementing encoding.BinaryMarshaler, which + // is not recognized for JSON encoding. + BinaryMarshaler: cbor.BinaryMarshalerNone, + + // Unmarshal into types that implement encoding.TextUnmarshaler by passing + // the contents of a CBOR string to their UnmarshalText method. + TextMarshaler: cbor.TextMarshalerTextString, + }.UserBufferEncMode() + if err != nil { + panic(err) + } + return encode + }(), + deterministic: true, +} + +var Encode = EncMode{ + delegate: func() cbor.UserBufferEncMode { + opts := encode.options() + // To encode a value of a type that implements json.Marshaler (and does not + // implement cbor.Marshaler), transcode the result of calling its MarshalJSON method + // directly to CBOR. + opts.JSONMarshalerTranscoder = TranscodeFunc(TranscodeFromJSON) + em, err := opts.UserBufferEncMode() + if err != nil { + panic(err) + } + return em + }(), + deterministic: true, +} + +var EncodeNondeterministic = EncMode{ + delegate: func() cbor.UserBufferEncMode { + opts := Encode.options() + opts.Sort = cbor.SortFastShuffle + em, err := opts.UserBufferEncMode() + if err != nil { + panic(err) + } + return em + }(), + deterministic: false, +} + +type EncMode struct { + delegate cbor.UserBufferEncMode + deterministic bool +} + +func (em EncMode) options() cbor.EncOptions { + return em.delegate.EncOptions() +} + +func (em EncMode) IsDeterministic() bool { + return em.deterministic +} + +func (em EncMode) MarshalTo(v interface{}, w io.Writer) error { + if buf, ok := w.(*buffer); ok { + return em.delegate.MarshalToBuffer(v, &buf.Buffer) + } + + buf := buffers.Get() + defer buffers.Put(buf) + if err := em.delegate.MarshalToBuffer(v, &buf.Buffer); err != nil { + return err + } + + if _, err := io.Copy(w, buf); err != nil { + return err + } + + return nil +} + +func (em EncMode) Marshal(v interface{}) ([]byte, error) { + buf := buffers.Get() + defer buffers.Put(buf) + + if err := em.MarshalTo(v, &buf.Buffer); err != nil { + return nil, err + } + + clone := make([]byte, buf.Len()) + copy(clone, buf.Bytes()) + + return clone, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode_test.go new file mode 100644 index 0000000000..5c557b7365 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/encode_test.go @@ -0,0 +1,132 @@ +/* +Copyright 2024 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 modes_test + +import ( + "fmt" + "math/big" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" + + "github.com/fxamacker/cbor/v2" + "github.com/google/go-cmp/cmp" +) + +type int64BinaryMarshaler int64 + +func (i int64BinaryMarshaler) MarshalBinary() ([]byte, error) { + return []byte{}, nil +} + +func TestEncode(t *testing.T) { + for _, tc := range []struct { + name string + modes []modes.EncMode + in interface{} + want []byte + assertOnError func(t *testing.T, e error) + }{ + { + name: "implementations of BinaryMarshaler are ignored", + in: int64BinaryMarshaler(7), + want: []byte{0x07}, + assertOnError: assertNilError, + }, + { + name: "all duplicate fields are ignored", // Matches behavior of JSON serializer. + in: struct { + A1 int `json:"a"` + A2 int `json:"a"` //nolint:govet // This is intentional to test that the encoder will not encode two map entries with the same key. + }{}, + want: []byte{0xa0}, // {} + assertOnError: assertNilError, + }, + { + name: "only tagged field is considered if any are tagged", // Matches behavior of JSON serializer. + in: struct { + A int + TaggedA int `json:"A"` + }{ + A: 1, + TaggedA: 2, + }, + want: []byte{0xa1, 0x41, 0x41, 0x02}, // {"A": 2} + assertOnError: assertNilError, + }, + { + name: "math/big.Int values are rejected", + in: big.NewInt(1), + assertOnError: assertOnConcreteError(func(t *testing.T, got *cbor.UnsupportedTypeError) { + if want := (&cbor.UnsupportedTypeError{Type: reflect.TypeFor[big.Int]()}); *want != *got { + t.Errorf("unexpected error, got %#v (%q), want %#v (%q)", got, got.Error(), want, want.Error()) + } + }), + }, + { + name: "byte array encodes to array of integers", + in: [3]byte{0x01, 0x02, 0x03}, + want: []byte{0x83, 0x01, 0x02, 0x03}, // [1, 2, 3] + assertOnError: assertNilError, + }, + { + name: "string marshalled to byte string", + in: "hello", + want: []byte{0x45, 'h', 'e', 'l', 'l', 'o'}, + assertOnError: assertNilError, + }, + { + name: "[]byte marshalled to byte string in expected base64 encoding tag", + in: []byte("hello"), + want: []byte{0xd6, 0x45, 'h', 'e', 'l', 'l', 'o'}, + assertOnError: assertNilError, + }, + { + name: "text marshaler", + in: &RoundtrippableText{Text: "a"}, + want: []byte{0x61, 0x61}, + assertOnError: assertNilError, + }, + { + name: "json marshaler", + in: &RoundtrippableJSON{Raw: `"a"`}, + want: []byte{0x41, 0x61}, + assertOnError: assertNilError, + }, + } { + encModes := tc.modes + if len(encModes) == 0 { + encModes = allEncModes + } + + for _, encMode := range encModes { + modeName, ok := encModeNames[encMode] + if !ok { + t.Fatal("test case configured to run against unrecognized mode") + } + + t.Run(fmt.Sprintf("mode=%s/%s", modeName, tc.name), func(t *testing.T) { + out, err := encMode.Marshal(tc.in) + tc.assertOnError(t, err) + if diff := cmp.Diff(tc.want, out, cmp.Comparer(func(a, b reflect.Type) bool { return a == b })); diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } + }) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/modes_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/modes_test.go new file mode 100644 index 0000000000..c303cf5d88 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/modes_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2024 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 modes_test + +import ( + "errors" + "testing" + + "github.com/fxamacker/cbor/v2" + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" +) + +var encModeNames = map[modes.EncMode]string{ + modes.Encode: "Encode", + modes.EncodeNondeterministic: "EncodeNondeterministic", +} + +var allEncModes = []modes.EncMode{ + modes.Encode, + modes.EncodeNondeterministic, +} + +var decModeNames = map[cbor.DecMode]string{ + modes.Decode: "Decode", + modes.DecodeLax: "DecodeLax", +} + +var allDecModes = []cbor.DecMode{ + modes.Decode, + modes.DecodeLax, +} + +func assertNilError(t *testing.T, e error) { + if e != nil { + t.Errorf("expected nil error, got: %v", e) + } +} + +func assertOnConcreteError[E error](fn func(*testing.T, E)) func(t *testing.T, e error) { + return func(t *testing.T, ei error) { + var ec E + if !errors.As(ei, &ec) { + t.Errorf("expected concrete error type %T, got %T: %v", ec, ei, ei) + return + } + fn(t, ec) + } +} + +func assertErrorMessage(want string) func(*testing.T, error) { + return func(t *testing.T, got error) { + if got == nil { + t.Error("expected non-nil error") + return + } + if got.Error() != want { + t.Errorf("got error %q, want %q", got.Error(), want) + } + } +} + +func assertIdenticalError[E error](expected E) func(*testing.T, error) { + return assertOnConcreteError(func(t *testing.T, actual E) { + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("diff between actual error and expected error:\n%s", diff) + } + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/roundtrip_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/roundtrip_test.go new file mode 100644 index 0000000000..e99f427127 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/roundtrip_test.go @@ -0,0 +1,449 @@ +/* +Copyright 2024 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 modes_test + +import ( + "encoding/base64" + "fmt" + "math" + "reflect" + "testing" + "time" + + "k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes" + + "github.com/fxamacker/cbor/v2" + "github.com/google/go-cmp/cmp" +) + +func nilPointerFor[T interface{}]() *T { + return nil +} + +type RoundtrippableText struct{ Text string } + +func (rt RoundtrippableText) MarshalText() ([]byte, error) { + return []byte(rt.Text), nil +} + +func (rt *RoundtrippableText) UnmarshalText(text []byte) error { + rt.Text = string(text) + return nil +} + +type RoundtrippableJSON struct{ Raw string } + +func (rj RoundtrippableJSON) MarshalJSON() ([]byte, error) { + return []byte(rj.Raw), nil +} + +func (rj *RoundtrippableJSON) UnmarshalJSON(raw []byte) error { + rj.Raw = string(raw) + return nil +} + +// TestRoundtrip roundtrips object serialization to interface{} and back via CBOR. +func TestRoundtrip(t *testing.T) { + type modePair struct { + enc modes.EncMode + dec cbor.DecMode + } + + for _, tc := range []struct { + name string + modePairs []modePair + obj interface{} + }{ + { + name: "nil slice", + obj: []interface{}(nil), + }, + { + name: "byte array", + obj: [3]byte{0x01, 0x02, 0x03}, + }, + { + name: "nil map", + obj: map[string]interface{}(nil), + }, + { + name: "empty slice", + obj: []interface{}{}, + }, + { + name: "empty map", + obj: map[string]interface{}{}, + }, + { + name: "nil pointer to slice", + obj: nilPointerFor[[]interface{}](), + }, + { + name: "nil pointer to map", + obj: nilPointerFor[map[string]interface{}](), + }, + { + name: "nonempty string", + obj: "hello world", + }, + { + name: "empty string", + obj: "", + }, + { + name: "string containing invalid UTF-8 sequence", + obj: "\x80", // first byte is a continuation byte + }, + { + name: "true", + obj: true, + }, + { + name: "false", + obj: false, + }, + { + name: "int64", + obj: int64(5), + }, + { + name: "int64 max", + obj: int64(math.MaxInt64), + }, + { + name: "int64 min", + obj: int64(math.MinInt64), + }, + { + name: "int64 zero", + obj: int64(math.MinInt64), + }, + { + name: "uint64 zero", + obj: uint64(0), + }, + { + name: "int32 max", + obj: int32(math.MaxInt32), + }, + { + name: "int32 min", + obj: int32(math.MinInt32), + }, + { + name: "int32 zero", + obj: int32(math.MinInt32), + }, + { + name: "uint32 max", + obj: uint32(math.MaxUint32), + }, + { + name: "uint32 zero", + obj: uint32(0), + }, + { + name: "int16 max", + obj: int16(math.MaxInt16), + }, + { + name: "int16 min", + obj: int16(math.MinInt16), + }, + { + name: "int16 zero", + obj: int16(math.MinInt16), + }, + { + name: "uint16 max", + obj: uint16(math.MaxUint16), + }, + { + name: "uint16 zero", + obj: uint16(0), + }, + { + name: "int8 max", + obj: int8(math.MaxInt8), + }, + { + name: "int8 min", + obj: int8(math.MinInt8), + }, + { + name: "int8 zero", + obj: int8(math.MinInt8), + }, + { + name: "uint8 max", + obj: uint8(math.MaxUint8), + }, + { + name: "uint8 zero", + obj: uint8(0), + }, + { + name: "float64", + obj: float64(2.71), + }, + { + name: "float64 max", + obj: float64(math.MaxFloat64), + }, + { + name: "float64 smallest nonzero", + obj: float64(math.SmallestNonzeroFloat64), + }, + { + name: "float64 no fractional component", + obj: float64(5), + }, + { + name: "float32", + obj: float32(2.71), + }, + { + name: "float32 max", + obj: float32(math.MaxFloat32), + }, + { + name: "float32 smallest nonzero", + obj: float32(math.SmallestNonzeroFloat32), + }, + { + name: "float32 no fractional component", + obj: float32(5), + }, + { + name: "time.Time", + obj: time.Date(2222, time.May, 4, 12, 13, 14, 123, time.UTC), + }, + { + name: "int64 omitempty", + obj: struct { + V int64 `json:"v,omitempty"` + }{}, + }, + { + name: "float64 omitempty", + obj: struct { + V float64 `json:"v,omitempty"` + }{}, + }, + { + name: "string omitempty", + obj: struct { + V string `json:"v,omitempty"` + }{}, + }, + { + name: "bool omitempty", + obj: struct { + V bool `json:"v,omitempty"` + }{}, + }, + { + name: "nil pointer omitempty", + obj: struct { + V *struct{} `json:"v,omitempty"` + }{}, + }, + { + name: "nil pointer to slice as struct field", + obj: struct { + V *[]interface{} `json:"v"` + }{}, + }, + { + name: "nil pointer to slice as struct field with omitempty", + obj: struct { + V *[]interface{} `json:"v,omitempty"` + }{}, + }, + { + name: "nil pointer to map as struct field", + obj: struct { + V *map[string]interface{} `json:"v"` + }{}, + }, + { + name: "nil pointer to map as struct field with omitempty", + obj: struct { + V *map[string]interface{} `json:"v,omitempty"` + }{}, + }, + { + name: "textmarshaler and textunmarshaler", + obj: RoundtrippableText{Text: "foo"}, + }, + { + name: "json marshaler and unmarshaler", + obj: RoundtrippableJSON{Raw: `{"foo":[42,3.1,true,false,null]}`}, + }, + } { + modePairs := tc.modePairs + if len(modePairs) == 0 { + // Default is all modes to all modes. + modePairs = []modePair{} + for _, encMode := range allEncModes { + for _, decMode := range allDecModes { + modePairs = append(modePairs, modePair{enc: encMode, dec: decMode}) + } + } + } + + for _, modePair := range modePairs { + encModeName, ok := encModeNames[modePair.enc] + if !ok { + t.Fatal("test case configured to run against unrecognized encode mode") + } + + decModeName, ok := decModeNames[modePair.dec] + if !ok { + t.Fatal("test case configured to run against unrecognized decode mode") + } + + t.Run(fmt.Sprintf("enc=%s/dec=%s/%s", encModeName, decModeName, tc.name), func(t *testing.T) { + original := tc.obj + + cborFromOriginal, err := modePair.enc.Marshal(original) + if err != nil { + t.Fatalf("unexpected error from Marshal of original: %v", err) + } + + var iface interface{} + if err := modePair.dec.Unmarshal(cborFromOriginal, &iface); err != nil { + t.Fatalf("unexpected error from Unmarshal into %T: %v", &iface, err) + } + + cborFromIface, err := modePair.enc.Marshal(iface) + if err != nil { + t.Fatalf("unexpected error from Marshal of iface: %v", err) + } + + { + // interface{} to interface{} + var iface2 interface{} + if err := modePair.dec.Unmarshal(cborFromIface, &iface2); err != nil { + t.Fatalf("unexpected error from Unmarshal into %T: %v", &iface2, err) + } + if diff := cmp.Diff(iface, iface2); diff != "" { + t.Errorf("unexpected difference on roundtrip from interface{} to interface{}:\n%s", diff) + } + } + + { + // original to original + final := reflect.New(reflect.TypeOf(original)) + err = modePair.dec.Unmarshal(cborFromOriginal, final.Interface()) + if err != nil { + t.Fatalf("unexpected error from Unmarshal into %T: %v", final.Interface(), err) + } + if diff := cmp.Diff(original, final.Elem().Interface()); diff != "" { + t.Errorf("unexpected difference on roundtrip from original to original:\n%s", diff) + } + } + + { + // original to interface{} to original + finalViaIface := reflect.New(reflect.TypeOf(original)) + err = modePair.dec.Unmarshal(cborFromIface, finalViaIface.Interface()) + if err != nil { + t.Fatalf("unexpected error from Unmarshal into %T: %v", finalViaIface.Interface(), err) + } + if diff := cmp.Diff(original, finalViaIface.Elem().Interface()); diff != "" { + t.Errorf("unexpected difference on roundtrip from original to interface{} to original:\n%s", diff) + } + } + }) + } + } +} + +// TestRoundtripTextEncoding exercises roundtrips between []byte and string. +func TestRoundtripTextEncoding(t *testing.T) { + for _, encMode := range allEncModes { + for _, decMode := range allDecModes { + t.Run(fmt.Sprintf("enc=%s/dec=%s/byte slice", encModeNames[encMode], decModeNames[decMode]), func(t *testing.T) { + original := []byte("foo") + + c, err := encMode.Marshal(original) + if err != nil { + t.Fatal(err) + } + + var unstructured interface{} + if err := decMode.Unmarshal(c, &unstructured); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(base64.StdEncoding.EncodeToString(original), unstructured); diff != "" { + t.Errorf("[]byte to interface{}: unexpected diff:\n%s", diff) + } + + var s string + if err := decMode.Unmarshal(c, &s); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(base64.StdEncoding.EncodeToString(original), s); diff != "" { + t.Errorf("[]byte to string: unexpected diff:\n%s", diff) + } + + var final []byte + if err := decMode.Unmarshal(c, &final); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(original, final); diff != "" { + t.Errorf("[]byte to []byte: unexpected diff:\n%s", diff) + } + }) + + t.Run(fmt.Sprintf("enc=%s/dec=%s/string", encModeNames[encMode], decModeNames[decMode]), func(t *testing.T) { + decoded := "foo" + original := base64.StdEncoding.EncodeToString([]byte(decoded)) // "Zm9v" + + c, err := encMode.Marshal(original) + if err != nil { + t.Fatal(err) + } + + var unstructured interface{} + if err := decMode.Unmarshal(c, &unstructured); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(original, unstructured); diff != "" { + t.Errorf("string to interface{}: unexpected diff:\n%s", diff) + } + + var b []byte + if err := decMode.Unmarshal(c, &b); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff([]byte(decoded), b); diff != "" { + t.Errorf("string to []byte: unexpected diff:\n%s", diff) + } + + var final string + if err := decMode.Unmarshal(c, &final); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(original, final); diff != "" { + t.Errorf("string to string: unexpected diff:\n%s", diff) + } + }) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding.go new file mode 100644 index 0000000000..5620e9ccc9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding.go @@ -0,0 +1,108 @@ +/* +Copyright 2025 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 modes + +import ( + "encoding/json" + "errors" + "io" + + kjson "sigs.k8s.io/json" +) + +type TranscodeFunc func(dst io.Writer, src io.Reader) error + +func (f TranscodeFunc) Transcode(dst io.Writer, src io.Reader) error { + return f(dst, src) +} + +func TranscodeFromJSON(dst io.Writer, src io.Reader) error { + var tmp any + dec := kjson.NewDecoderCaseSensitivePreserveInts(src) + if err := dec.Decode(&tmp); err != nil { + return err + } + if err := dec.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + return errors.New("extraneous data") + } + + return encode.MarshalTo(tmp, dst) +} + +func TranscodeToJSON(dst io.Writer, src io.Reader) error { + var tmp any + dec := decode.NewDecoder(src) + if err := dec.Decode(&tmp); err != nil { + return err + } + if err := dec.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + return errors.New("extraneous data") + } + + // Use an Encoder to avoid the extra []byte allocated by Marshal. Encode, unlike Marshal, + // appends a trailing newline to separate consecutive encodings of JSON values that aren't + // self-delimiting, like numbers. Strip the newline to avoid the assumption that every + // json.Unmarshaler implementation will accept trailing whitespace. + enc := json.NewEncoder(&trailingLinefeedSuppressor{delegate: dst}) + enc.SetIndent("", "") + return enc.Encode(tmp) +} + +// trailingLinefeedSuppressor is an io.Writer that wraps another io.Writer, suppressing a single +// trailing linefeed if it is the last byte written by the latest call to Write. +type trailingLinefeedSuppressor struct { + lf bool + delegate io.Writer +} + +func (w *trailingLinefeedSuppressor) Write(p []byte) (int, error) { + if len(p) == 0 { + // Avoid flushing a buffered linefeeds on an empty write. + return 0, nil + } + + if w.lf { + // The previous write had a trailing linefeed that was buffered. That wasn't the + // last Write call, so flush the buffered linefeed before continuing. + n, err := w.delegate.Write([]byte{'\n'}) + if n > 0 { + w.lf = false + } + if err != nil { + return 0, err + } + } + + if p[len(p)-1] != '\n' { + return w.delegate.Write(p) + } + + p = p[:len(p)-1] + + if len(p) == 0 { // []byte{'\n'} + w.lf = true + return 1, nil + } + + n, err := w.delegate.Write(p) + if n == len(p) { + // Everything up to the trailing linefeed has been flushed. Eat the linefeed. + w.lf = true + n++ + } + return n, err +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding_test.go new file mode 100644 index 0000000000..3033b054e8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes/transcoding_test.go @@ -0,0 +1,291 @@ +/* +Copyright 2025 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 modes + +import ( + "errors" + "strings" + "testing" +) + +func TestTranscodeFromJSON(t *testing.T) { + for _, tc := range []struct { + name string + json string + cbor string + err error + }{ + { + name: "whole number", + json: "42", + cbor: "\x18\x2a", + }, + { + name: "decimal number", + json: "1.5", + cbor: "\xf9\x3e\x00", + }, + { + name: "false", + json: "false", + cbor: "\xf4", + }, + { + name: "true", + json: "true", + cbor: "\xf5", + }, + { + name: "null", + json: "null", + cbor: "\xf6", + }, + { + name: "string", + json: `"foo"`, + cbor: "\x43foo", + }, + { + name: "array", + json: `[]`, + cbor: "\x80", + }, + { + name: "object", + json: `{"foo":"bar"}`, + cbor: "\xa1\x43foo\x43bar", + }, + { + name: "extraneous data", + json: "{}{}", + err: errors.New("extraneous data"), + }, + { + name: "eof", + json: "", + err: errors.New("EOF"), + }, + { + name: "unexpected eof", + json: "{", + err: errors.New("unexpected EOF"), + }, + { + name: "malformed json", + json: "}", + err: errors.New("invalid character '}' looking for beginning of value"), + }, + } { + t.Run(tc.name, func(t *testing.T) { + var out strings.Builder + err := TranscodeFromJSON(&out, strings.NewReader(tc.json)) + if (err == nil) != (tc.err == nil) || tc.err != nil && tc.err.Error() != err.Error() { + t.Fatalf("unexpected error: want %v got %v", tc.err, err) + } + if got, want := out.String(), tc.cbor; got != want { + t.Errorf("unexpected transcoding: want 0x%x got 0x%x", want, got) + } + }) + } +} + +func TestTranscodeToJSON(t *testing.T) { + for _, tc := range []struct { + name string + cbor string + json string + err error + }{ + { + name: "whole number", + cbor: "\x18\x2a", + json: "42", + }, + { + name: "decimal number", + cbor: "\xf9\x3e\x00", + json: "1.5", + }, + { + name: "false", + cbor: "\xf4", + json: "false", + }, + { + name: "true", + cbor: "\xf5", + json: "true", + }, + { + name: "null", + cbor: "\xf6", + json: "null", + }, + { + name: "string", + cbor: "\x43foo", + json: `"foo"`, + }, + { + name: "array", + cbor: "\x80", + json: `[]`, + }, + { + name: "object", + cbor: "\xa1\x43foo\x43bar", + json: `{"foo":"bar"}`, + }, + { + name: "extraneous data", + cbor: "\xa0\xa0", + err: errors.New("extraneous data"), + }, + { + name: "unexpected eof", + cbor: "\xa1", + err: errors.New("unexpected EOF"), + }, + { + name: "malformed cbor", + cbor: "\xff", + err: errors.New(`cbor: unexpected "break" code`), + }, + } { + t.Run(tc.name, func(t *testing.T) { + var out strings.Builder + err := TranscodeToJSON(&out, strings.NewReader(tc.cbor)) + if (err == nil) != (tc.err == nil) || tc.err != nil && tc.err.Error() != err.Error() { + t.Fatalf("unexpected error: want %v got %v", tc.err, err) + } + if got, want := out.String(), tc.json; got != want { + t.Errorf("unexpected transcoding: want %q got %q", want, got) + } + }) + } +} + +type write struct { + p string + n int + err error +} + +type mockWriter struct { + t testing.TB + calls []write +} + +func (m *mockWriter) Write(p []byte) (int, error) { + if len(m.calls) == 0 { + m.t.Fatalf("unexpected call (p=%q)", string(p)) + } + if got, want := string(p), m.calls[0].p; got != want { + m.t.Errorf("unexpected argument: want %q, got %q", want, got) + } + n, err := m.calls[0].n, m.calls[0].err + m.calls = m.calls[1:] + return n, err +} + +func TestTrailingLinefeedSuppressor(t *testing.T) { + for _, tc := range []struct { + name string + calls []write + delegated []write + }{ + { + name: "one write without newline", + calls: []write{{"foo", 3, nil}}, + delegated: []write{{"foo", 3, nil}}, + }, + { + name: "one write with newline", + calls: []write{{"foo\n", 4, nil}}, + delegated: []write{{"foo", 3, nil}}, + }, + { + name: "one write with only newline", + calls: []write{{"\n", 1, nil}}, + delegated: nil, + }, + { + name: "one empty write", + calls: []write{{"", 0, nil}}, + delegated: nil, + }, + { + name: "three writes, all with only newline", + calls: []write{{"\n", 1, nil}, {"\n", 1, nil}, {"\n", 1, nil}}, + delegated: []write{{"\n", 1, nil}, {"\n", 1, nil}}, + }, + { + name: "buffered linefeed not flushed on empty write", + calls: []write{{"\n", 1, nil}, {"", 0, nil}}, + delegated: nil, + }, + + { + name: "two writes, last with trailing newline", + calls: []write{{"foo", 3, nil}, {"bar\n", 4, nil}}, + delegated: []write{{"foo", 3, nil}, {"bar", 3, nil}}, + }, + { + name: "two writes, first with trailing newline", + calls: []write{{"foo\n", 4, nil}, {"bar", 3, nil}}, + delegated: []write{{"foo", 3, nil}, {"\n", 1, nil}, {"bar", 3, nil}}, + }, + { + name: "two writes, both with trailing newlines", + calls: []write{{"foo\n", 4, nil}, {"bar\n", 4, nil}}, + delegated: []write{{"foo", 3, nil}, {"\n", 1, nil}, {"bar", 3, nil}}, + }, + { + name: "two writes, neither with newlines", + calls: []write{{"foo", 3, nil}, {"bar", 3, nil}}, + delegated: []write{{"foo", 3, nil}, {"bar", 3, nil}}, + }, + { + name: "delegate error before reaching newline", + calls: []write{{"foo\n", 1, errors.New("test")}, {"oo\n", 3, nil}}, + delegated: []write{{"foo", 1, errors.New("test")}, {"oo", 2, nil}}, + }, + { + name: "delegate error after reaching newline", + calls: []write{{"foo\n", 4, errors.New("test")}}, + delegated: []write{{"foo", 3, errors.New("test")}}, + }, + { + name: "delegate error flushing newline", + calls: []write{{"foo\n", 4, nil}, {"\n", 0, errors.New("test")}, {"\n", 1, nil}}, + delegated: []write{{"foo", 3, nil}, {"\n", 0, errors.New("test")}, {"\n", 1, nil}}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + w := &trailingLinefeedSuppressor{delegate: &mockWriter{t: t, calls: tc.delegated}} + for _, call := range tc.calls { + n, err := w.Write([]byte(call.p)) + if n != call.n { + t.Errorf("unexpected n: want %d got %d", call.n, n) + } + if (err == nil) != (call.err == nil) || call.err != nil && call.err.Error() != err.Error() { + t.Errorf("unexpected error: want %v got %v", call.err, err) + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw.go new file mode 100644 index 0000000000..6d4f283cae --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw.go @@ -0,0 +1,236 @@ +/* +Copyright 2024 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 cbor + +import ( + "fmt" + "reflect" + "sync" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +var sharedTranscoders transcoders + +var rawTypeTranscodeFuncs = map[reflect.Type]func(reflect.Value) error{ + reflect.TypeFor[runtime.RawExtension](): func(rv reflect.Value) error { + if !rv.CanAddr() { + return nil + } + re := rv.Addr().Interface().(*runtime.RawExtension) + if re.Raw == nil { + // When Raw is nil it encodes to null. Don't change nil Raw values during + // transcoding, they would have unmarshalled from JSON as nil too. + return nil + } + j, err := re.MarshalJSON() + if err != nil { + return fmt.Errorf("failed to transcode RawExtension to JSON: %w", err) + } + re.Raw = j + return nil + }, + reflect.TypeFor[metav1.FieldsV1](): func(rv reflect.Value) error { + if !rv.CanAddr() { + return nil + } + fields := rv.Addr().Interface().(*metav1.FieldsV1) + if fields.GetRawReader().Size() == 0 { + // When Raw is nil it encodes to null. Don't change nil Raw values during + // transcoding, they would have unmarshalled from JSON as nil too. + return nil + } + j, err := fields.MarshalJSON() + if err != nil { + return fmt.Errorf("failed to transcode FieldsV1 to JSON: %w", err) + } + fields.SetRawBytes(j) + return nil + }, +} + +func transcodeRawTypes(v interface{}) error { + if v == nil { + return nil + } + + rv := reflect.ValueOf(v) + return sharedTranscoders.getTranscoder(rv.Type()).fn(rv) +} + +type transcoder struct { + fn func(rv reflect.Value) error +} + +var noop = transcoder{ + fn: func(reflect.Value) error { + return nil + }, +} + +type transcoders struct { + lock sync.RWMutex + m map[reflect.Type]**transcoder +} + +func (ts *transcoders) getTranscoder(rt reflect.Type) transcoder { + ts.lock.RLock() + tpp, ok := ts.m[rt] + ts.lock.RUnlock() + if ok { + return **tpp + } + + ts.lock.Lock() + defer ts.lock.Unlock() + tp := ts.getTranscoderLocked(rt) + return *tp +} + +func (ts *transcoders) getTranscoderLocked(rt reflect.Type) *transcoder { + if tpp, ok := ts.m[rt]; ok { + // A transcoder for this type was cached while waiting to acquire the lock. + return *tpp + } + + // Cache the transcoder now, before populating fn, so that circular references between types + // don't overflow the call stack. + t := new(transcoder) + if ts.m == nil { + ts.m = make(map[reflect.Type]**transcoder) + } + ts.m[rt] = &t + + for rawType, fn := range rawTypeTranscodeFuncs { + if rt == rawType { + t = &transcoder{fn: fn} + return t + } + } + + switch rt.Kind() { + case reflect.Array: + te := ts.getTranscoderLocked(rt.Elem()) + rtlen := rt.Len() + if rtlen == 0 || te == &noop { + t = &noop + break + } + t.fn = func(rv reflect.Value) error { + for i := 0; i < rtlen; i++ { + if err := te.fn(rv.Index(i)); err != nil { + return err + } + } + return nil + } + case reflect.Interface: + // Any interface value might have a dynamic type involving RawExtension. It needs to + // be checked. + t.fn = func(rv reflect.Value) error { + if rv.IsNil() { + return nil + } + rv = rv.Elem() + // The interface element's type is dynamic so its transcoder can't be + // determined statically. + return ts.getTranscoder(rv.Type()).fn(rv) + } + case reflect.Map: + rtk := rt.Key() + tk := ts.getTranscoderLocked(rtk) + rte := rt.Elem() + te := ts.getTranscoderLocked(rte) + if tk == &noop && te == &noop { + t = &noop + break + } + t.fn = func(rv reflect.Value) error { + iter := rv.MapRange() + rvk := reflect.New(rtk).Elem() + rve := reflect.New(rte).Elem() + for iter.Next() { + rvk.SetIterKey(iter) + if err := tk.fn(rvk); err != nil { + return err + } + rve.SetIterValue(iter) + if err := te.fn(rve); err != nil { + return err + } + } + return nil + } + case reflect.Pointer: + te := ts.getTranscoderLocked(rt.Elem()) + if te == &noop { + t = &noop + break + } + t.fn = func(rv reflect.Value) error { + if rv.IsNil() { + return nil + } + return te.fn(rv.Elem()) + } + case reflect.Slice: + te := ts.getTranscoderLocked(rt.Elem()) + if te == &noop { + t = &noop + break + } + t.fn = func(rv reflect.Value) error { + for i := 0; i < rv.Len(); i++ { + if err := te.fn(rv.Index(i)); err != nil { + return err + } + } + return nil + } + case reflect.Struct: + type fieldTranscoder struct { + Index int + Transcoder *transcoder + } + var fieldTranscoders []fieldTranscoder + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + tf := ts.getTranscoderLocked(f.Type) + if tf == &noop { + continue + } + fieldTranscoders = append(fieldTranscoders, fieldTranscoder{Index: i, Transcoder: tf}) + } + if len(fieldTranscoders) == 0 { + t = &noop + break + } + t.fn = func(rv reflect.Value) error { + for _, ft := range fieldTranscoders { + if err := ft.Transcoder.fn(rv.Field(ft.Index)); err != nil { + return err + } + } + return nil + } + default: + t = &noop + } + + return t +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw_test.go new file mode 100644 index 0000000000..5f6b140e41 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/cbor/raw_test.go @@ -0,0 +1,189 @@ +/* +Copyright 2024 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 cbor + +import ( + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/google/go-cmp/cmp" +) + +func TestTranscodeRawTypes(t *testing.T) { + for _, tc := range []struct { + In interface{} + Out interface{} + }{ + { + In: nil, + Out: nil, + }, + { + In: &runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}, + Out: &runtime.RawExtension{Raw: []byte(`7`)}, + }, + { + In: &runtime.RawExtension{}, + Out: &runtime.RawExtension{}, + }, + { + In: runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}, + Out: runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}, // not addressable + }, + { + In: &[...]runtime.RawExtension{{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}, {Raw: []byte{0xd9, 0xd9, 0xf7, 0x08}}, {Raw: []byte{0xd9, 0xd9, 0xf7, 0x09}}}, + Out: &[...]runtime.RawExtension{{Raw: []byte(`7`)}, {Raw: []byte(`8`)}, {Raw: []byte(`9`)}}, + }, + { + In: &[0]runtime.RawExtension{}, + Out: &[0]runtime.RawExtension{}, + }, + { + In: &[]runtime.RawExtension{{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}, {Raw: []byte{0xd9, 0xd9, 0xf7, 0x08}}, {Raw: []byte{0xd9, 0xd9, 0xf7, 0x09}}}, + Out: &[]runtime.RawExtension{{Raw: []byte(`7`)}, {Raw: []byte(`8`)}, {Raw: []byte(`9`)}}, + }, + { + In: &[]runtime.RawExtension{}, + Out: &[]runtime.RawExtension{}, + }, + { + In: &[]string{"foo"}, + Out: &[]string{"foo"}, + }, + { + In: (*runtime.RawExtension)(nil), + Out: (*runtime.RawExtension)(nil), + }, + { + In: &struct{ I fmt.Stringer }{I: &runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}}, + Out: &struct{ I fmt.Stringer }{I: &runtime.RawExtension{Raw: []byte(`7`)}}, + }, + { + In: &struct{ I fmt.Stringer }{I: nil}, + Out: &struct{ I fmt.Stringer }{I: nil}, + }, + { + In: &struct{ I int64 }{I: 7}, + Out: &struct{ I int64 }{I: 7}, + }, + { + In: &struct { + E runtime.RawExtension + I int64 + }{E: runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}, I: 7}, + Out: &struct { + E runtime.RawExtension + I int64 + }{E: runtime.RawExtension{Raw: []byte(`7`)}, I: 7}, + }, + { + In: &struct { + runtime.RawExtension + }{RawExtension: runtime.RawExtension{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}}, + Out: &struct { + runtime.RawExtension + }{RawExtension: runtime.RawExtension{Raw: []byte(`7`)}}, + }, + { + In: &map[string]string{"hello": "world"}, + Out: &map[string]string{"hello": "world"}, + }, + { + In: &map[string]runtime.RawExtension{"hello": {Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}}, + Out: &map[string]runtime.RawExtension{"hello": {Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}}, // not addressable + }, + { + In: &map[string][]runtime.RawExtension{"hello": {{Raw: []byte{0xd9, 0xd9, 0xf7, 0x07}}}}, + Out: &map[string][]runtime.RawExtension{"hello": {{Raw: []byte(`7`)}}}, + }, + { + In: metav1.NewFieldsV1(string([]byte{0xa0})), + Out: metav1.NewFieldsV1(`{}`), + }, + { + In: &metav1.FieldsV1{}, + Out: &metav1.FieldsV1{}, + }, + { + In: *metav1.NewFieldsV1(string([]byte{0xa0})), + Out: *metav1.NewFieldsV1(string([]byte{0xa0})), // not addressable + }, + { + In: &[...]metav1.FieldsV1{*metav1.NewFieldsV1(string([]byte{0xa0})), *metav1.NewFieldsV1(string([]byte{0xf6}))}, + Out: &[...]metav1.FieldsV1{*metav1.NewFieldsV1(`{}`), *metav1.NewFieldsV1(`null`)}, + }, + { + In: &[0]metav1.FieldsV1{}, + Out: &[0]metav1.FieldsV1{}, + }, + { + In: &[]metav1.FieldsV1{*metav1.NewFieldsV1(string([]byte{0xa0})), *metav1.NewFieldsV1(string([]byte{0xf6}))}, + Out: &[]metav1.FieldsV1{*metav1.NewFieldsV1(`{}`), *metav1.NewFieldsV1(`null`)}, + }, + { + In: &[]metav1.FieldsV1{}, + Out: &[]metav1.FieldsV1{}, + }, + { + In: (*metav1.FieldsV1)(nil), + Out: (*metav1.FieldsV1)(nil), + }, + { + In: &struct{ I fmt.Stringer }{I: metav1.NewFieldsV1(string([]byte{0xa0}))}, + Out: &struct{ I fmt.Stringer }{I: metav1.NewFieldsV1(`{}`)}, + }, + { + In: &struct { + E metav1.FieldsV1 + I int64 + }{E: *metav1.NewFieldsV1(string([]byte{0xa0})), I: 7}, + Out: &struct { + E metav1.FieldsV1 + I int64 + }{E: *metav1.NewFieldsV1(`{}`), I: 7}, + }, + { + In: &struct { + metav1.FieldsV1 + }{FieldsV1: *metav1.NewFieldsV1(string([]byte{0xa0}))}, + Out: &struct { + metav1.FieldsV1 + }{FieldsV1: *metav1.NewFieldsV1(`{}`)}, + }, + { + In: &map[string]metav1.FieldsV1{"hello": *metav1.NewFieldsV1(string([]byte{0xa0}))}, + Out: &map[string]metav1.FieldsV1{"hello": *metav1.NewFieldsV1(string([]byte{0xa0}))}, // not addressable + }, + { + In: &map[string][]metav1.FieldsV1{"hello": {*metav1.NewFieldsV1(string([]byte{0xa0}))}}, + Out: &map[string][]metav1.FieldsV1{"hello": {*metav1.NewFieldsV1(`{}`)}}, + }, + } { + t.Run(fmt.Sprintf("%#v", tc.In), func(t *testing.T) { + if err := transcodeRawTypes(tc.In); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if diff := cmp.Diff(tc.Out, tc.In); diff != "" { + t.Errorf("unexpected diff:\n%s", diff) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go new file mode 100644 index 0000000000..81286fccb4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go @@ -0,0 +1,322 @@ +/* +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 serializer + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" + "k8s.io/apimachinery/pkg/runtime/serializer/versioning" +) + +func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory, options CodecFactoryOptions) []runtime.SerializerInfo { + jsonSerializer := json.NewSerializerWithOptions( + mf, scheme, scheme, + json.SerializerOptions{Yaml: false, Pretty: false, Strict: options.Strict, StreamingCollectionsEncoding: options.StreamingCollectionsEncodingToJSON}, + ) + jsonSerializerType := runtime.SerializerInfo{ + MediaType: runtime.ContentTypeJSON, + MediaTypeType: "application", + MediaTypeSubType: "json", + EncodesAsText: true, + Serializer: jsonSerializer, + StrictSerializer: json.NewSerializerWithOptions( + mf, scheme, scheme, + json.SerializerOptions{Yaml: false, Pretty: false, Strict: true, StreamingCollectionsEncoding: options.StreamingCollectionsEncodingToJSON}, + ), + StreamSerializer: &runtime.StreamSerializerInfo{ + EncodesAsText: true, + Serializer: jsonSerializer, + Framer: json.Framer, + }, + } + if options.Pretty { + jsonSerializerType.PrettySerializer = json.NewSerializerWithOptions( + mf, scheme, scheme, + json.SerializerOptions{Yaml: false, Pretty: true, Strict: options.Strict}, + ) + } + + yamlSerializer := json.NewSerializerWithOptions( + mf, scheme, scheme, + json.SerializerOptions{Yaml: true, Pretty: false, Strict: options.Strict}, + ) + strictYAMLSerializer := json.NewSerializerWithOptions( + mf, scheme, scheme, + json.SerializerOptions{Yaml: true, Pretty: false, Strict: true}, + ) + protoSerializer := protobuf.NewSerializerWithOptions(scheme, scheme, protobuf.SerializerOptions{ + StreamingCollectionsEncoding: options.StreamingCollectionsEncodingToProtobuf, + }) + protoRawSerializer := protobuf.NewRawSerializer(scheme, scheme) + + serializers := []runtime.SerializerInfo{ + jsonSerializerType, + { + MediaType: runtime.ContentTypeYAML, + MediaTypeType: "application", + MediaTypeSubType: "yaml", + EncodesAsText: true, + Serializer: yamlSerializer, + StrictSerializer: strictYAMLSerializer, + }, + { + MediaType: runtime.ContentTypeProtobuf, + MediaTypeType: "application", + MediaTypeSubType: "vnd.kubernetes.protobuf", + Serializer: protoSerializer, + // note, strict decoding is unsupported for protobuf, + // fall back to regular serializing + StrictSerializer: protoSerializer, + StreamSerializer: &runtime.StreamSerializerInfo{ + Serializer: protoRawSerializer, + Framer: protobuf.LengthDelimitedFramer, + }, + }, + } + + for _, f := range options.serializers { + serializers = append(serializers, f(scheme, scheme)) + } + + return serializers +} + +// CodecFactory provides methods for retrieving codecs and serializers for specific +// versions and content types. +type CodecFactory struct { + scheme *runtime.Scheme + universal runtime.Decoder + accepts []runtime.SerializerInfo + + legacySerializer runtime.Serializer +} + +// CodecFactoryOptions holds the options for configuring CodecFactory behavior +type CodecFactoryOptions struct { + // Strict configures all serializers in strict mode + Strict bool + // Pretty includes a pretty serializer along with the non-pretty one + Pretty bool + + StreamingCollectionsEncodingToJSON bool + StreamingCollectionsEncodingToProtobuf bool + + serializers []func(runtime.ObjectCreater, runtime.ObjectTyper) runtime.SerializerInfo +} + +// CodecFactoryOptionsMutator takes a pointer to an options struct and then modifies it. +// Functions implementing this type can be passed to the NewCodecFactory() constructor. +type CodecFactoryOptionsMutator func(*CodecFactoryOptions) + +// EnablePretty enables including a pretty serializer along with the non-pretty one +func EnablePretty(options *CodecFactoryOptions) { + options.Pretty = true +} + +// DisablePretty disables including a pretty serializer along with the non-pretty one +func DisablePretty(options *CodecFactoryOptions) { + options.Pretty = false +} + +// EnableStrict enables configuring all serializers in strict mode +func EnableStrict(options *CodecFactoryOptions) { + options.Strict = true +} + +// DisableStrict disables configuring all serializers in strict mode +func DisableStrict(options *CodecFactoryOptions) { + options.Strict = false +} + +// WithSerializer configures a serializer to be supported in addition to the default serializers. +func WithSerializer(f func(runtime.ObjectCreater, runtime.ObjectTyper) runtime.SerializerInfo) CodecFactoryOptionsMutator { + return func(options *CodecFactoryOptions) { + options.serializers = append(options.serializers, f) + } +} + +func WithStreamingCollectionEncodingToJSON() CodecFactoryOptionsMutator { + return func(options *CodecFactoryOptions) { + options.StreamingCollectionsEncodingToJSON = true + } +} + +func WithStreamingCollectionEncodingToProtobuf() CodecFactoryOptionsMutator { + return func(options *CodecFactoryOptions) { + options.StreamingCollectionsEncodingToProtobuf = true + } +} + +// NewCodecFactory provides methods for retrieving serializers for the supported wire formats +// and conversion wrappers to define preferred internal and external versions. In the future, +// as the internal version is used less, callers may instead use a defaulting serializer and +// only convert objects which are shared internally (Status, common API machinery). +// +// Mutators can be passed to change the CodecFactoryOptions before construction of the factory. +// It is recommended to explicitly pass mutators instead of relying on defaults. +// By default, Pretty is enabled -- this is conformant with previously supported behavior. +// +// TODO: allow other codecs to be compiled in? +// TODO: accept a scheme interface +func NewCodecFactory(scheme *runtime.Scheme, mutators ...CodecFactoryOptionsMutator) CodecFactory { + options := CodecFactoryOptions{Pretty: true} + for _, fn := range mutators { + fn(&options) + } + + serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory, options) + return newCodecFactory(scheme, serializers) +} + +// newCodecFactory is a helper for testing that allows a different metafactory to be specified. +func newCodecFactory(scheme *runtime.Scheme, serializers []runtime.SerializerInfo) CodecFactory { + decoders := make([]runtime.Decoder, 0, len(serializers)) + var accepts []runtime.SerializerInfo + alreadyAccepted := make(map[string]struct{}) + + var legacySerializer runtime.Serializer + for _, d := range serializers { + decoders = append(decoders, d.Serializer) + if _, ok := alreadyAccepted[d.MediaType]; ok { + continue + } + alreadyAccepted[d.MediaType] = struct{}{} + + acceptedSerializerShallowCopy := d + if d.StreamSerializer != nil { + cloned := *d.StreamSerializer + acceptedSerializerShallowCopy.StreamSerializer = &cloned + } + accepts = append(accepts, acceptedSerializerShallowCopy) + + if d.MediaType == runtime.ContentTypeJSON { + legacySerializer = d.Serializer + } + } + if legacySerializer == nil { + legacySerializer = serializers[0].Serializer + } + + return CodecFactory{ + scheme: scheme, + universal: recognizer.NewDecoder(decoders...), + + accepts: accepts, + + legacySerializer: legacySerializer, + } +} + +// WithoutConversion returns a NegotiatedSerializer that performs no conversion, even if the +// caller requests it. +func (f CodecFactory) WithoutConversion() runtime.NegotiatedSerializer { + return WithoutConversionCodecFactory{f} +} + +// SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for. +func (f CodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { + return f.accepts +} + +// LegacyCodec encodes output to a given API versions, and decodes output into the internal form from +// any recognized source. The returned codec will always encode output to JSON. If a type is not +// found in the list of versions an error will be returned. +// +// This method is deprecated - clients and servers should negotiate a serializer by mime-type and +// invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder(). +// +// TODO: make this call exist only in pkg/api, and initialize it with the set of default versions. +// All other callers will be forced to request a Codec directly. +func (f CodecFactory) LegacyCodec(version ...schema.GroupVersion) runtime.Codec { + return versioning.NewDefaultingCodecForScheme(f.scheme, f.legacySerializer, f.universal, schema.GroupVersions(version), runtime.InternalGroupVersioner) +} + +// UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies +// runtime.Object. It does not perform conversion. It does not perform defaulting. +func (f CodecFactory) UniversalDeserializer() runtime.Decoder { + return f.universal +} + +// UniversalDecoder returns a runtime.Decoder capable of decoding all known API objects in all known formats. Used +// by clients that do not need to encode objects but want to deserialize API objects stored on disk. Only decodes +// objects in groups registered with the scheme. The GroupVersions passed may be used to select alternate +// versions of objects to return - by default, runtime.APIVersionInternal is used. If any versions are specified, +// unrecognized groups will be returned in the version they are encoded as (no conversion). This decoder performs +// defaulting. +// +// TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form +// TODO: only accept a group versioner +func (f CodecFactory) UniversalDecoder(versions ...schema.GroupVersion) runtime.Decoder { + var versioner runtime.GroupVersioner + if len(versions) == 0 { + versioner = runtime.InternalGroupVersioner + } else { + versioner = schema.GroupVersions(versions) + } + return f.CodecForVersions(nil, f.universal, nil, versioner) +} + +// CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list, +// it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not +// converted. If encode or decode are nil, no conversion is performed. +func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec { + // TODO: these are for backcompat, remove them in the future + if encode == nil { + encode = runtime.DisabledGroupVersioner + } + if decode == nil { + decode = runtime.InternalGroupVersioner + } + return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode) +} + +// DecoderToVersion returns a decoder that targets the provided group version. +func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { + return f.CodecForVersions(nil, decoder, nil, gv) +} + +// EncoderForVersion returns an encoder that targets the provided group version. +func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + return f.CodecForVersions(encoder, nil, gv, nil) +} + +// WithoutConversionCodecFactory is a CodecFactory that will explicitly ignore requests to perform conversion. +// This wrapper is used while code migrates away from using conversion (such as external clients) and in the future +// will be unnecessary when we change the signature of NegotiatedSerializer. +type WithoutConversionCodecFactory struct { + CodecFactory +} + +// EncoderForVersion returns an encoder that does not do conversion, but does set the group version kind of the object +// when serialized. +func (f WithoutConversionCodecFactory) EncoderForVersion(serializer runtime.Encoder, version runtime.GroupVersioner) runtime.Encoder { + return runtime.WithVersionEncoder{ + Version: version, + Encoder: serializer, + ObjectTyper: f.CodecFactory.scheme, + } +} + +// DecoderToVersion returns an decoder that does not do conversion. +func (f WithoutConversionCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder { + return runtime.WithoutVersionDecoder{ + Decoder: serializer, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/codec_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/codec_test.go new file mode 100644 index 0000000000..ceb6f93da8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/codec_test.go @@ -0,0 +1,370 @@ +/* +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 serializer + +import ( + "encoding/json" + "fmt" + "log" + "os" + "reflect" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" + "k8s.io/apimachinery/pkg/util/diff" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + "github.com/google/go-cmp/cmp" + flag "github.com/spf13/pflag" + "sigs.k8s.io/randfill" + "sigs.k8s.io/yaml" +) + +var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.") + +type testMetaFactory struct{} + +func (testMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) { + findKind := struct { + APIVersion string `json:"myVersionKey,omitempty"` + ObjectKind string `json:"myKindKey,omitempty"` + }{} + // yaml is a superset of json, so we use it to decode here. That way, + // we understand both. + if err := yaml.Unmarshal(data, &findKind); err != nil { + return nil, fmt.Errorf("couldn't get version/kind: %v", err) + } + gv, err := schema.ParseGroupVersion(findKind.APIVersion) + if err != nil { + return nil, err + } + return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.ObjectKind}, nil +} + +// TestObjectFuzzer can randomly populate all the above objects. +var TestObjectFuzzer = randfill.New().NilChance(.5).NumElements(1, 100).Funcs( + func(j *runtimetesting.MyWeirdCustomEmbeddedVersionKindField, c randfill.Continue) { + c.FillNoCustom(j) + j.APIVersion = "" + j.ObjectKind = "" + }, +) + +// Returns a new Scheme set up with the test objects. +func GetTestScheme() (*runtime.Scheme, runtime.Codec) { + internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Version: "v1"} + externalGV2 := schema.GroupVersion{Version: "v2"} + + s := runtime.NewScheme() + // Ordinarily, we wouldn't add TestType2, but because this is a test and + // both types are from the same package, we need to get it into the system + // so that converter will match it with ExternalType2. + s.AddKnownTypes(internalGV, &runtimetesting.TestType1{}, &runtimetesting.TestType2{}, &runtimetesting.ExternalInternalSame{}) + s.AddKnownTypes(externalGV, &runtimetesting.ExternalInternalSame{}) + s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &runtimetesting.ExternalTestType2{}) + s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &runtimetesting.TestType1{}) + s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(externalGV2.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) + + s.AddUnversionedTypes(externalGV, &metav1.Status{}) + + utilruntime.Must(runtimetesting.RegisterConversions(s)) + + cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})) + codec := cf.LegacyCodec(schema.GroupVersion{Version: "v1"}) + return s, codec +} + +var semantic = conversion.EqualitiesOrDie( + func(a, b runtimetesting.MyWeirdCustomEmbeddedVersionKindField) bool { + a.APIVersion, a.ObjectKind = "", "" + b.APIVersion, b.ObjectKind = "", "" + return a == b + }, +) + +func runTest(t *testing.T, source interface{}) { + name := reflect.TypeOf(source).Elem().Name() + TestObjectFuzzer.Fill(source) + + _, codec := GetTestScheme() + data, err := runtime.Encode(codec, source.(runtime.Object)) + if err != nil { + t.Errorf("%v: %v (%#v)", name, err, source) + return + } + obj2, err := runtime.Decode(codec, data) + if err != nil { + t.Errorf("%v: %v (%v)", name, err, string(data)) + return + } + if !semantic.DeepEqual(source, obj2) { + t.Errorf("1: %v: diff: %v", name, diff.ObjectGoPrintSideBySide(source, obj2)) + return + } + obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface() + if err := runtime.DecodeInto(codec, data, obj3.(runtime.Object)); err != nil { + t.Errorf("2: %v: %v", name, err) + return + } + if !semantic.DeepEqual(source, obj3) { + t.Errorf("3: %v: diff: %v", name, cmp.Diff(source, obj3)) + return + } +} + +func TestTypes(t *testing.T) { + table := []interface{}{ + &runtimetesting.TestType1{}, + &runtimetesting.ExternalInternalSame{}, + } + for _, item := range table { + // Try a few times, since runTest uses random values. + for i := 0; i < *fuzzIters; i++ { + runTest(t, item) + } + } +} + +func TestVersionedEncoding(t *testing.T) { + s, _ := GetTestScheme() + cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})) + info, _ := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON) + encoder := info.Serializer + + codec := cf.EncoderForVersion(encoder, schema.GroupVersion{Version: "v2"}) + out, err := runtime.Encode(codec, &runtimetesting.TestType1{}) + if err != nil { + t.Fatal(err) + } + if string(out) != `{"myVersionKey":"v2","myKindKey":"TestType1"}`+"\n" { + t.Fatal(string(out)) + } + + codec = cf.EncoderForVersion(encoder, schema.GroupVersion{Version: "v3"}) + _, err = runtime.Encode(codec, &runtimetesting.TestType1{}) + if err == nil { + t.Fatal(err) + } + + // unversioned encode with no versions is written directly to wire + codec = cf.EncoderForVersion(encoder, runtime.InternalGroupVersioner) + out, err = runtime.Encode(codec, &runtimetesting.TestType1{}) + if err != nil { + t.Fatal(err) + } + if string(out) != `{}`+"\n" { + t.Fatal(string(out)) + } +} + +func TestMultipleNames(t *testing.T) { + _, codec := GetTestScheme() + + obj, _, err := codec.Decode([]byte(`{"myKindKey":"TestType3","myVersionKey":"v1","A":"value"}`), nil, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + internal := obj.(*runtimetesting.TestType1) + if internal.A != "value" { + t.Fatalf("unexpected decoded object: %#v", internal) + } + + out, err := runtime.Encode(codec, internal) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(string(out), `"myKindKey":"TestType1"`) { + t.Errorf("unexpected encoded output: %s", string(out)) + } +} + +func TestStrictOption(t *testing.T) { + s, _ := GetTestScheme() + duplicateKeys := `{"myKindKey":"TestType3","myVersionKey":"v1","myVersionKey":"v1","A":"value"}` + + strictCodec := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})).LegacyCodec() + _, _, err := strictCodec.Decode([]byte(duplicateKeys), nil, nil) + if !runtime.IsStrictDecodingError(err) { + t.Fatalf("StrictDecodingError not returned on object with duplicate keys: %v, type: %v", err, reflect.TypeOf(err)) + } + + nonStrictCodec := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: false})).LegacyCodec() + _, _, err = nonStrictCodec.Decode([]byte(duplicateKeys), nil, nil) + if runtime.IsStrictDecodingError(err) { + t.Fatalf("Non-Strict decoder returned a StrictDecodingError: %v", err) + } +} + +func TestConvertTypesWhenDefaultNamesMatch(t *testing.T) { + internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Version: "v1"} + + s := runtime.NewScheme() + // create two names internally, with TestType1 being preferred + s.AddKnownTypeWithName(internalGV.WithKind("TestType1"), &runtimetesting.TestType1{}) + s.AddKnownTypeWithName(internalGV.WithKind("OtherType1"), &runtimetesting.TestType1{}) + // create two names externally, with TestType1 being preferred + s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &runtimetesting.ExternalTestType1{}) + s.AddKnownTypeWithName(externalGV.WithKind("OtherType1"), &runtimetesting.ExternalTestType1{}) + if err := runtimetesting.RegisterConversions(s); err != nil { + t.Fatalf("unexpected error; %v", err) + } + + ext := &runtimetesting.ExternalTestType1{} + ext.APIVersion = "v1" + ext.ObjectKind = "OtherType1" + ext.A = "test" + data, err := json.Marshal(ext) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expect := &runtimetesting.TestType1{A: "test"} + + codec := newCodecFactory( + s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true}), + ).LegacyCodec(schema.GroupVersion{Version: "v1"}) + + obj, err := runtime.Decode(codec, data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !semantic.DeepEqual(expect, obj) { + t.Errorf("unexpected object: %#v", obj) + } + + into := &runtimetesting.TestType1{} + if err := runtime.DecodeInto(codec, data, into); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !semantic.DeepEqual(expect, into) { + t.Errorf("unexpected object: %#v", obj) + } +} + +func TestEncode_Ptr(t *testing.T) { + _, codec := GetTestScheme() + tt := &runtimetesting.TestType1{A: "I am a pointer object"} + data, err := runtime.Encode(codec, tt) + obj2, err2 := runtime.Decode(codec, data) + if err != nil || err2 != nil { + t.Fatalf("Failure: '%v' '%v'\n%s", err, err2, data) + } + if _, ok := obj2.(*runtimetesting.TestType1); !ok { + t.Fatalf("Got wrong type") + } + if !semantic.DeepEqual(obj2, tt) { + t.Errorf("Expected:\n %#v,\n Got:\n %#v", tt, obj2) + } +} + +func TestBadJSONRejection(t *testing.T) { + log.SetOutput(os.Stderr) + _, codec := GetTestScheme() + badJSONs := [][]byte{ + []byte(`{"myVersionKey":"v1"}`), // Missing kind + []byte(`{"myVersionKey":"v1","myKindKey":"bar"}`), // Unknown kind + []byte(`{"myVersionKey":"bar","myKindKey":"TestType1"}`), // Unknown version + []byte(`{"myKindKey":"TestType1"}`), // Missing version + } + for _, b := range badJSONs { + if _, err := runtime.Decode(codec, b); err == nil { + t.Errorf("Did not reject bad json: %s", string(b)) + } + } + badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`) + if err := runtime.DecodeInto(codec, badJSONKindMismatch, &runtimetesting.TestType1{}); err == nil { + t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch) + } + if err := runtime.DecodeInto(codec, []byte(``), &runtimetesting.TestType1{}); err != nil { + t.Errorf("Should allow empty decode: %v", err) + } + if _, _, err := codec.Decode([]byte(``), &schema.GroupVersionKind{Kind: "ExternalInternalSame"}, nil); err == nil { + t.Errorf("Did not give error for empty data with only kind default") + } + if _, _, err := codec.Decode([]byte(`{"myVersionKey":"v1"}`), &schema.GroupVersionKind{Kind: "ExternalInternalSame"}, nil); err != nil { + t.Errorf("Gave error for version and kind default") + } + if _, _, err := codec.Decode([]byte(`{"myKindKey":"ExternalInternalSame"}`), &schema.GroupVersionKind{Version: "v1"}, nil); err != nil { + t.Errorf("Gave error for version and kind default") + } + if _, _, err := codec.Decode([]byte(``), &schema.GroupVersionKind{Kind: "ExternalInternalSame", Version: "v1"}, nil); err != nil { + t.Errorf("Gave error for version and kind defaulted: %v", err) + } + if _, err := runtime.Decode(codec, []byte(``)); err == nil { + t.Errorf("Did not give error for empty data") + } +} + +// Returns a new Scheme set up with the test objects needed by TestDirectCodec. +func GetDirectCodecTestScheme() *runtime.Scheme { + internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Version: "v1"} + + s := runtime.NewScheme() + // Ordinarily, we wouldn't add TestType2, but because this is a test and + // both types are from the same package, we need to get it into the system + // so that converter will match it with ExternalType2. + s.AddKnownTypes(internalGV, &runtimetesting.TestType1{}) + s.AddKnownTypes(externalGV, &runtimetesting.ExternalTestType1{}) + + s.AddUnversionedTypes(externalGV, &metav1.Status{}) + + utilruntime.Must(runtimetesting.RegisterConversions(s)) + return s +} + +func TestDirectCodec(t *testing.T) { + s := GetDirectCodecTestScheme() + cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}, CodecFactoryOptions{Pretty: true, Strict: true})) + info, _ := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON) + serializer := info.Serializer + df := cf.WithoutConversion() + ignoredGV, err := schema.ParseGroupVersion("ignored group/ignored version") + if err != nil { + t.Fatal(err) + } + directEncoder := df.EncoderForVersion(serializer, ignoredGV) + directDecoder := df.DecoderToVersion(serializer, ignoredGV) + out, err := runtime.Encode(directEncoder, &runtimetesting.ExternalTestType1{}) + if err != nil { + t.Fatal(err) + } + if string(out) != `{"myVersionKey":"v1","myKindKey":"ExternalTestType1"}`+"\n" { + t.Fatal(string(out)) + } + a, _, err := directDecoder.Decode(out, nil, nil) + if err != nil { + t.Fatalf("error on Decode: %v", err) + } + e := &runtimetesting.ExternalTestType1{ + MyWeirdCustomEmbeddedVersionKindField: runtimetesting.MyWeirdCustomEmbeddedVersionKindField{ + APIVersion: "v1", + ObjectKind: "ExternalTestType1", + }, + } + if !semantic.DeepEqual(e, a) { + t.Fatalf("expect %v, got %v", e, a) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/encoder_with_allocator_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/encoder_with_allocator_test.go new file mode 100644 index 0000000000..73406d0731 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/encoder_with_allocator_test.go @@ -0,0 +1,139 @@ +/* +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 serializer + +import ( + "crypto/rand" + "io/ioutil" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" +) + +func BenchmarkProtobufEncoder(b *testing.B) { + benchmarkEncodeFor(b, protobuf.NewSerializer(nil, nil)) +} + +func BenchmarkProtobufEncodeWithAllocator(b *testing.B) { + benchmarkEncodeWithAllocatorFor(b, protobuf.NewSerializer(nil, nil)) +} + +func BenchmarkRawProtobufEncoder(b *testing.B) { + benchmarkEncodeFor(b, protobuf.NewRawSerializer(nil, nil)) +} + +func BenchmarkRawProtobufEncodeWithAllocator(b *testing.B) { + benchmarkEncodeWithAllocatorFor(b, protobuf.NewRawSerializer(nil, nil)) +} + +func benchmarkEncodeFor(b *testing.B, target runtime.Encoder) { + for _, tc := range benchTestCases() { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + err := target.Encode(tc.obj, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +func benchmarkEncodeWithAllocatorFor(b *testing.B, target runtime.EncoderWithAllocator) { + for _, tc := range benchTestCases() { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + allocator := &runtime.Allocator{} + for n := 0; n < b.N; n++ { + err := target.EncodeWithAllocator(tc.obj, ioutil.Discard, allocator) + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +type benchTestCase struct { + name string + obj runtime.Object +} + +func benchTestCases() []benchTestCase { + return []benchTestCase{ + { + name: "an obj with 1kB payload", + obj: func() runtime.Object { + carpPayload := make([]byte, 1000) // 1 kB + if _, err := rand.Read(carpPayload); err != nil { + panic(err) + } + return carpWithPayload(carpPayload) + }(), + }, + { + name: "an obj with 10kB payload", + obj: func() runtime.Object { + carpPayload := make([]byte, 10000) // 10 kB + if _, err := rand.Read(carpPayload); err != nil { + panic(err) + } + return carpWithPayload(carpPayload) + }(), + }, + { + name: "an obj with 100kB payload", + obj: func() runtime.Object { + carpPayload := make([]byte, 100000) // 100 kB + if _, err := rand.Read(carpPayload); err != nil { + panic(err) + } + return carpWithPayload(carpPayload) + }(), + }, + { + name: "an obj with 1MB payload", + obj: func() runtime.Object { + carpPayload := make([]byte, 1000000) // 1 MB + if _, err := rand.Read(carpPayload); err != nil { + panic(err) + } + return carpWithPayload(carpPayload) + }(), + }, + } +} + +func carpWithPayload(carpPayload []byte) *testapigroupv1.Carp { + gvk := &schema.GroupVersionKind{Group: "group", Version: "version", Kind: "Carp"} + return &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind}, + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + Spec: testapigroupv1.CarpSpec{ + Subdomain: "carp.k8s.io", + NodeSelector: map[string]string{"payload": string(carpPayload)}, + }, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/collections.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/collections.go new file mode 100644 index 0000000000..3fa6012dde --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/collections.go @@ -0,0 +1,249 @@ +/* +Copyright 2025 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 json + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "sort" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/conversion" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +func streamEncodeCollections(obj runtime.Object, w io.Writer) (bool, error) { + list, ok := obj.(*unstructured.UnstructuredList) + if ok { + return true, newStreamEncoder(w).encodeUnstructuredList(list) + } + if _, ok := obj.(json.Marshaler); ok { + return false, nil + } + typeMeta, listMeta, items, err := getListMeta(obj) + if err == nil { + return true, newStreamEncoder(w).encodeList(typeMeta, listMeta, items) + } + return false, nil +} + +// getListMeta implements list extraction logic for json stream serialization. +// +// Reason for a custom logic instead of reusing accessors from meta package: +// * Validate json tags to prevent incompatibility with json standard package. +// * ListMetaAccessor doesn't distinguish empty from nil value. +// * TypeAccessort reparsing "apiVersion" and serializing it with "{group}/{version}" +func getListMeta(list runtime.Object) (metav1.TypeMeta, metav1.ListMeta, []runtime.Object, error) { + listValue, err := conversion.EnforcePtr(list) + if err != nil { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, err + } + listType := listValue.Type() + if listType.NumField() != 3 { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected ListType to have 3 fields") + } + // TypeMeta + typeMeta, ok := listValue.Field(0).Interface().(metav1.TypeMeta) + if !ok { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected TypeMeta field to have TypeMeta type") + } + if !listType.Field(0).Anonymous { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected TypeMeta json field tag to be embedded`) + } + if jsonTag, jsonTagExists := listType.Field(0).Tag.Lookup("json"); !jsonTagExists { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected TypeMeta json field tag`) + } else if jsonTag != "" && jsonTag != ",inline" { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected TypeMeta json field tag to be "" or ",inline"`) + } + // ListMeta + listMeta, ok := listValue.Field(1).Interface().(metav1.ListMeta) + if !ok { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf("expected ListMeta field to have ListMeta type") + } + if listType.Field(1).Tag.Get("json") != "metadata,omitempty" { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected ListMeta json field tag to be "metadata,omitempty"`) + } + // Items + items, err := meta.ExtractList(list) + if err != nil { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, err + } + if listType.Field(2).Tag.Get("json") != "items" { + return metav1.TypeMeta{}, metav1.ListMeta{}, nil, fmt.Errorf(`expected Items json field tag to be "items"`) + } + return typeMeta, listMeta, items, nil +} + +// streamEncoder encodes JSON values to w, reusing an internal buffer across +// values to avoid the fresh output allocation json.Marshal makes per call. +type streamEncoder struct { + w io.Writer + buf bytes.Buffer + json *json.Encoder +} + +func newStreamEncoder(w io.Writer) *streamEncoder { + e := &streamEncoder{w: w} + e.json = json.NewEncoder(&e.buf) + return e +} + +func (e *streamEncoder) encodeList(typeMeta metav1.TypeMeta, listMeta metav1.ListMeta, items []runtime.Object) error { + // Start + if _, err := e.w.Write([]byte(`{`)); err != nil { + return err + } + + // TypeMeta + if typeMeta.Kind != "" { + if err := e.encodeKeyValuePair("kind", typeMeta.Kind, []byte(",")); err != nil { + return err + } + } + if typeMeta.APIVersion != "" { + if err := e.encodeKeyValuePair("apiVersion", typeMeta.APIVersion, []byte(",")); err != nil { + return err + } + } + + // ListMeta + if err := e.encodeKeyValuePair("metadata", listMeta, []byte(",")); err != nil { + return err + } + + // Items + if err := e.encodeItemsObjectSlice(items); err != nil { + return err + } + + // End + _, err := e.w.Write([]byte("}\n")) + return err +} + +func (e *streamEncoder) encodeItemsObjectSlice(items []runtime.Object) (err error) { + if items == nil { + err := e.encodeKeyValuePair("items", nil, nil) + return err + } + _, err = e.w.Write([]byte(`"items":[`)) + if err != nil { + return err + } + suffix := []byte(",") + for i, item := range items { + if i == len(items)-1 { + suffix = nil + } + err := e.encodeValue(item, suffix) + if err != nil { + return err + } + } + _, err = e.w.Write([]byte("]")) + if err != nil { + return err + } + return err +} + +func (e *streamEncoder) encodeUnstructuredList(list *unstructured.UnstructuredList) error { + _, err := e.w.Write([]byte(`{`)) + if err != nil { + return err + } + keys := make([]string, 0, len(list.Object)+1) + for key := range list.Object { + keys = append(keys, key) + } + if _, exists := list.Object["items"]; !exists { + keys = append(keys, "items") + } + sort.Strings(keys) + + suffix := []byte(",") + for i, key := range keys { + if i == len(keys)-1 { + suffix = nil + } + if key == "items" { + err = e.encodeItemsUnstructuredSlice(list.Items, suffix) + } else { + err = e.encodeKeyValuePair(key, list.Object[key], suffix) + } + if err != nil { + return err + } + } + _, err = e.w.Write([]byte("}\n")) + return err +} + +func (e *streamEncoder) encodeItemsUnstructuredSlice(items []unstructured.Unstructured, suffix []byte) (err error) { + _, err = e.w.Write([]byte(`"items":[`)) + if err != nil { + return err + } + comma := []byte(",") + for i, item := range items { + if i == len(items)-1 { + comma = nil + } + err := e.encodeValue(item.Object, comma) + if err != nil { + return err + } + } + _, err = e.w.Write([]byte("]")) + if err != nil { + return err + } + if len(suffix) > 0 { + _, err = e.w.Write(suffix) + } + return err +} + +func (e *streamEncoder) encodeKeyValuePair(key string, value any, suffix []byte) (err error) { + err = e.encodeValue(key, []byte(":")) + if err != nil { + return err + } + err = e.encodeValue(value, suffix) + if err != nil { + return err + } + return err +} + +func (e *streamEncoder) encodeValue(value any, suffix []byte) error { + e.buf.Reset() + if err := e.json.Encode(value); err != nil { + return err + } + // Encode appends a newline after the value; replace it with the suffix to + // keep the output identical to json.Marshal's. + e.buf.Truncate(e.buf.Len() - 1) + e.buf.Write(suffix) + _, err := e.w.Write(e.buf.Bytes()) + return err +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/collections_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/collections_test.go new file mode 100644 index 0000000000..3fc398961b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/collections_test.go @@ -0,0 +1,869 @@ +/* +Copyright 2025 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 json + +import ( + "bytes" + "fmt" + "math/rand" + "slices" + "testing" + + "github.com/google/go-cmp/cmp" + + "sigs.k8s.io/randfill" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestCollectionsEncoding(t *testing.T) { + t.Run("Normal", func(t *testing.T) { + testCollectionsEncoding(t, NewSerializerWithOptions(DefaultMetaFactory, nil, nil, SerializerOptions{}), false) + }) + t.Run("Streaming", func(t *testing.T) { + testCollectionsEncoding(t, NewSerializerWithOptions(DefaultMetaFactory, nil, nil, SerializerOptions{StreamingCollectionsEncoding: true}), true) + }) +} + +// testCollectionsEncoding should provide comprehensive tests to validate streaming implementation of encoder. +func testCollectionsEncoding(t *testing.T, s *Serializer, streamingEnabled bool) { + var buf writeCountingBuffer + var remainingItems int64 = 1 + // As defined in KEP-5116 we it should include the following scenarios: + // Context: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/5116-streaming-response-encoding#unit-tests + for _, tc := range []struct { + name string + in runtime.Object + cannotStream bool + expect string + // allow provides allowed alternate representations. + // For the json v1->v2 transition, this is used to tolerate changes + // in how malformed input in munged. + allow []string + }{ + // Preserving the distinction between integers and floating-point numbers + { + name: "Struct with floats", + in: &StructWithFloatsList{ + Items: []StructWithFloats{ + { + Int: 1, + Float32: float32(1), + Float64: 1.1, + }, + }, + }, + expect: "{\"metadata\":{},\"items\":[{\"metadata\":{},\"Int\":1,\"Float32\":1,\"Float64\":1.1}]}\n", + }, + { + name: "Unstructured object float", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "int": 1, + "float32": float32(1), + "float64": 1.1, + }, + }, + expect: "{\"float32\":1,\"float64\":1.1,\"int\":1,\"items\":[]}\n", + }, + { + name: "Unstructured items float", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "int": 1, + "float32": float32(1), + "float64": 1.1, + }, + }, + }, + }, + expect: "{\"items\":[{\"float32\":1,\"float64\":1.1,\"int\":1}]}\n", + }, + // Handling structs with duplicate field names (JSON tag names) without producing duplicate keys in the encoded output + { + name: "StructWithDuplicatedTags", + in: &StructWithDuplicatedTagsList{ + Items: []StructWithDuplicatedTags{ + { + Key1: "key1", + Key2: "key2", + }, + }, + }, + expect: "{\"metadata\":{},\"items\":[{\"metadata\":{}}]}\n", + }, + // Encoding Go strings containing invalid UTF-8 sequences without error + { + name: "UnstructuredList object invalid UTF-8 ", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "key": "\x80", // first byte is a continuation byte + }, + }, + expect: "{\"items\":[],\"key\":\"\\ufffd\"}\n", + // Go json/v2 emits U+FFFD as raw UTF-8 bytes rather than the \ufffd escape. + allow: []string{"{\"items\":[],\"key\":\"\ufffd\"}\n"}, + }, + { + name: "UnstructuredList items invalid UTF-8 ", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "key": "\x80", // first byte is a continuation byte + }, + }, + }, + }, + expect: "{\"items\":[{\"key\":\"\\ufffd\"}]}\n", + // Go json/v2 emits U+FFFD as raw UTF-8 bytes rather than the \ufffd escape. + allow: []string{"{\"items\":[{\"key\":\"\ufffd\"}]}\n"}, + }, + // Preserving the distinction between absent, present-but-null, and present-and-empty states for slices and maps + { + name: "CarpList items nil", + in: &testapigroupv1.CarpList{ + Items: nil, + }, + expect: "{\"metadata\":{},\"items\":null}\n", + }, + { + name: "CarpList slice nil", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Status: testapigroupv1.CarpStatus{ + Conditions: nil, + }, + }, + }, + }, + expect: "{\"metadata\":{},\"items\":[{\"metadata\":{},\"spec\":{},\"status\":{}}]}\n", + }, + { + name: "CarpList map nil", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Spec: testapigroupv1.CarpSpec{ + NodeSelector: nil, + }, + }, + }, + }, + expect: "{\"metadata\":{},\"items\":[{\"metadata\":{},\"spec\":{},\"status\":{}}]}\n", + }, + { + name: "UnstructuredList items nil", + in: &unstructured.UnstructuredList{ + Items: nil, + }, + expect: "{\"items\":[]}\n", + }, + { + name: "UnstructuredList items slice nil", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "slice": ([]string)(nil), + }, + }, + }, + }, + expect: "{\"items\":[{\"slice\":null}]}\n", + }, + { + name: "UnstructuredList items map nil", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "map": (map[string]string)(nil), + }, + }, + }, + }, + expect: "{\"items\":[{\"map\":null}]}\n", + }, + { + name: "UnstructuredList object nil", + in: &unstructured.UnstructuredList{ + Object: nil, + }, + expect: "{\"items\":[]}\n", + }, + { + name: "UnstructuredList object slice nil", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "slice": ([]string)(nil), + }, + }, + expect: "{\"items\":[],\"slice\":null}\n", + }, + { + name: "UnstructuredList object map nil", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "map": (map[string]string)(nil), + }, + }, + expect: "{\"items\":[],\"map\":null}\n", + }, + { + name: "CarpList items empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{}, + }, + expect: "{\"metadata\":{},\"items\":[]}\n", + }, + { + name: "CarpList slice empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Status: testapigroupv1.CarpStatus{ + Conditions: []testapigroupv1.CarpCondition{}, + }, + }, + }, + }, + expect: "{\"metadata\":{},\"items\":[{\"metadata\":{},\"spec\":{},\"status\":{}}]}\n", + }, + { + name: "CarpList map empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Spec: testapigroupv1.CarpSpec{ + NodeSelector: map[string]string{}, + }, + }, + }, + }, + expect: "{\"metadata\":{},\"items\":[{\"metadata\":{},\"spec\":{},\"status\":{}}]}\n", + }, + { + name: "UnstructuredList items empty", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{}, + }, + expect: "{\"items\":[]}\n", + }, + { + name: "UnstructuredList items slice empty", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "slice": []string{}, + }, + }, + }, + }, + expect: "{\"items\":[{\"slice\":[]}]}\n", + }, + { + name: "UnstructuredList items map empty", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "map": map[string]string{}, + }, + }, + }, + }, + expect: "{\"items\":[{\"map\":{}}]}\n", + }, + { + name: "UnstructuredList object empty", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{}, + }, + expect: "{\"items\":[]}\n", + }, + { + name: "UnstructuredList object slice empty", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "slice": []string{}, + }, + }, + expect: "{\"items\":[],\"slice\":[]}\n", + }, + { + name: "UnstructuredList object map empty", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "map": map[string]string{}, + }, + }, + expect: "{\"items\":[],\"map\":{}}\n", + }, + // Handling structs implementing MarshallJSON method, especially built-in collection types. + { + name: "List with MarshallJSON cannot be streamed", + in: &ListWithMarshalJSONList{}, + expect: "\"marshallJSON\"\n", + cannotStream: true, + }, + { + name: "Struct with MarshallJSON", + in: &StructWithMarshalJSONList{ + Items: []StructWithMarshalJSON{ + {}, + }, + }, + expect: "{\"metadata\":{},\"items\":[\"marshallJSON\"]}\n", + }, + // Handling raw bytes. + { + name: "Struct with raw bytes", + in: &StructWithRawBytesList{ + Items: []StructWithRawBytes{ + { + Slice: []byte{0x01, 0x02, 0x03}, + Array: [3]byte{0x01, 0x02, 0x03}, + }, + }, + }, + expect: "{\"metadata\":{},\"items\":[{\"metadata\":{},\"Slice\":\"AQID\",\"Array\":[1,2,3]}]}\n", + }, + { + name: "UnstructuredList object raw bytes", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "slice": []byte{0x01, 0x02, 0x03}, + "array": [3]byte{0x01, 0x02, 0x03}, + }, + }, + expect: "{\"array\":[1,2,3],\"items\":[],\"slice\":\"AQID\"}\n", + }, + { + name: "UnstructuredList items raw bytes", + in: &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "slice": []byte{0x01, 0x02, 0x03}, + "array": [3]byte{0x01, 0x02, 0x03}, + }, + }, + }, + }, + expect: "{\"items\":[{\"array\":[1,2,3],\"slice\":\"AQID\"}]}\n", + }, + // Other scenarios: + { + name: "List just kind", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + }, + }, + expect: "{\"kind\":\"List\",\"metadata\":{},\"items\":null}\n", + }, + { + name: "List just apiVersion", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + }, + }, + expect: "{\"apiVersion\":\"v1\",\"metadata\":{},\"items\":null}\n", + }, + { + name: "List no elements", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + }, + Items: []testapigroupv1.Carp{}, + }, + expect: "{\"kind\":\"List\",\"apiVersion\":\"v1\",\"metadata\":{\"resourceVersion\":\"2345\"},\"items\":[]}\n", + }, + { + name: "List one element with continue", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + Continue: "abc", + RemainingItemCount: &remainingItems, + }, + Items: []testapigroupv1.Carp{ + {TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "default", + }}, + }, + }, + expect: "{\"kind\":\"List\",\"apiVersion\":\"v1\",\"metadata\":{\"resourceVersion\":\"2345\",\"continue\":\"abc\",\"remainingItemCount\":1},\"items\":[{\"kind\":\"Carp\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"pod\",\"namespace\":\"default\"},\"spec\":{},\"status\":{}}]}\n", + }, + { + name: "List two elements", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + }, + Items: []testapigroupv1.Carp{ + {TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "default", + }}, + {TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod2", + Namespace: "default2", + }}, + }, + }, + expect: `{"kind":"List","apiVersion":"v1","metadata":{"resourceVersion":"2345"},"items":[{"kind":"Carp","apiVersion":"v1","metadata":{"name":"pod","namespace":"default"},"spec":{},"status":{}},{"kind":"Carp","apiVersion":"v1","metadata":{"name":"pod2","namespace":"default2"},"spec":{},"status":{}}]} +`, + }, + { + name: "List with extra field cannot be streamed", + in: &ListWithAdditionalFields{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + }, + Items: []testapigroupv1.Carp{}, + }, + cannotStream: true, + expect: "{\"kind\":\"List\",\"apiVersion\":\"v1\",\"metadata\":{\"resourceVersion\":\"2345\"},\"items\":[],\"AdditionalField\":0}\n", + }, + { + name: "Not a collection cannot be streamed", + in: &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + }, + cannotStream: true, + expect: "{\"kind\":\"List\",\"apiVersion\":\"v1\",\"metadata\":{},\"spec\":{},\"status\":{}}\n", + }, + { + name: "UnstructuredList empty", + in: &unstructured.UnstructuredList{}, + expect: "{\"items\":[]}\n", + }, + { + name: "UnstructuredList just kind", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"kind": "List"}, + }, + expect: "{\"items\":[],\"kind\":\"List\"}\n", + }, + { + name: "UnstructuredList just apiVersion", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"apiVersion": "v1"}, + }, + expect: "{\"apiVersion\":\"v1\",\"items\":[]}\n", + }, + { + name: "UnstructuredList no elements", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{"resourceVersion": "2345"}}, + Items: []unstructured.Unstructured{}, + }, + expect: "{\"apiVersion\":\"v1\",\"items\":[],\"kind\":\"List\",\"metadata\":{\"resourceVersion\":\"2345\"}}\n", + }, + { + name: "UnstructuredList one element with continue", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{ + "resourceVersion": "2345", + "continue": "abc", + "remainingItemCount": "1", + }}, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "pod", + "namespace": "default", + }, + }, + }, + }, + }, + expect: "{\"apiVersion\":\"v1\",\"items\":[{\"apiVersion\":\"v1\",\"kind\":\"Carp\",\"metadata\":{\"name\":\"pod\",\"namespace\":\"default\"}}],\"kind\":\"List\",\"metadata\":{\"continue\":\"abc\",\"remainingItemCount\":\"1\",\"resourceVersion\":\"2345\"}}\n", + }, + { + name: "UnstructuredList two elements", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{ + "resourceVersion": "2345", + }}, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "pod", + "namespace": "default", + }, + }, + }, + { + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Carp", + "metadata": map[string]interface{}{ + "name": "pod2", + "namespace": "default", + }, + }, + }, + }, + }, + expect: "{\"apiVersion\":\"v1\",\"items\":[{\"apiVersion\":\"v1\",\"kind\":\"Carp\",\"metadata\":{\"name\":\"pod\",\"namespace\":\"default\"}},{\"apiVersion\":\"v1\",\"kind\":\"Carp\",\"metadata\":{\"name\":\"pod2\",\"namespace\":\"default\"}}],\"kind\":\"List\",\"metadata\":{\"resourceVersion\":\"2345\"}}\n", + }, + { + name: "UnstructuredList conflict on items", + in: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"items": []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "name": "pod", + }, + }, + }, + }, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "name": "pod2", + }, + }, + }, + }, + expect: "{\"items\":[{\"name\":\"pod2\"}]}\n", + }, + } { + t.Run(tc.name, func(t *testing.T) { + buf.Reset() + if err := s.Encode(tc.in, &buf); err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("encoded: %s", buf.String()) + if got := buf.String(); got != tc.expect && !slices.Contains(tc.allow, got) { + t.Errorf("not matching:\n%s", cmp.Diff(got, tc.expect)) + } + expectStreaming := !tc.cannotStream && streamingEnabled + if expectStreaming && buf.writeCount <= 1 { + t.Errorf("expected streaming but Write was called only: %d", buf.writeCount) + } + if !expectStreaming && buf.writeCount > 1 { + t.Errorf("expected non-streaming but Write was called more than once: %d", buf.writeCount) + } + }) + } +} + +type StructWithFloatsList struct { + metav1.TypeMeta `json:""` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithFloats `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (l *StructWithFloatsList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithFloats struct { + metav1.TypeMeta `json:""` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Int int + Float32 float32 + Float64 float64 +} + +func (s *StructWithFloats) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithDuplicatedTagsList struct { + metav1.TypeMeta `json:""` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithDuplicatedTags `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (l *StructWithDuplicatedTagsList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithDuplicatedTags struct { + metav1.TypeMeta `json:""` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Key1 string `json:"key"` + Key2 string `json:"key"` //nolint:govet +} + +func (s *StructWithDuplicatedTags) DeepCopyObject() runtime.Object { + return nil +} + +type ListWithMarshalJSONList struct { + metav1.TypeMeta `json:""` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []string `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (l *ListWithMarshalJSONList) DeepCopyObject() runtime.Object { + return nil +} + +func (l *ListWithMarshalJSONList) MarshalJSON() ([]byte, error) { + return []byte(`"marshallJSON"`), nil +} + +type StructWithMarshalJSONList struct { + metav1.TypeMeta `json:""` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithMarshalJSON `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (s *StructWithMarshalJSONList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithMarshalJSON struct { + metav1.TypeMeta `json:""` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` +} + +func (l *StructWithMarshalJSON) DeepCopyObject() runtime.Object { + return nil +} + +func (l *StructWithMarshalJSON) MarshalJSON() ([]byte, error) { + return []byte(`"marshallJSON"`), nil +} + +type StructWithRawBytesList struct { + metav1.TypeMeta `json:""` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []StructWithRawBytes `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func (s *StructWithRawBytesList) DeepCopyObject() runtime.Object { + return nil +} + +type StructWithRawBytes struct { + metav1.TypeMeta `json:""` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Slice []byte + Array [3]byte +} + +func (s *StructWithRawBytes) DeepCopyObject() runtime.Object { + return nil +} + +type ListWithAdditionalFields struct { + metav1.TypeMeta `json:""` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + Items []testapigroupv1.Carp `json:"items" protobuf:"bytes,2,rep,name=items"` + AdditionalField int +} + +func (s *ListWithAdditionalFields) DeepCopyObject() runtime.Object { + return nil +} + +type writeCountingBuffer struct { + writeCount int + bytes.Buffer +} + +func (b *writeCountingBuffer) Write(data []byte) (int, error) { + b.writeCount++ + return b.Buffer.Write(data) +} + +func (b *writeCountingBuffer) Reset() { + b.writeCount = 0 + b.Buffer.Reset() +} + +func TestFuzzCollectionsEncoding(t *testing.T) { + disableFuzzFieldsV1 := func(field *metav1.FieldsV1, c randfill.Continue) {} + fuzzUnstructuredList := func(list *unstructured.UnstructuredList, c randfill.Continue) { + list.Object = map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + c.String(0): c.String(0), + c.String(0): c.Uint64(), + c.String(0): c.Bool(), + "metadata": map[string]interface{}{ + "resourceVersion": fmt.Sprintf("%d", c.Uint64()), + "continue": c.String(0), + "remainingItemCount": fmt.Sprintf("%d", c.Uint64()), + c.String(0): c.String(0), + }} + c.Fill(&list.Items) + } + fuzzMap := func(kvs map[string]interface{}, c randfill.Continue) { + kvs[c.String(0)] = c.Bool() + kvs[c.String(0)] = c.Uint64() + kvs[c.String(0)] = c.String(0) + } + f := randfill.New().Funcs(disableFuzzFieldsV1, fuzzUnstructuredList, fuzzMap) + streamingBuffer := &bytes.Buffer{} + normalSerializer := NewSerializerWithOptions(DefaultMetaFactory, nil, nil, SerializerOptions{StreamingCollectionsEncoding: false}) + normalBuffer := &bytes.Buffer{} + t.Run("CarpList", func(t *testing.T) { + for range 1000 { + list := &testapigroupv1.CarpList{} + f.Fill(list) + streamingBuffer.Reset() + normalBuffer.Reset() + ok, err := streamEncodeCollections(list, streamingBuffer) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !ok { + t.Fatalf("expected streaming encoder to encode %T", list) + } + if err := normalSerializer.Encode(list, normalBuffer); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(normalBuffer.String(), streamingBuffer.String()); diff != "" { + t.Logf("normal: %s", normalBuffer.String()) + t.Logf("streaming: %s", streamingBuffer.String()) + t.Errorf("not matching:\n%s", diff) + } + } + }) + t.Run("UnstructuredList", func(t *testing.T) { + for range 1000 { + list := &unstructured.UnstructuredList{} + f.Fill(list) + streamingBuffer.Reset() + normalBuffer.Reset() + ok, err := streamEncodeCollections(list, streamingBuffer) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !ok { + t.Fatalf("expected streaming encoder to encode %T", list) + } + if err := normalSerializer.Encode(list, normalBuffer); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(normalBuffer.String(), streamingBuffer.String()); diff != "" { + t.Logf("normal: %s", normalBuffer.String()) + t.Logf("streaming: %s", streamingBuffer.String()) + t.Errorf("not matching:\n%s", diff) + } + } + }) +} + +func BenchmarkStreamEncodeCollections(b *testing.B) { + disableFuzzFieldsV1 := func(field *metav1.FieldsV1, c randfill.Continue) {} + fuzzMap := func(kvs map[string]interface{}, c randfill.Continue) { + kvs[c.String(0)] = c.Bool() + kvs[c.String(0)] = c.Uint64() + kvs[c.String(0)] = c.String(0) + } + f := randfill.New().RandSource(rand.NewSource(12345)).Funcs(disableFuzzFieldsV1, fuzzMap) + carpList := &testapigroupv1.CarpList{} + carpList.Items = make([]testapigroupv1.Carp, 1000) + for i := range 1000 { + f.Fill(&carpList.Items[i]) + } + carpList.Kind = "CarpList" + carpList.APIVersion = "testapigroup.k8s.io/v1" + carpList.ResourceVersion = "12345" + unstructuredList := &unstructured.UnstructuredList{} + unstructuredList.Object = map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + "metadata": map[string]interface{}{ + "resourceVersion": "12345", + }, + } + unstructuredList.Items = make([]unstructured.Unstructured, 1000) + for i := range 1000 { + unstrMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&carpList.Items[i]) + if err != nil { + b.Fatalf("failed to convert carp to unstructured: %v", err) + } + unstructuredList.Items[i] = unstructured.Unstructured{Object: unstrMap} + } + b.Run("CarpList", func(b *testing.B) { + var buf bytes.Buffer + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + ok, err := streamEncodeCollections(carpList, &buf) + if err != nil { + b.Fatal(err) + } + if !ok { + b.Fatal("not ok") + } + } + }) + b.Run("UnstructuredList", func(b *testing.B) { + var buf bytes.Buffer + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + ok, err := streamEncodeCollections(unstructuredList, &buf) + if err != nil { + b.Fatal(err) + } + if !ok { + b.Fatal("not ok") + } + } + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go new file mode 100644 index 0000000000..52c814172f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go @@ -0,0 +1,364 @@ +/* +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 json + +import ( + "encoding/json" + "io" + "strconv" + + kjson "sigs.k8s.io/json" + "sigs.k8s.io/yaml" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" + "k8s.io/apimachinery/pkg/util/framer" + utilyaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/klog/v2" +) + +// NewSerializer creates a JSON serializer that handles encoding versioned objects into the proper JSON form. If typer +// is not nil, the object has the group, version, and kind fields set. +// Deprecated: use NewSerializerWithOptions instead. +func NewSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, pretty bool) *Serializer { + return NewSerializerWithOptions(meta, creater, typer, SerializerOptions{false, pretty, false, false}) +} + +// NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer +// is not nil, the object has the group, version, and kind fields set. This serializer supports only the subset of YAML that +// matches JSON, and will error if constructs are used that do not serialize to JSON. +// Deprecated: use NewSerializerWithOptions instead. +func NewYAMLSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper) *Serializer { + return NewSerializerWithOptions(meta, creater, typer, SerializerOptions{true, false, false, false}) +} + +// NewSerializerWithOptions creates a JSON/YAML serializer that handles encoding versioned objects into the proper JSON/YAML +// form. If typer is not nil, the object has the group, version, and kind fields set. Options are copied into the Serializer +// and are immutable. +func NewSerializerWithOptions(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, options SerializerOptions) *Serializer { + return &Serializer{ + meta: meta, + creater: creater, + typer: typer, + options: options, + identifier: identifier(options), + } +} + +// identifier computes Identifier of Encoder based on the given options. +func identifier(options SerializerOptions) runtime.Identifier { + result := map[string]string{ + "name": "json", + "yaml": strconv.FormatBool(options.Yaml), + "pretty": strconv.FormatBool(options.Pretty), + "strict": strconv.FormatBool(options.Strict), + } + identifier, err := json.Marshal(result) + if err != nil { + //nolint:logcheck // Should not be reached. + klog.Fatalf("Failed marshaling identifier for json Serializer: %v", err) + } + return runtime.Identifier(identifier) +} + +// SerializerOptions holds the options which are used to configure a JSON/YAML serializer. +// example: +// (1) To configure a JSON serializer, set `Yaml` to `false`. +// (2) To configure a YAML serializer, set `Yaml` to `true`. +// (3) To configure a strict serializer that can return strictDecodingError, set `Strict` to `true`. +type SerializerOptions struct { + // Yaml: configures the Serializer to work with JSON(false) or YAML(true). + // When `Yaml` is enabled, this serializer only supports the subset of YAML that + // matches JSON, and will error if constructs are used that do not serialize to JSON. + Yaml bool + + // Pretty: configures a JSON enabled Serializer(`Yaml: false`) to produce human-readable output. + // This option is silently ignored when `Yaml` is `true`. + Pretty bool + + // Strict: configures the Serializer to return strictDecodingError's when duplicate fields are present decoding JSON or YAML. + // Note that enabling this option is not as performant as the non-strict variant, and should not be used in fast paths. + Strict bool + + // StreamingCollectionsEncoding enables encoding collection, one item at the time, drastically reducing memory needed. + StreamingCollectionsEncoding bool +} + +// Serializer handles encoding versioned objects into the proper JSON form +type Serializer struct { + meta MetaFactory + options SerializerOptions + creater runtime.ObjectCreater + typer runtime.ObjectTyper + + identifier runtime.Identifier +} + +// Serializer implements Serializer +var _ runtime.Serializer = &Serializer{} +var _ recognizer.RecognizingDecoder = &Serializer{} + +// gvkWithDefaults returns group kind and version defaulting from provided default +func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind { + if len(actual.Kind) == 0 { + actual.Kind = defaultGVK.Kind + } + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = defaultGVK.Group + actual.Version = defaultGVK.Version + } + if len(actual.Version) == 0 && actual.Group == defaultGVK.Group { + actual.Version = defaultGVK.Version + } + return actual +} + +// Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then +// load that data into an object matching the desired schema kind or the provided into. +// If into is *runtime.Unknown, the raw data will be extracted and no decoding will be performed. +// If into is not registered with the typer, then the object will be straight decoded using normal JSON/YAML unmarshalling. +// If into is provided and the original data is not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. +// If into is nil or data's gvk different from into's gvk, it will generate a new Object with ObjectCreater.New(gvk) +// On success or most errors, the method will return the calculated schema kind. +// The gvk calculate priority will be originalData > default gvk > into +func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + data := originalData + if s.options.Yaml { + altered, err := yaml.YAMLToJSON(data) + if err != nil { + return nil, nil, err + } + data = altered + } + + actual, err := s.meta.Interpret(data) + if err != nil { + return nil, nil, err + } + + if gvk != nil { + *actual = gvkWithDefaults(*actual, *gvk) + } + + if unk, ok := into.(*runtime.Unknown); ok && unk != nil { + unk.Raw = originalData + unk.ContentType = runtime.ContentTypeJSON + unk.GetObjectKind().SetGroupVersionKind(*actual) + return unk, actual, nil + } + + if into != nil { + _, isUnstructured := into.(runtime.Unstructured) + types, _, err := s.typer.ObjectKinds(into) + switch { + case runtime.IsNotRegisteredError(err), isUnstructured: + strictErrs, err := s.unmarshal(into, data, originalData) + if err != nil { + return nil, actual, err + } + + // when decoding directly into a provided unstructured object, + // extract the actual gvk decoded from the provided data, + // and ensure it is non-empty. + if isUnstructured { + *actual = into.GetObjectKind().GroupVersionKind() + if len(actual.Kind) == 0 { + return nil, actual, runtime.NewMissingKindErr(string(originalData)) + } + // TODO(109023): require apiVersion here as well once unstructuredJSONScheme#Decode does + } + + if len(strictErrs) > 0 { + return into, actual, runtime.NewStrictDecodingError(strictErrs) + } + return into, actual, nil + case err != nil: + return nil, actual, err + default: + *actual = gvkWithDefaults(*actual, types[0]) + } + } + + if len(actual.Kind) == 0 { + return nil, actual, runtime.NewMissingKindErr(string(originalData)) + } + if len(actual.Version) == 0 { + return nil, actual, runtime.NewMissingVersionErr(string(originalData)) + } + + // use the target if necessary + obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into) + if err != nil { + return nil, actual, err + } + + strictErrs, err := s.unmarshal(obj, data, originalData) + if err != nil { + return nil, actual, err + } else if len(strictErrs) > 0 { + return obj, actual, runtime.NewStrictDecodingError(strictErrs) + } + return obj, actual, nil +} + +// Encode serializes the provided object to the given writer. +func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { + if co, ok := obj.(runtime.CacheableObject); ok { + return co.CacheEncode(s.Identifier(), s.doEncode, w) + } + return s.doEncode(obj, w) +} + +func (s *Serializer) doEncode(obj runtime.Object, w io.Writer) error { + if s.options.Yaml { + json, err := json.Marshal(obj) + if err != nil { + return err + } + data, err := yaml.JSONToYAML(json) + if err != nil { + return err + } + _, err = w.Write(data) + return err + } + + if s.options.Pretty { + data, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + _, err = w.Write(data) + return err + } + if s.options.StreamingCollectionsEncoding { + ok, err := streamEncodeCollections(obj, w) + if err != nil { + return err + } + if ok { + return nil + } + } + encoder := json.NewEncoder(w) + return encoder.Encode(obj) +} + +// IsStrict indicates whether the serializer +// uses strict decoding or not +func (s *Serializer) IsStrict() bool { + return s.options.Strict +} + +func (s *Serializer) unmarshal(into runtime.Object, data, originalData []byte) (strictErrs []error, err error) { + // If the deserializer is non-strict, return here. + if !s.options.Strict { + if err := kjson.UnmarshalCaseSensitivePreserveInts(data, into); err != nil { + return nil, err + } + return nil, nil + } + + if s.options.Yaml { + // In strict mode pass the original data through the YAMLToJSONStrict converter. + // This is done to catch duplicate fields in YAML that would have been dropped in the original YAMLToJSON conversion. + // TODO: rework YAMLToJSONStrict to return warnings about duplicate fields without terminating so we don't have to do this twice. + _, err := yaml.YAMLToJSONStrict(originalData) + if err != nil { + strictErrs = append(strictErrs, err) + } + } + + var strictJSONErrs []error + if u, isUnstructured := into.(runtime.Unstructured); isUnstructured { + // Unstructured is a custom unmarshaler that gets delegated + // to, so in order to detect strict JSON errors we need + // to unmarshal directly into the object. + m := map[string]interface{}{} + strictJSONErrs, err = kjson.UnmarshalStrict(data, &m) + u.SetUnstructuredContent(m) + } else { + strictJSONErrs, err = kjson.UnmarshalStrict(data, into) + } + if err != nil { + // fatal decoding error, not due to strictness + return nil, err + } + strictErrs = append(strictErrs, strictJSONErrs...) + return strictErrs, nil +} + +// Identifier implements runtime.Encoder interface. +func (s *Serializer) Identifier() runtime.Identifier { + return s.identifier +} + +// RecognizesData implements the RecognizingDecoder interface. +func (s *Serializer) RecognizesData(data []byte) (ok, unknown bool, err error) { + if s.options.Yaml { + // we could potentially look for '---' + return false, true, nil + } + return utilyaml.IsJSONBuffer(data), false, nil +} + +// Framer is the default JSON framing behavior, with newlines delimiting individual objects. +var Framer = jsonFramer{} + +type jsonFramer struct{} + +// NewFrameWriter implements stream framing for this serializer +func (jsonFramer) NewFrameWriter(w io.Writer) io.Writer { + // we can write JSON objects directly to the writer, because they are self-framing + return w +} + +// NewFrameReader implements stream framing for this serializer +func (jsonFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { + // we need to extract the JSON chunks of data to pass to Decode() + return framer.NewJSONFramedReader(r) +} + +// YAMLFramer is the default JSON framing behavior, with newlines delimiting individual objects. +var YAMLFramer = yamlFramer{} + +type yamlFramer struct{} + +// NewFrameWriter implements stream framing for this serializer +func (yamlFramer) NewFrameWriter(w io.Writer) io.Writer { + return yamlFrameWriter{w} +} + +// NewFrameReader implements stream framing for this serializer +func (yamlFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { + // extract the YAML document chunks directly + return utilyaml.NewDocumentDecoder(r) +} + +type yamlFrameWriter struct { + w io.Writer +} + +// Write separates each document with the YAML document separator (`---` followed by line +// break). Writers must write well formed YAML documents (include a final line break). +func (w yamlFrameWriter) Write(data []byte) (n int, err error) { + if _, err := w.w.Write([]byte("---\n")); err != nil { + return 0, err + } + return w.w.Write(data) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json_limit_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json_limit_test.go new file mode 100644 index 0000000000..8214d293c6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json_limit_test.go @@ -0,0 +1,169 @@ +/* +Copyright 2019 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 json + +import ( + gojson "encoding/json" + "strings" + "testing" + + utiljson "k8s.io/apimachinery/pkg/util/json" +) + +type testcase struct { + name string + data []byte + checkErr func(t testing.TB, err error) + + benchmark bool +} + +func testcases() []testcase { + // verify we got an error of some kind + nonNilError := func(t testing.TB, err error) { + if err == nil { + t.Errorf("expected error, got none") + } + } + // verify the parse completed, either with success or a max depth error + successOrMaxDepthError := func(t testing.TB, err error) { + if err != nil && !strings.Contains(err.Error(), "max depth") { + t.Errorf("expected success or error containing 'max depth', got: %v", err) + } + } + + return []testcase{ + { + name: "3MB of deeply nested slices", + checkErr: successOrMaxDepthError, + data: []byte(`{"a":` + strings.Repeat(`[`, 3*1024*1024/2) + strings.Repeat(`]`, 3*1024*1024/2) + "}"), + }, + { + name: "3MB of unbalanced nested slices", + checkErr: nonNilError, + data: []byte(`{"a":` + strings.Repeat(`[`, 3*1024*1024)), + }, + { + name: "3MB of deeply nested maps", + checkErr: successOrMaxDepthError, + data: []byte(strings.Repeat(`{"":`, 3*1024*1024/5/2) + "{}" + strings.Repeat(`}`, 3*1024*1024/5/2)), + }, + { + name: "3MB of unbalanced nested maps", + checkErr: nonNilError, + data: []byte(strings.Repeat(`{"":`, 3*1024*1024/5)), + }, + { + name: "3MB of empty slices", + data: []byte(`{"a":[` + strings.Repeat(`[],`, 3*1024*1024/3-2) + `[]]}`), + benchmark: true, + }, + { + name: "3MB of slices", + data: []byte(`{"a":[` + strings.Repeat(`[0],`, 3*1024*1024/4-2) + `[0]]}`), + benchmark: true, + }, + { + name: "3MB of empty maps", + data: []byte(`{"a":[` + strings.Repeat(`{},`, 3*1024*1024/3-2) + `{}]}`), + benchmark: true, + }, + { + name: "3MB of maps", + data: []byte(`{"a":[` + strings.Repeat(`{"a":0},`, 3*1024*1024/8-2) + `{"a":0}]}`), + benchmark: true, + }, + { + name: "3MB of ints", + data: []byte(`{"a":[` + strings.Repeat(`0,`, 3*1024*1024/2-2) + `0]}`), + benchmark: true, + }, + { + name: "3MB of floats", + data: []byte(`{"a":[` + strings.Repeat(`0.0,`, 3*1024*1024/4-2) + `0.0]}`), + benchmark: true, + }, + { + name: "3MB of bools", + data: []byte(`{"a":[` + strings.Repeat(`true,`, 3*1024*1024/5-2) + `true]}`), + benchmark: true, + }, + { + name: "3MB of empty strings", + data: []byte(`{"a":[` + strings.Repeat(`"",`, 3*1024*1024/3-2) + `""]}`), + benchmark: true, + }, + { + name: "3MB of strings", + data: []byte(`{"a":[` + strings.Repeat(`"abcdefghijklmnopqrstuvwxyz012",`, 3*1024*1024/30-2) + `"abcdefghijklmnopqrstuvwxyz012"]}`), + benchmark: true, + }, + { + name: "3MB of nulls", + data: []byte(`{"a":[` + strings.Repeat(`null,`, 3*1024*1024/5-2) + `null]}`), + benchmark: true, + }, + } +} + +var decoders = map[string]func([]byte, interface{}) error{ + "gojson": gojson.Unmarshal, + "utiljson": utiljson.Unmarshal, +} + +func TestJSONLimits(t *testing.T) { + for _, tc := range testcases() { + if tc.benchmark { + continue + } + t.Run(tc.name, func(t *testing.T) { + for decoderName, decoder := range decoders { + t.Run(decoderName, func(t *testing.T) { + v := map[string]interface{}{} + err := decoder(tc.data, &v) + + if tc.checkErr != nil { + tc.checkErr(t, err) + } else if err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } + }) + } +} + +func BenchmarkJSONLimits(b *testing.B) { + for _, tc := range testcases() { + b.Run(tc.name, func(b *testing.B) { + for decoderName, decoder := range decoders { + b.Run(decoderName, func(b *testing.B) { + for i := 0; i < b.N; i++ { + v := map[string]interface{}{} + err := decoder(tc.data, &v) + + if tc.checkErr != nil { + tc.checkErr(b, err) + } else if err != nil { + b.Errorf("unexpected error: %v", err) + } + } + }) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go new file mode 100644 index 0000000000..b21479a53f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go @@ -0,0 +1,1091 @@ +/* +Copyright 2015 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 json_test + +import ( + "bytes" + "fmt" + "reflect" + "strings" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" + "k8s.io/apimachinery/pkg/util/diff" + + "github.com/google/go-cmp/cmp" +) + +type testDecodable struct { + metav1.TypeMeta `json:""` + + Other string + Value int `json:"value"` + Spec DecodableSpec `json:"spec"` + Interface interface{} `json:"interface"` +} + +// DecodableSpec has 15 fields. +type DecodableSpec struct { + A int `json:"A"` + B int `json:"B"` + C int `json:"C"` + D int `json:"D"` + E int `json:"E"` + F int `json:"F"` + G int `json:"G"` + H int `json:"h"` + I int `json:"i"` + J int `json:"j"` + K int `json:"k"` + L int `json:"l"` + M int `json:"m"` + N int `json:"n"` + O int `json:"o"` +} + +func (d *testDecodable) DeepCopyObject() runtime.Object { + if d == nil { + return nil + } + out := new(testDecodable) + d.DeepCopyInto(out) + return out +} +func (d *testDecodable) DeepCopyInto(out *testDecodable) { + *out = *d + out.Other = d.Other + out.Value = d.Value + out.Spec = d.Spec + out.Interface = d.Interface + return +} + +type testDecodeCoercion struct { + metav1.TypeMeta `json:""` + + Bool bool `json:"bool"` + + Int int `json:"int"` + Int32 int `json:"int32"` + Int64 int `json:"int64"` + + Float32 float32 `json:"float32"` + Float64 float64 `json:"float64"` + + String string `json:"string"` + + Struct testDecodable `json:"struct"` + + Array []string `json:"array"` + Map map[string]string `json:"map"` +} + +func (d *testDecodeCoercion) DeepCopyObject() runtime.Object { + if d == nil { + return nil + } + out := new(testDecodeCoercion) + d.DeepCopyInto(out) + return out +} +func (d *testDecodeCoercion) DeepCopyInto(out *testDecodeCoercion) { + *out = *d + return +} + +func TestDecode(t *testing.T) { + type testCase struct { + creater runtime.ObjectCreater + typer runtime.ObjectTyper + yaml bool + pretty bool + strict bool + + data []byte + defaultGVK *schema.GroupVersionKind + into runtime.Object + + errFn func(error) bool + expectedObject runtime.Object + expectedGVK *schema.GroupVersionKind + } + + testCases := []testCase{ + // missing metadata without into, typed creater + { + data: []byte("{}"), + + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + }, + { + data: []byte("{}"), + + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"kind":"Foo"}`), + + expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, + }, + { + data: []byte(`{"kind":"Foo"}`), + + expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"apiVersion":"foo/v1"}`), + + expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + }, + { + data: []byte(`{"apiVersion":"foo/v1"}`), + + expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &testDecodable{}}, + + expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "/v1", Kind: "Foo"}}, + expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, + }, + { + data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &testDecodable{}}, + + expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "/v1", Kind: "Foo"}}, + expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, + strict: true, + }, + + // missing metadata with unstructured into + { + data: []byte("{}"), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{}, + + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + }, + { + data: []byte("{}"), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{}, + + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{}, + + expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"kind": "Foo"}}, + // TODO(109023): expect this to error; unstructured decoding currently only requires kind to be set, not apiVersion + }, + { + data: []byte(`{"kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{}, + + expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"kind": "Foo"}}, + strict: true, + // TODO(109023): expect this to error; unstructured decoding currently only requires kind to be set, not apiVersion + }, + + { + data: []byte(`{"apiVersion":"foo/v1"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{}, + + expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + }, + { + data: []byte(`{"apiVersion":"foo/v1"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{}, + + expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{}, + + expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, + }, + { + data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{}, + + expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, + strict: true, + }, + + // missing metadata with unstructured into providing metadata + { + data: []byte("{}"), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, + + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + }, + { + data: []byte("{}"), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, + + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, + + expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"kind": "Foo"}}, + // TODO(109023): expect this to error; unstructured decoding currently only requires kind to be set, not apiVersion + }, + { + data: []byte(`{"kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, + + expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"kind": "Foo"}}, + strict: true, + // TODO(109023): expect this to error; unstructured decoding currently only requires kind to be set, not apiVersion + }, + + { + data: []byte(`{"apiVersion":"foo/v1"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, + + expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + }, + { + data: []byte(`{"apiVersion":"foo/v1"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, + + expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, + + expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, + }, + { + data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + into: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "into/v1", "kind": "Into"}}, + + expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, + strict: true, + }, + + // missing metadata without into, unstructured creater + { + data: []byte("{}"), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &unstructured.Unstructured{}}, + + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + }, + { + data: []byte("{}"), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &unstructured.Unstructured{}}, + + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &unstructured.Unstructured{}}, + + expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, + }, + { + data: []byte(`{"kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &unstructured.Unstructured{}}, + + expectedGVK: &schema.GroupVersionKind{Kind: "Foo"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"apiVersion":"foo/v1"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &unstructured.Unstructured{}}, + + expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + }, + { + data: []byte(`{"apiVersion":"foo/v1"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &unstructured.Unstructured{}}, + + expectedGVK: &schema.GroupVersionKind{Group: "foo", Version: "v1"}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'Kind' is missing in") }, + strict: true, + }, + + { + data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &unstructured.Unstructured{}}, + + expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, + }, + { + data: []byte(`{"apiVersion":"/v1","kind":"Foo"}`), + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + creater: &mockCreater{obj: &unstructured.Unstructured{}}, + + expectedGVK: &schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Foo"}, + expectedObject: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "/v1", "kind": "Foo"}}, + strict: true, + }, + + // creator errors + { + data: []byte("{}"), + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + creater: &mockCreater{err: fmt.Errorf("fake error")}, + + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + errFn: func(err error) bool { return err.Error() == "fake error" }, + }, + { + data: []byte("{}"), + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + creater: &mockCreater{err: fmt.Errorf("fake error")}, + + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + errFn: func(err error) bool { return err.Error() == "fake error" }, + }, + // creator typed + { + data: []byte("{}"), + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + }, + { + data: []byte("{}"), + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + strict: true, + }, + + // version without group is not defaulted + { + data: []byte(`{"apiVersion":"blah"}`), + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "", Version: "blah"}, + }, + // group without version is defaulted + { + data: []byte(`{"apiVersion":"other/"}`), + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "other/"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + }, + // group version, kind is defaulted + { + data: []byte(`{"apiVersion":"other1/blah1"}`), + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "other1/blah1"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other1", Version: "blah1"}, + }, + // gvk all provided then not defaulted at all + { + data: []byte(`{"kind":"Test","apiVersion":"other/blah"}`), + defaultGVK: &schema.GroupVersionKind{Kind: "Test1", Group: "other1", Version: "blah1"}, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + }, + //gvk defaulting if kind not provided in data and defaultGVK use into's kind + { + data: []byte(`{"apiVersion":"b1/c1"}`), + into: &testDecodable{TypeMeta: metav1.TypeMeta{Kind: "a3", APIVersion: "b1/c1"}}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}}, + defaultGVK: nil, + creater: &mockCreater{obj: &testDecodable{}}, + expectedObject: &testDecodable{TypeMeta: metav1.TypeMeta{Kind: "a3", APIVersion: "b1/c1"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "a3", Group: "b1", Version: "c1"}, + }, + + // accept runtime.Unknown as into and bypass creator + { + data: []byte(`{}`), + into: &runtime.Unknown{}, + + expectedGVK: &schema.GroupVersionKind{}, + expectedObject: &runtime.Unknown{ + Raw: []byte(`{}`), + ContentType: runtime.ContentTypeJSON, + }, + }, + { + data: []byte(`{"test":"object"}`), + into: &runtime.Unknown{}, + + expectedGVK: &schema.GroupVersionKind{}, + expectedObject: &runtime.Unknown{ + Raw: []byte(`{"test":"object"}`), + ContentType: runtime.ContentTypeJSON, + }, + }, + { + data: []byte(`{"test":"object"}`), + into: &runtime.Unknown{}, + defaultGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, + Raw: []byte(`{"test":"object"}`), + ContentType: runtime.ContentTypeJSON, + }, + }, + + // unregistered objects can be decoded into directly + { + data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`), + into: &testDecodable{}, + typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodable{ + TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, + Other: "test", + Value: 1, + }, + }, + // registered types get defaulted by the into object kind + { + data: []byte(`{"value":1,"Other":"test"}`), + into: &testDecodable{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodable{ + Other: "test", + Value: 1, + }, + }, + // registered types get defaulted by the into object kind even without version, but return an error + { + data: []byte(`{"value":1,"Other":"test"}`), + into: &testDecodable{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: ""}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: ""}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "Object 'apiVersion' is missing in") }, + expectedObject: &testDecodable{ + Other: "test", + Value: 1, + }, + }, + // Error on invalid number + { + data: []byte(`{"kind":"Test","apiVersion":"other/blah","interface":1e1000}`), + creater: &mockCreater{obj: &testDecodable{}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `json: cannot unmarshal number 1e1000 into Go struct field testDecodable.interface of type float64`) + }, + }, + // Unmarshalling is case-sensitive + { + // "VaLue" should have been "value" + data: []byte(`{"kind":"Test","apiVersion":"other/blah","VaLue":1,"Other":"test"}`), + into: &testDecodable{}, + typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodable{ + TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, + Other: "test", + }, + }, + // Unmarshalling is case-sensitive for big struct. + { + // "b" should have been "B", "I" should have been "i" + data: []byte(`{"kind":"Test","apiVersion":"other/blah","spec": {"A": 1, "b": 2, "h": 3, "I": 4}}`), + into: &testDecodable{}, + typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodable{ + TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, + Spec: DecodableSpec{A: 1, H: 3}, + }, + }, + // Unknown fields should return an error from the strict JSON deserializer. + { + data: []byte(`{"unknown": 1}`), + into: &testDecodable{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `unknown field "unknown"`) + }, + strict: true, + }, + // Unknown fields should return an error from the strict YAML deserializer. + { + data: []byte("unknown: 1\n"), + into: &testDecodable{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `unknown field "unknown"`) + }, + yaml: true, + strict: true, + }, + // Duplicate fields should return an error from the strict JSON deserializer. + { + data: []byte(`{"value":1,"value":1}`), + into: &testDecodable{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `duplicate field "value"`) + }, + strict: true, + }, + // Duplicate fields should return an error from the strict YAML deserializer. + { + data: []byte("value: 1\n" + + "value: 1\n"), + into: &testDecodable{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `"value" already set in map`) + }, + yaml: true, + strict: true, + }, + // Duplicate fields should return an error from the strict JSON deserializer for unstructured. + { + data: []byte(`{"kind":"Custom","value":1,"value":1}`), + into: &unstructured.Unstructured{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Custom"}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `duplicate field "value"`) + }, + strict: true, + }, + // Duplicate fields should return an error from the strict YAML deserializer for unstructured. + { + data: []byte("kind: Custom\n" + + "value: 1\n" + + "value: 1\n"), + into: &unstructured.Unstructured{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Custom"}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `"value" already set in map`) + }, + yaml: true, + strict: true, + }, + // Strict JSON decode into unregistered objects directly. + { + data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`), + into: &testDecodable{}, + typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodable{ + TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, + Other: "test", + Value: 1, + }, + strict: true, + }, + // Strict YAML decode into unregistered objects directly. + { + data: []byte("kind: Test\n" + + "apiVersion: other/blah\n" + + "value: 1\n" + + "Other: test\n"), + into: &testDecodable{}, + typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind("mock", schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodable{ + TypeMeta: metav1.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, + Other: "test", + Value: 1, + }, + yaml: true, + strict: true, + }, + // Valid strict JSON decode without GVK. + { + data: []byte(`{"value":1234}`), + into: &testDecodable{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodable{ + Value: 1234, + }, + strict: true, + }, + // Valid strict YAML decode without GVK. + { + data: []byte("value: 1234\n"), + into: &testDecodable{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodable{ + Value: 1234, + }, + yaml: true, + strict: true, + }, + // Invalid strict JSON, results in json parse error: + { + data: []byte("foo"), + into: &unstructured.Unstructured{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `json parse error: invalid character 'o'`) + }, + strict: true, + }, + // empty JSON strict, results in missing kind error + { + data: []byte("{}"), + into: &unstructured.Unstructured{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `Object 'Kind' is missing`) + }, + strict: true, + }, + // coerce from null + { + data: []byte(`{"bool":null,"int":null,"int32":null,"int64":null,"float32":null,"float64":null,"string":null,"array":null,"map":null,"struct":null}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{}, + strict: true, + }, + { + data: []byte(`{"bool":null,"int":null,"int32":null,"int64":null,"float32":null,"float64":null,"string":null,"array":null,"map":null,"struct":null}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{}, + yaml: true, + strict: true, + }, + // coerce from string + { + data: []byte(`{"string":""}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{}, + strict: true, + }, + { + data: []byte(`{"string":""}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{}, + yaml: true, + strict: true, + }, + // coerce from array + { + data: []byte(`{"array":[]}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{Array: []string{}}, + strict: true, + }, + { + data: []byte(`{"array":[]}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{Array: []string{}}, + yaml: true, + strict: true, + }, + // coerce from map + { + data: []byte(`{"map":{},"struct":{}}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{Map: map[string]string{}}, + strict: true, + }, + { + data: []byte(`{"map":{},"struct":{}}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{Map: map[string]string{}}, + yaml: true, + strict: true, + }, + // coerce from int + { + data: []byte(`{"int":1,"int32":1,"int64":1,"float32":1,"float64":1}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{Int: 1, Int32: 1, Int64: 1, Float32: 1, Float64: 1}, + strict: true, + }, + { + data: []byte(`{"int":1,"int32":1,"int64":1,"float32":1,"float64":1}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{Int: 1, Int32: 1, Int64: 1, Float32: 1, Float64: 1}, + yaml: true, + strict: true, + }, + // coerce from float + { + data: []byte(`{"float32":1.0,"float64":1.0}`), + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{Float32: 1, Float64: 1}, + strict: true, + }, + { + data: []byte(`{"int":1.0,"int32":1.0,"int64":1.0,"float32":1.0,"float64":1.0}`), // floating point gets dropped in yaml -> json step + into: &testDecodeCoercion{}, + typer: &mockTyper{gvk: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + expectedObject: &testDecodeCoercion{Int: 1, Int32: 1, Int64: 1, Float32: 1, Float64: 1}, + yaml: true, + strict: true, + }, + } + + logTestCase := func(t *testing.T, tc testCase) { + t.Logf("data=%s\n\tinto=%T, yaml=%v, strict=%v", string(tc.data), tc.into, tc.yaml, tc.strict) + } + + for i, test := range testCases { + var s runtime.Serializer + if test.yaml { + s = json.NewSerializerWithOptions(json.DefaultMetaFactory, test.creater, test.typer, json.SerializerOptions{Yaml: test.yaml, Pretty: false, Strict: test.strict}) + } else { + s = json.NewSerializerWithOptions(json.DefaultMetaFactory, test.creater, test.typer, json.SerializerOptions{Yaml: test.yaml, Pretty: test.pretty, Strict: test.strict}) + } + obj, gvk, err := s.Decode([]byte(test.data), test.defaultGVK, test.into) + + if !reflect.DeepEqual(test.expectedGVK, gvk) { + logTestCase(t, test) + t.Errorf("%d: unexpected GVK: %v", i, gvk) + } + + switch { + case err == nil && test.errFn != nil: + logTestCase(t, test) + t.Errorf("%d: failed: not getting the expected error", i) + continue + case err != nil && test.errFn == nil: + logTestCase(t, test) + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + logTestCase(t, test) + t.Errorf("%d: failed: %v", i, err) + } + if !runtime.IsStrictDecodingError(err) && obj != nil { + logTestCase(t, test) + t.Errorf("%d: should have returned nil object", i) + } + continue + } + + if test.into != nil && test.into != obj { + logTestCase(t, test) + t.Errorf("%d: expected into to be returned: %v", i, obj) + continue + } + + if !reflect.DeepEqual(test.expectedObject, obj) { + logTestCase(t, test) + t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) + } + } +} + +func TestCacheableObject(t *testing.T) { + gvk := schema.GroupVersionKind{Group: "group", Version: "version", Kind: "MockCacheableObject"} + creater := &mockCreater{obj: &runtimetesting.MockCacheableObject{}} + typer := &mockTyper{gvk: &gvk} + serializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, creater, typer, json.SerializerOptions{}) + + runtimetesting.CacheableObjectTest(t, serializer) +} + +type mockCreater struct { + apiVersion string + kind string + err error + obj runtime.Object +} + +func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) { + c.apiVersion, c.kind = kind.GroupVersion().String(), kind.Kind + return c.obj, c.err +} + +type mockTyper struct { + gvk *schema.GroupVersionKind + err error +} + +func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { + if t.gvk == nil { + return nil, false, t.err + } + return []schema.GroupVersionKind{*t.gvk}, false, t.err +} + +func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { + return false +} + +type testEncodableDuplicateTag struct { + metav1.TypeMeta `json:""` + + A1 int `json:"a"` + A2 int `json:"a"` //nolint:govet // This is intentional to test that the encoder will not encode two map entries with the same key. +} + +func (testEncodableDuplicateTag) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +type testEncodableTagMatchesUntaggedName struct { + metav1.TypeMeta `json:""` + + A int + TaggedA int `json:"A"` +} + +func (testEncodableTagMatchesUntaggedName) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +type staticTextMarshaler int + +func (staticTextMarshaler) MarshalText() ([]byte, error) { + return []byte("static"), nil +} + +type testEncodableMap[K comparable] map[K]interface{} + +func (testEncodableMap[K]) GetObjectKind() schema.ObjectKind { + panic("unimplemented") +} + +func (testEncodableMap[K]) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +func TestEncode(t *testing.T) { + for _, tc := range []struct { + name string + in runtime.Object + want []byte + }{ + // The Go visibility rules for struct fields are amended for JSON when deciding + // which field to marshal or unmarshal. If there are multiple fields at the same + // level, and that level is the least nested (and would therefore be the nesting + // level selected by the usual Go rules), the following extra rules apply: + + // 1) Of those fields, if any are JSON-tagged, only tagged fields are considered, + // even if there are multiple untagged fields that would otherwise conflict. + { + name: "only tagged field is considered if any are tagged", + in: &testEncodableTagMatchesUntaggedName{ + A: 1, + TaggedA: 2, + }, + want: []byte("{\"A\":2}\n"), + }, + // 2) If there is exactly one field (tagged or not according to the first rule), + // that is selected. + // 3) Otherwise there are multiple fields, and all are ignored; no error occurs. + { + name: "all duplicate fields are ignored", + in: &testEncodableDuplicateTag{}, + want: []byte("{}\n"), + }, + { + name: "text marshaler keys can compare inequal but serialize to duplicates", + in: testEncodableMap[staticTextMarshaler]{ + staticTextMarshaler(1): nil, + staticTextMarshaler(2): nil, + }, + want: []byte("{\"static\":null,\"static\":null}\n"), + }, + { + name: "time.Time keys can compare inequal but serialize to duplicates because time.Time implements TextMarshaler", + in: testEncodableMap[time.Time]{ + time.Date(2222, 11, 30, 23, 59, 58, 57, time.UTC): nil, + time.Date(2222, 11, 30, 23, 59, 58, 57, time.FixedZone("", 0)): nil, + }, + want: []byte("{\"2222-11-30T23:59:58.000000057Z\":null,\"2222-11-30T23:59:58.000000057Z\":null}\n"), + }, + { + name: "metav1.Time keys can compare inequal but serialize to duplicates because metav1.Time embeds time.Time which implements TextMarshaler", + in: testEncodableMap[metav1.Time]{ + metav1.Date(2222, 11, 30, 23, 59, 58, 57, time.UTC): nil, + metav1.Date(2222, 11, 30, 23, 59, 58, 57, time.FixedZone("", 0)): nil, + }, + want: []byte("{\"2222-11-30T23:59:58.000000057Z\":null,\"2222-11-30T23:59:58.000000057Z\":null}\n"), + }, + } { + t.Run(tc.name, func(t *testing.T) { + var dst bytes.Buffer + s := json.NewSerializerWithOptions(json.DefaultMetaFactory, nil, nil, json.SerializerOptions{}) + if err := s.Encode(tc.in, &dst); err != nil { + t.Errorf("unexpected error: %v", err) + } + if diff := cmp.Diff(tc.want, dst.Bytes()); diff != "" { + t.Errorf("unexpected output:\n%s", diff) + } + }) + } +} + +// TestRoundtripUnstructuredFloat64 demonstrates that encoding a fractionless float64 value to JSON +// then decoding into interface{} can produce a value with concrete type int64. This is a +// consequence of two specific behaviors. First, there is nothing in the JSON encoding of a +// fractionless float64 value to distinguish it from the JSON encoding of an integer value. Second, +// if, when unmarshaling into interface{}, the decoder encounters a JSON number with no decimal +// point in the input, it produces a value with concrete type int64 as long as the number can be +// precisely represented by an int64. +func TestRoundtripUnstructuredFractionlessFloat64(t *testing.T) { + s := json.NewSerializerWithOptions(json.DefaultMetaFactory, runtime.NewScheme(), runtime.NewScheme(), json.SerializerOptions{}) + + initial := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "with-fraction": float64(1.5), + "without-fraction": float64(1), + "without-fraction-big-positive": float64(9223372036854776000), + "without-fraction-big-negative": float64(-9223372036854776000), + }} + + var buf bytes.Buffer + if err := s.Encode(initial, &buf); err != nil { + t.Fatal(err) + } + + final := &unstructured.Unstructured{} + got, _, err := s.Decode(buf.Bytes(), nil, final) + if err != nil { + t.Fatal(err) + } + if got != final { + t.Fatalf("expected Decode to return target Unstructured object but got: %v", got) + } + + expected := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "with-fraction": float64(1.5), + "without-fraction": int64(1), // note the change in concrete type + "without-fraction-big-positive": float64(9223372036854776000), + "without-fraction-big-negative": float64(-9223372036854776000), + }} + + if diff := cmp.Diff(expected, final); diff != "" { + t.Fatalf("unexpected diff:\n%s", diff) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/meta.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/meta.go new file mode 100644 index 0000000000..df3f5f989a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/meta.go @@ -0,0 +1,63 @@ +/* +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 json + +import ( + "encoding/json" + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// MetaFactory is used to store and retrieve the version and kind +// information for JSON objects in a serializer. +type MetaFactory interface { + // Interpret should return the version and kind of the wire-format of + // the object. + Interpret(data []byte) (*schema.GroupVersionKind, error) +} + +// DefaultMetaFactory is a default factory for versioning objects in JSON. The object +// in memory and in the default JSON serialization will use the "kind" and "apiVersion" +// fields. +var DefaultMetaFactory = SimpleMetaFactory{} + +// SimpleMetaFactory provides default methods for retrieving the type and version of objects +// that are identified with an "apiVersion" and "kind" fields in their JSON +// serialization. It may be parameterized with the names of the fields in memory, or an +// optional list of base structs to search for those fields in memory. +type SimpleMetaFactory struct { +} + +// Interpret will return the APIVersion and Kind of the JSON wire-format +// encoding of an object, or an error. +func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) { + findKind := struct { + // +optional + APIVersion string `json:"apiVersion,omitempty"` + // +optional + Kind string `json:"kind,omitempty"` + }{} + if err := json.Unmarshal(data, &findKind); err != nil { + return nil, fmt.Errorf("couldn't get version/kind; json parse error: %v", err) + } + gv, err := schema.ParseGroupVersion(findKind.APIVersion) + if err != nil { + return nil, err + } + return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.Kind}, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/meta_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/meta_test.go new file mode 100644 index 0000000000..5c5dda89e6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/json/meta_test.go @@ -0,0 +1,45 @@ +/* +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 json + +import "testing" + +func TestSimpleMetaFactoryInterpret(t *testing.T) { + factory := SimpleMetaFactory{} + gvk, err := factory.Interpret([]byte(`{"apiVersion":"1","kind":"object"}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gvk.Version != "1" || gvk.Kind != "object" { + t.Errorf("unexpected interpret: %#v", gvk) + } + + // no kind or version + gvk, err = factory.Interpret([]byte(`{}`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gvk.Version != "" || gvk.Kind != "" { + t.Errorf("unexpected interpret: %#v", gvk) + } + + // unparsable + _, err = factory.Interpret([]byte(`{`)) + if err == nil { + t.Errorf("unexpected non-error") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/negotiated_codec.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/negotiated_codec.go new file mode 100644 index 0000000000..a42b4a41a8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/negotiated_codec.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 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 serializer + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// TODO: We should split negotiated serializers that we can change versions on from those we can change +// serialization formats on +type negotiatedSerializerWrapper struct { + info runtime.SerializerInfo +} + +func NegotiatedSerializerWrapper(info runtime.SerializerInfo) runtime.NegotiatedSerializer { + return &negotiatedSerializerWrapper{info} +} + +func (n *negotiatedSerializerWrapper) SupportedMediaTypes() []runtime.SerializerInfo { + return []runtime.SerializerInfo{n.info} +} + +func (n *negotiatedSerializerWrapper) EncoderForVersion(e runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder { + return e +} + +func (n *negotiatedSerializerWrapper) DecoderToVersion(d runtime.Decoder, _gv runtime.GroupVersioner) runtime.Decoder { + return d +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/collections.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/collections.go new file mode 100644 index 0000000000..8db8932fbb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/collections.go @@ -0,0 +1,173 @@ +/* +Copyright 2025 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 protobuf + +import ( + "encoding/binary" + "errors" + "io" + "math/bits" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" +) + +var ( + errFieldCount = errors.New("expected ListType to have 3 fields") + errTypeMetaField = errors.New("expected TypeMeta field to have TypeMeta type") + errTypeMetaProtobufTag = errors.New(`expected TypeMeta protobuf field tag to be ""`) + errListMetaField = errors.New("expected ListMeta field to have ListMeta type") + errListMetaProtobufTag = errors.New(`expected ListMeta protobuf field tag to be "bytes,1,opt,name=metadata"`) + errItemsProtobufTag = errors.New(`expected Items protobuf field tag to be "bytes,2,rep,name=items"`) + errItemsSizer = errors.New(`expected Items elements to implement proto.Sizer`) +) + +// getStreamingListData implements list extraction logic for protobuf stream serialization. +// +// Reason for a custom logic instead of reusing accessors from meta package: +// * Validate proto tags to prevent incompatibility with proto standard package. +// * ListMetaAccessor doesn't distinguish empty from nil value. +// * TypeAccessor reparsing "apiVersion" and serializing it with "{group}/{version}" +func getStreamingListData(list runtime.Object) (data streamingListData, err error) { + listValue, err := conversion.EnforcePtr(list) + if err != nil { + return data, err + } + listType := listValue.Type() + if listType.NumField() != 3 { + return data, errFieldCount + } + // TypeMeta: validated, but not returned as is not serialized. + _, ok := listValue.Field(0).Interface().(metav1.TypeMeta) + if !ok { + return data, errTypeMetaField + } + if listType.Field(0).Tag.Get("protobuf") != "" { + return data, errTypeMetaProtobufTag + } + // ListMeta + listMeta, ok := listValue.Field(1).Interface().(metav1.ListMeta) + if !ok { + return data, errListMetaField + } + // if we were ever to relax the protobuf tag check we should update the hardcoded `0xa` below when writing ListMeta. + if listType.Field(1).Tag.Get("protobuf") != "bytes,1,opt,name=metadata" { + return data, errListMetaProtobufTag + } + data.listMeta = listMeta + // Items; if we were ever to relax the protobuf tag check we should update the hardcoded `0x12` below when writing Items. + if listType.Field(2).Tag.Get("protobuf") != "bytes,2,rep,name=items" { + return data, errItemsProtobufTag + } + items, err := meta.ExtractList(list) + if err != nil { + return data, err + } + data.items = items + data.totalSize, data.listMetaSize, data.itemsSizes, err = listSize(listMeta, items) + return data, err +} + +type streamingListData struct { + // totalSize is the total size of the serialized List object, including their proto headers/size bytes + totalSize int + + // listMetaSize caches results from .Size() call to listMeta, doesn't include header bytes (field identifier, size) + listMetaSize int + listMeta metav1.ListMeta + + // itemsSizes caches results from .Size() call to items, doesn't include header bytes (field identifier, size) + itemsSizes []int + items []runtime.Object +} + +type sizer interface { + Size() int +} + +// listSize return size of ListMeta and items to be later used for preallocations. +// listMetaSize and itemSizes do not include header bytes (field identifier, size). +func listSize(listMeta metav1.ListMeta, items []runtime.Object) (totalSize, listMetaSize int, itemSizes []int, err error) { + // ListMeta + listMetaSize = listMeta.Size() + totalSize += 1 + sovGenerated(uint64(listMetaSize)) + listMetaSize + // Items + itemSizes = make([]int, len(items)) + for i, item := range items { + sizer, ok := item.(sizer) + if !ok { + return totalSize, listMetaSize, nil, errItemsSizer + } + n := sizer.Size() + itemSizes[i] = n + totalSize += 1 + sovGenerated(uint64(n)) + n + } + return totalSize, listMetaSize, itemSizes, nil +} + +func streamingEncodeUnknownList(w io.Writer, unk runtime.Unknown, listData streamingListData, memAlloc runtime.MemoryAllocator) error { + _, err := w.Write(protoEncodingPrefix) + if err != nil { + return err + } + // encodeList is responsible for encoding the List into the unknown Raw. + encodeList := func(writer io.Writer) (int, error) { + return streamingEncodeList(writer, listData, memAlloc) + } + _, err = unk.MarshalToWriter(w, listData.totalSize, encodeList) + return err +} + +func streamingEncodeList(w io.Writer, listData streamingListData, memAlloc runtime.MemoryAllocator) (size int, err error) { + // headerScratch escapes via w.Write, so allocate it once per call instead of once per item. + headerScratch := make([]byte, 1+binary.MaxVarintLen64) + // ListMeta; 0xa = (1 << 3) | 2; field number: 1, type: 2 (LEN). https://protobuf.dev/programming-guides/encoding/#structure + n, err := doEncodeWithHeader(&listData.listMeta, w, 0xa, listData.listMetaSize, headerScratch, memAlloc) + size += n + if err != nil { + return size, err + } + // Items; 0x12 = (2 << 3) | 2; field number: 2, type: 2 (LEN). https://protobuf.dev/programming-guides/encoding/#structure + for i, item := range listData.items { + n, err := doEncodeWithHeader(item, w, 0x12, listData.itemsSizes[i], headerScratch, memAlloc) + size += n + if err != nil { + return size, err + } + } + return size, nil +} + +// sovGenerated is copied from `generated.pb.go` returns size of varint. +func sovGenerated(v uint64) int { + return (bits.Len64(v|1) + 6) / 7 +} + +// encodeVarintGenerated is copied from `generated.pb.go` encodes varint. +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/collections_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/collections_test.go new file mode 100644 index 0000000000..826fdc1681 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/collections_test.go @@ -0,0 +1,363 @@ +/* +Copyright 2025 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 protobuf + +import ( + "bytes" + "encoding/base64" + "io" + "math/rand" + "os/exec" + "testing" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/randfill" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestCollectionsEncoding(t *testing.T) { + t.Run("Normal", func(t *testing.T) { + testCollectionsEncoding(t, NewSerializer(nil, nil), false) + }) + t.Run("Streaming", func(t *testing.T) { + testCollectionsEncoding(t, NewSerializerWithOptions(nil, nil, SerializerOptions{StreamingCollectionsEncoding: true}), true) + }) +} + +func testCollectionsEncoding(t *testing.T, s *Serializer, streamingEnabled bool) { + var remainingItems int64 = 1 + testCases := []struct { + name string + in runtime.Object + // expect is base64 encoded protobuf bytes + expect string + }{ + { + name: "CarpList items nil", + in: &testapigroupv1.CarpList{ + Items: nil, + }, + expect: "azhzAAoECgASABIICgYKABIAGgAaACIA", + }, + { + name: "CarpList slice nil", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Status: testapigroupv1.CarpStatus{ + Conditions: nil, + }, + }, + }, + }, + expect: "azhzAAoECgASABJBCgYKABIAGgASNwoQCgASABoAIgAqADIAOABCABIXGgBCAEoAUgBYAGAAaACCAQCKAQCaAQAaCgoAGgAiACoAMgAaACIA", + }, + { + name: "CarpList map nil", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Spec: testapigroupv1.CarpSpec{ + NodeSelector: nil, + }, + }, + }, + }, + expect: "azhzAAoECgASABJBCgYKABIAGgASNwoQCgASABoAIgAqADIAOABCABIXGgBCAEoAUgBYAGAAaACCAQCKAQCaAQAaCgoAGgAiACoAMgAaACIA", + }, + { + name: "CarpList items empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{}, + }, + expect: "azhzAAoECgASABIICgYKABIAGgAaACIA", + }, + { + name: "CarpList slice empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Status: testapigroupv1.CarpStatus{ + Conditions: []testapigroupv1.CarpCondition{}, + }, + }, + }, + }, + expect: "azhzAAoECgASABJBCgYKABIAGgASNwoQCgASABoAIgAqADIAOABCABIXGgBCAEoAUgBYAGAAaACCAQCKAQCaAQAaCgoAGgAiACoAMgAaACIA", + }, + { + name: "CarpList map empty", + in: &testapigroupv1.CarpList{ + Items: []testapigroupv1.Carp{ + { + Spec: testapigroupv1.CarpSpec{ + NodeSelector: map[string]string{}, + }, + }, + }, + }, + expect: "azhzAAoECgASABJBCgYKABIAGgASNwoQCgASABoAIgAqADIAOABCABIXGgBCAEoAUgBYAGAAaACCAQCKAQCaAQAaCgoAGgAiACoAMgAaACIA", + }, + { + name: "List just kind", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + }, + }, + expect: "azhzAAoICgASBExpc3QSCAoGCgASABoAGgAiAA==", + }, + { + name: "List just apiVersion", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + }, + }, + expect: "azhzAAoGCgJ2MRIAEggKBgoAEgAaABoAIgA=", + }, + { + name: "List no elements", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + }, + Items: []testapigroupv1.Carp{}, + }, + expect: "azhzAAoKCgJ2MRIETGlzdBIMCgoKABIEMjM0NRoAGgAiAA==", + }, + { + name: "List one element with continue", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + Continue: "abc", + RemainingItemCount: &remainingItems, + }, + Items: []testapigroupv1.Carp{ + {TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "default", + }}, + }, + }, + expect: "azhzAAoKCgJ2MRIETGlzdBJUCg8KABIEMjM0NRoDYWJjIAESQQoaCgNwb2QSABoHZGVmYXVsdCIAKgAyADgAQgASFxoAQgBKAFIAWABgAGgAggEAigEAmgEAGgoKABoAIgAqADIAGgAiAA==", + }, + { + name: "List two elements", + in: &testapigroupv1.CarpList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{ + ResourceVersion: "2345", + }, + Items: []testapigroupv1.Carp{ + {TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "default", + }}, + {TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Carp"}, ObjectMeta: metav1.ObjectMeta{ + Name: "pod2", + Namespace: "default2", + }}, + }, + }, + expect: "azhzAAoKCgJ2MRIETGlzdBKUAQoKCgASBDIzNDUaABJBChoKA3BvZBIAGgdkZWZhdWx0IgAqADIAOABCABIXGgBCAEoAUgBYAGAAaACCAQCKAQCaAQAaCgoAGgAiACoAMgASQwocCgRwb2QyEgAaCGRlZmF1bHQyIgAqADIAOABCABIXGgBCAEoAUgBYAGAAaACCAQCKAQCaAQAaCgoAGgAiACoAMgAaACIA", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var buf writeCountingBuffer + if err := s.Encode(tc.in, &buf); err != nil { + t.Fatalf("unexpected error: %v", err) + } + actualBytes := buf.Bytes() + expectBytes, err := io.ReadAll(base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(tc.expect))) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(expectBytes, actualBytes) { + expectedBytes, err := base64.StdEncoding.DecodeString(tc.expect) + if err == nil { + t.Errorf("expected:\n%v\ngot:\n%v", expectedBytes, actualBytes) + } else { + t.Errorf("expected:\n%v\ngot:\n%v", tc.expect, base64.StdEncoding.EncodeToString(actualBytes)) + } + actualProto := dumpProto(t, actualBytes[4:]) + expectedProto := dumpProto(t, expectBytes[4:]) + if actualProto != "" && expectedProto != "" { + t.Log(cmp.Diff(actualProto, expectedProto)) + } else { + t.Log(cmp.Diff(actualBytes, expectBytes)) + } + } + if streamingEnabled && buf.writeCount <= 1 { + t.Errorf("expected streaming but Write was called only: %d", buf.writeCount) + } + if !streamingEnabled && buf.writeCount > 1 { + t.Errorf("expected non-streaming but Write was called more than once: %d", buf.writeCount) + } + }) + } +} + +// dumpProto does a best-effort dump of the given proto bytes using protoc if it can be found in the path. +// This is only used when the test has already failed, to try to give more visibility into the diff of the failure. +func dumpProto(t *testing.T, data []byte) string { + t.Helper() + protoc, err := exec.LookPath("protoc") + if err != nil { + t.Logf("cannot find protoc in path to dump proto contents: %v", err) + return "" + } + cmd := exec.Command(protoc, "--decode_raw") + cmd.Stdin = bytes.NewBuffer(data) + d, err := cmd.CombinedOutput() + if err != nil { + t.Logf("protoc invocation failed: %v", err) + return "" + } + return string(d) +} + +type writeCountingBuffer struct { + writeCount int + bytes.Buffer +} + +func (b *writeCountingBuffer) Write(data []byte) (int, error) { + b.writeCount++ + return b.Buffer.Write(data) +} + +func (b *writeCountingBuffer) Reset() { + b.writeCount = 0 + b.Buffer.Reset() +} + +func TestFuzzCollection(t *testing.T) { + f := randfill.New() + streamingEncoder := NewSerializerWithOptions(nil, nil, SerializerOptions{StreamingCollectionsEncoding: true}) + streamingBuffer := &bytes.Buffer{} + normalEncoder := NewSerializerWithOptions(nil, nil, SerializerOptions{StreamingCollectionsEncoding: false}) + normalBuffer := &bytes.Buffer{} + for i := 0; i < 1000; i++ { + list := &testapigroupv1.CarpList{} + f.FillNoCustom(list) + streamingBuffer.Reset() + normalBuffer.Reset() + if err := streamingEncoder.Encode(list, streamingBuffer); err != nil { + t.Fatal(err) + } + if err := normalEncoder.Encode(list, normalBuffer); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(streamingBuffer.String(), normalBuffer.String()); diff != "" { + t.Logf("normal: %s", normalBuffer.String()) + t.Logf("streaming: %s", streamingBuffer.String()) + t.Fatalf("unexpected output:\n%s", diff) + } + } +} + +func TestCallsToSize(t *testing.T) { + counter := &countingSizer{data: []byte("abba")} + listMeta := metav1.ListMeta{} + listData := streamingListData{ + totalSize: 14, + listMeta: listMeta, + listMetaSize: listMeta.Size(), + itemsSizes: []int{counter.Size()}, + items: []runtime.Object{counter}, + } + err := streamingEncodeUnknownList(io.Discard, runtime.Unknown{}, listData, &runtime.Allocator{}) + if err != nil { + t.Fatal(err) + } + if counter.count != 1 { + t.Errorf("Expected only 1 call to sizer, got %d", counter.count) + } +} + +type countingSizer struct { + data []byte + count int +} + +var _ runtime.ProtobufMarshaller = (*countingSizer)(nil) + +func (s *countingSizer) MarshalTo(data []byte) (int, error) { + return copy(data, s.data), nil +} +func (s *countingSizer) Size() int { + s.count++ + return len(s.data) +} + +func (s *countingSizer) DeepCopyObject() runtime.Object { + return nil +} + +func (s *countingSizer) GetObjectKind() schema.ObjectKind { + return nil +} + +func BenchmarkStreamEncodeProtobufCollections(b *testing.B) { + disableFuzzFieldsV1 := func(field *metav1.FieldsV1, c randfill.Continue) {} + fuzzMap := func(kvs map[string]interface{}, c randfill.Continue) { + kvs[c.String(0)] = c.Bool() + kvs[c.String(0)] = c.Uint64() + kvs[c.String(0)] = c.String(0) + } + f := randfill.New().RandSource(rand.NewSource(12345)).Funcs(disableFuzzFieldsV1, fuzzMap) + carpList := &testapigroupv1.CarpList{} + carpList.Items = make([]testapigroupv1.Carp, 1000) + for i := range 1000 { + f.Fill(&carpList.Items[i]) + } + carpList.Kind = "CarpList" + carpList.APIVersion = "testapigroup.k8s.io/v1" + carpList.ResourceVersion = "12345" + + encoder := NewSerializerWithOptions(nil, nil, SerializerOptions{StreamingCollectionsEncoding: true}) + b.Run("CarpList", func(b *testing.B) { + var buf bytes.Buffer + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + buf.Reset() + if err := encoder.Encode(carpList, &buf); err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/doc.go new file mode 100644 index 0000000000..381748d69f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 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 protobuf provides a Kubernetes serializer for the protobuf format. +package protobuf diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go new file mode 100644 index 0000000000..93ad95cef3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go @@ -0,0 +1,568 @@ +/* +Copyright 2015 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 protobuf + +import ( + "bytes" + "fmt" + "io" + "net/http" + "reflect" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" + "k8s.io/apimachinery/pkg/util/framer" + "k8s.io/klog/v2" +) + +var ( + // protoEncodingPrefix serves as a magic number for an encoded protobuf message on this serializer. All + // proto messages serialized by this schema will be preceded by the bytes 0x6b 0x38 0x73, with the fourth + // byte being reserved for the encoding style. The only encoding style defined is 0x00, which means that + // the rest of the byte stream is a message of type k8s.io.kubernetes.pkg.runtime.Unknown (proto2). + // + // See k8s.io/apimachinery/pkg/runtime/generated.proto for details of the runtime.Unknown message. + // + // This encoding scheme is experimental, and is subject to change at any time. + protoEncodingPrefix = []byte{0x6b, 0x38, 0x73, 0x00} +) + +type errNotMarshalable struct { + t reflect.Type +} + +func (e errNotMarshalable) Error() string { + return fmt.Sprintf("object %v does not implement the protobuf marshalling interface and cannot be encoded to a protobuf message", e.t) +} + +func (e errNotMarshalable) Status() metav1.Status { + return metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusNotAcceptable, + Reason: metav1.StatusReason("NotAcceptable"), + Message: e.Error(), + } +} + +// IsNotMarshalable checks the type of error, returns a boolean true if error is not nil and not marshalable false otherwise +func IsNotMarshalable(err error) bool { + _, ok := err.(errNotMarshalable) + return err != nil && ok +} + +// NewSerializer creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If a typer +// is passed, the encoded object will have group, version, and kind fields set. If typer is nil, the objects will be written +// as-is (any type info passed with the object will be used). +func NewSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper) *Serializer { + return NewSerializerWithOptions(creater, typer, SerializerOptions{}) +} + +// NewSerializerWithOptions creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If a typer +// is passed, the encoded object will have group, version, and kind fields set. If typer is nil, the objects will be written +// as-is (any type info passed with the object will be used). +func NewSerializerWithOptions(creater runtime.ObjectCreater, typer runtime.ObjectTyper, opts SerializerOptions) *Serializer { + return &Serializer{ + prefix: protoEncodingPrefix, + creater: creater, + typer: typer, + options: opts, + } +} + +// Serializer handles encoding versioned objects into the proper wire form +type Serializer struct { + prefix []byte + creater runtime.ObjectCreater + typer runtime.ObjectTyper + + options SerializerOptions +} + +// SerializerOptions holds the options which are used to configure a Proto serializer. +type SerializerOptions struct { + // StreamingCollectionsEncoding enables encoding collection, one item at the time, drastically reducing memory needed. + StreamingCollectionsEncoding bool +} + +var _ runtime.Serializer = &Serializer{} +var _ runtime.EncoderWithAllocator = &Serializer{} +var _ recognizer.RecognizingDecoder = &Serializer{} + +const serializerIdentifier runtime.Identifier = "protobuf" + +// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default +// gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, +// the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will +// be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is +// not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most +// errors, the method will return the calculated schema kind. +func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + prefixLen := len(s.prefix) + switch { + case len(originalData) == 0: + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty data") + case len(originalData) < prefixLen || !bytes.Equal(s.prefix, originalData[:prefixLen]): + return nil, nil, fmt.Errorf("provided data does not appear to be a protobuf message, expected prefix %v", s.prefix) + case len(originalData) == prefixLen: + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty body") + } + + data := originalData[prefixLen:] + unk := runtime.Unknown{} + if err := unk.Unmarshal(data); err != nil { + return nil, nil, err + } + + actual := unk.GroupVersionKind() + copyKindDefaults(&actual, gvk) + + if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { + *intoUnknown = unk + if ok, _, _ := s.RecognizesData(unk.Raw); ok { + intoUnknown.ContentType = runtime.ContentTypeProtobuf + } + return intoUnknown, &actual, nil + } + + if into != nil { + types, _, err := s.typer.ObjectKinds(into) + switch { + case runtime.IsNotRegisteredError(err): + unmarshaler, ok := into.(unmarshaler) + if !ok { + return nil, &actual, errNotMarshalable{reflect.TypeOf(into)} + } + // top-level unmarshal resets before delegating unmarshaling to the object + unmarshaler.Reset() + if err := unmarshaler.Unmarshal(unk.Raw); err != nil { + return nil, &actual, err + } + return into, &actual, nil + case err != nil: + return nil, &actual, err + default: + copyKindDefaults(&actual, &types[0]) + // if the result of defaulting did not set a version or group, ensure that at least group is set + // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group + // of into is set if there is no better information from the caller or object. + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = types[0].Group + } + } + } + + if len(actual.Kind) == 0 { + return nil, &actual, runtime.NewMissingKindErr(fmt.Sprintf("%#v", unk.TypeMeta)) + } + if len(actual.Version) == 0 { + return nil, &actual, runtime.NewMissingVersionErr(fmt.Sprintf("%#v", unk.TypeMeta)) + } + + return unmarshalToObject(s.typer, s.creater, &actual, into, unk.Raw) +} + +// EncodeWithAllocator writes an object to the provided writer. +// In addition, it allows for providing a memory allocator for efficient memory usage during object serialization. +func (s *Serializer) EncodeWithAllocator(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + return s.encode(obj, w, memAlloc) +} + +// Encode serializes the provided object to the given writer. +func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { + return s.encode(obj, w, &runtime.SimpleAllocator{}) +} + +func (s *Serializer) encode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + if co, ok := obj.(runtime.CacheableObject); ok { + return co.CacheEncode(s.Identifier(), func(obj runtime.Object, w io.Writer) error { return s.doEncode(obj, w, memAlloc) }, w) + } + return s.doEncode(obj, w, memAlloc) +} + +func (s *Serializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + if memAlloc == nil { + //nolint:logcheck // Should not be reached in normal operations. + klog.Error("a mandatory memory allocator wasn't provided, this might have a negative impact on performance, check invocations of EncodeWithAllocator method, falling back on runtime.SimpleAllocator") + memAlloc = &runtime.SimpleAllocator{} + } + prefixSize := uint64(len(s.prefix)) + + var unk runtime.Unknown + switch t := obj.(type) { + case *runtime.Unknown: + estimatedSize := prefixSize + uint64(t.Size()) + data := memAlloc.Allocate(estimatedSize) + i, err := t.MarshalTo(data[prefixSize:]) + if err != nil { + return err + } + copy(data, s.prefix) + _, err = w.Write(data[:prefixSize+uint64(i)]) + return err + default: + kind := obj.GetObjectKind().GroupVersionKind() + unk = runtime.Unknown{ + TypeMeta: runtime.TypeMeta{ + Kind: kind.Kind, + APIVersion: kind.GroupVersion().String(), + }, + } + } + if s.options.StreamingCollectionsEncoding { + listData, err := getStreamingListData(obj) + if err == nil { + // Doesn't honor custom proto marshaling methods (like json streaming), because all proto objects implement proto methods. + return streamingEncodeUnknownList(w, unk, listData, memAlloc) + } + } + + switch t := obj.(type) { + case bufferedMarshaller: + // this path performs a single allocation during write only when the Allocator wasn't provided + // it also requires the caller to implement the more efficient Size and MarshalToSizedBuffer methods + encodedSize := uint64(t.Size()) + estimatedSize := prefixSize + estimateUnknownSize(&unk, encodedSize) + data := memAlloc.Allocate(estimatedSize) + + i, err := unk.NestedMarshalTo(data[prefixSize:], t, encodedSize) + if err != nil { + return err + } + + copy(data, s.prefix) + + _, err = w.Write(data[:prefixSize+uint64(i)]) + return err + + case unbufferedMarshaller: + // this path performs extra allocations + data, err := t.Marshal() + if err != nil { + return err + } + unk.Raw = data + + estimatedSize := prefixSize + uint64(unk.Size()) + data = memAlloc.Allocate(estimatedSize) + + i, err := unk.MarshalTo(data[prefixSize:]) + if err != nil { + return err + } + + copy(data, s.prefix) + + _, err = w.Write(data[:prefixSize+uint64(i)]) + return err + + default: + // TODO: marshal with a different content type and serializer (JSON for third party objects) + return errNotMarshalable{reflect.TypeOf(obj)} + } +} + +// Identifier implements runtime.Encoder interface. +func (s *Serializer) Identifier() runtime.Identifier { + return serializerIdentifier +} + +// RecognizesData implements the RecognizingDecoder interface. +func (s *Serializer) RecognizesData(data []byte) (bool, bool, error) { + return bytes.HasPrefix(data, s.prefix), false, nil +} + +// copyKindDefaults defaults dst to the value in src if dst does not have a value set. +func copyKindDefaults(dst, src *schema.GroupVersionKind) { + if src == nil { + return + } + // apply kind and version defaulting from provided default + if len(dst.Kind) == 0 { + dst.Kind = src.Kind + } + if len(dst.Version) == 0 && len(src.Version) > 0 { + dst.Group = src.Group + dst.Version = src.Version + } +} + +// bufferedMarshaller describes a more efficient marshalling interface that can avoid allocating multiple +// byte buffers by pre-calculating the size of the final buffer needed. +type bufferedMarshaller interface { + runtime.ProtobufMarshaller +} + +// Like bufferedMarshaller, but is able to marshal backwards, which is more efficient since it doesn't call Size() as frequently. +type bufferedReverseMarshaller interface { + runtime.ProtobufReverseMarshaller +} + +type unbufferedMarshaller interface { + Marshal() ([]byte, error) +} + +// unmarshaler is the subset of gogo Message and Unmarshaler used by unmarshal +type unmarshaler interface { + // Reset() is called on the top-level message before unmarshaling, + // and clears all existing data from the message instance. + Reset() + // Unmarshal decodes from the start of the data into the message. + Unmarshal([]byte) error +} + +// estimateUnknownSize returns the expected bytes consumed by a given runtime.Unknown +// object with a nil RawJSON struct and the expected size of the provided buffer. The +// returned size will not be correct if RawJSOn is set on unk. +func estimateUnknownSize(unk *runtime.Unknown, byteSize uint64) uint64 { + size := uint64(unk.Size()) + // protobuf uses 1 byte for the tag, a varint for the length of the array (at most 8 bytes - uint64 - here), + // and the size of the array. + size += 1 + 8 + byteSize + return size +} + +// NewRawSerializer creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If typer +// is not nil, the object has the group, version, and kind fields set. This serializer does not provide type information for the +// encoded object, and thus is not self describing (callers must know what type is being described in order to decode). +// +// This encoding scheme is experimental, and is subject to change at any time. +func NewRawSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper) *RawSerializer { + return &RawSerializer{ + creater: creater, + typer: typer, + } +} + +// RawSerializer encodes and decodes objects without adding a runtime.Unknown wrapper (objects are encoded without identifying +// type). +type RawSerializer struct { + creater runtime.ObjectCreater + typer runtime.ObjectTyper +} + +var _ runtime.Serializer = &RawSerializer{} + +const rawSerializerIdentifier runtime.Identifier = "raw-protobuf" + +// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default +// gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, +// the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will +// be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is +// not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most +// errors, the method will return the calculated schema kind. +func (s *RawSerializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + if into == nil { + return nil, nil, fmt.Errorf("this serializer requires an object to decode into: %#v", s) + } + + if len(originalData) == 0 { + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty data") + } + data := originalData + + actual := &schema.GroupVersionKind{} + copyKindDefaults(actual, gvk) + + if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { + intoUnknown.Raw = data + intoUnknown.ContentEncoding = "" + intoUnknown.ContentType = runtime.ContentTypeProtobuf + intoUnknown.SetGroupVersionKind(*actual) + return intoUnknown, actual, nil + } + + types, _, err := s.typer.ObjectKinds(into) + switch { + case runtime.IsNotRegisteredError(err): + unmarshaler, ok := into.(unmarshaler) + if !ok { + return nil, actual, errNotMarshalable{reflect.TypeOf(into)} + } + // top-level unmarshal resets before delegating unmarshaling to the object + unmarshaler.Reset() + if err := unmarshaler.Unmarshal(data); err != nil { + return nil, actual, err + } + return into, actual, nil + case err != nil: + return nil, actual, err + default: + copyKindDefaults(actual, &types[0]) + // if the result of defaulting did not set a version or group, ensure that at least group is set + // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group + // of into is set if there is no better information from the caller or object. + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = types[0].Group + } + } + + if len(actual.Kind) == 0 { + return nil, actual, runtime.NewMissingKindErr("") + } + if len(actual.Version) == 0 { + return nil, actual, runtime.NewMissingVersionErr("") + } + + return unmarshalToObject(s.typer, s.creater, actual, into, data) +} + +// unmarshalToObject is the common code between decode in the raw and normal serializer. +func unmarshalToObject(typer runtime.ObjectTyper, creater runtime.ObjectCreater, actual *schema.GroupVersionKind, into runtime.Object, data []byte) (runtime.Object, *schema.GroupVersionKind, error) { + // use the target if necessary + obj, err := runtime.UseOrCreateObject(typer, creater, *actual, into) + if err != nil { + return nil, actual, err + } + + unmarshaler, ok := obj.(unmarshaler) + if !ok { + return nil, actual, errNotMarshalable{reflect.TypeOf(obj)} + } + // top-level unmarshal resets before delegating unmarshaling to the object + unmarshaler.Reset() + if err := unmarshaler.Unmarshal(data); err != nil { + return nil, actual, err + } + if actual != nil { + obj.GetObjectKind().SetGroupVersionKind(*actual) + } + return obj, actual, nil +} + +// Encode serializes the provided object to the given writer. Overrides is ignored. +func (s *RawSerializer) Encode(obj runtime.Object, w io.Writer) error { + return s.encode(obj, w, &runtime.SimpleAllocator{}) +} + +// EncodeWithAllocator writes an object to the provided writer. +// In addition, it allows for providing a memory allocator for efficient memory usage during object serialization. +func (s *RawSerializer) EncodeWithAllocator(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + return s.encode(obj, w, memAlloc) +} + +func (s *RawSerializer) encode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + if co, ok := obj.(runtime.CacheableObject); ok { + return co.CacheEncode(s.Identifier(), func(obj runtime.Object, w io.Writer) error { return s.doEncode(obj, w, memAlloc) }, w) + } + return s.doEncode(obj, w, memAlloc) +} + +func (s *RawSerializer) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + _, err := doEncode(obj, w, nil, memAlloc) + return err +} + +func doEncodeWithHeader(obj any, w io.Writer, field byte, precomputedSize int, headerScratch []byte, memAlloc runtime.MemoryAllocator) (size int, err error) { + // Field identifier and size + header := headerScratch[:1+sovGenerated(uint64(precomputedSize))] + header[0] = field + encodeVarintGenerated(header, len(header), uint64(precomputedSize)) + n, err := w.Write(header) + size += n + if err != nil { + return size, err + } + // Obj + n, err = doEncode(obj, w, &precomputedSize, memAlloc) + size += n + if err != nil { + return size, err + } + if n != precomputedSize { + return size, fmt.Errorf("the size value was %d, but doEncode wrote %d bytes to data", precomputedSize, n) + } + return size, nil +} + +// doEncode encodes provided object into writer using a allocator if possible. +// Avoids call by object Size if precomputedObjSize is provided. +// precomputedObjSize should not include header bytes (field identifier, size). +func doEncode(obj any, w io.Writer, precomputedObjSize *int, memAlloc runtime.MemoryAllocator) (int, error) { + if memAlloc == nil { + //nolint:logcheck // Should not be reached in normal operations. + klog.Error("a mandatory memory allocator wasn't provided, this might have a negative impact on performance, check invocations of EncodeWithAllocator method, falling back on runtime.SimpleAllocator") + memAlloc = &runtime.SimpleAllocator{} + } + switch t := obj.(type) { + case bufferedReverseMarshaller: + // this path performs a single allocation during write only when the Allocator wasn't provided + // it also requires the caller to implement the more efficient Size and MarshalToSizedBuffer methods + if precomputedObjSize == nil { + s := t.Size() + precomputedObjSize = &s + } + data := memAlloc.Allocate(uint64(*precomputedObjSize)) + + n, err := t.MarshalToSizedBuffer(data) + if err != nil { + return 0, err + } + return w.Write(data[:n]) + + case bufferedMarshaller: + // this path performs a single allocation during write only when the Allocator wasn't provided + // it also requires the caller to implement the more efficient Size and MarshalTo methods + if precomputedObjSize == nil { + s := t.Size() + precomputedObjSize = &s + } + data := memAlloc.Allocate(uint64(*precomputedObjSize)) + + n, err := t.MarshalTo(data) + if err != nil { + return 0, err + } + return w.Write(data[:n]) + + case unbufferedMarshaller: + // this path performs extra allocations + data, err := t.Marshal() + if err != nil { + return 0, err + } + return w.Write(data) + + default: + return 0, errNotMarshalable{reflect.TypeOf(obj)} + } +} + +// Identifier implements runtime.Encoder interface. +func (s *RawSerializer) Identifier() runtime.Identifier { + return rawSerializerIdentifier +} + +// LengthDelimitedFramer is exported variable of type lengthDelimitedFramer +var LengthDelimitedFramer = lengthDelimitedFramer{} + +// Provides length delimited frame reader and writer methods +type lengthDelimitedFramer struct{} + +// NewFrameWriter implements stream framing for this serializer +func (lengthDelimitedFramer) NewFrameWriter(w io.Writer) io.Writer { + return framer.NewLengthDelimitedFrameWriter(w) +} + +// NewFrameReader implements stream framing for this serializer +func (lengthDelimitedFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { + return framer.NewLengthDelimitedFrameReader(r) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf_test.go new file mode 100644 index 0000000000..df9a52d5d8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf_test.go @@ -0,0 +1,180 @@ +/* +Copyright 2019 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 protobuf + +import ( + "bytes" + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + testapigroupv1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" +) + +func TestCacheableObject(t *testing.T) { + gvk := schema.GroupVersionKind{Group: "group", Version: "version", Kind: "MockCacheableObject"} + creater := &mockCreater{obj: &runtimetesting.MockCacheableObject{}} + typer := &mockTyper{gvk: &gvk} + + encoders := []runtime.Encoder{ + NewSerializer(creater, typer), + NewRawSerializer(creater, typer), + } + + for _, encoder := range encoders { + runtimetesting.CacheableObjectTest(t, encoder) + } +} + +type mockCreater struct { + apiVersion string + kind string + err error + obj runtime.Object +} + +func (c *mockCreater) New(kind schema.GroupVersionKind) (runtime.Object, error) { + c.apiVersion, c.kind = kind.GroupVersion().String(), kind.Kind + return c.obj, c.err +} + +type mockTyper struct { + gvk *schema.GroupVersionKind + err error +} + +func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { + if t.gvk == nil { + return nil, false, t.err + } + return []schema.GroupVersionKind{*t.gvk}, false, t.err +} + +func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { + return false +} + +func TestSerializerEncodeWithAllocator(t *testing.T) { + testCases := []struct { + name string + obj runtime.Object + }{ + { + name: "encode a bufferedMarshaller obj", + obj: &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{APIVersion: "group/version", Kind: "Carp"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + Spec: testapigroupv1.CarpSpec{ + Subdomain: "carp.k8s.io", + }, + }, + }, + + { + name: "encode a runtime.Unknown obj", + obj: &runtime.Unknown{TypeMeta: runtime.TypeMeta{APIVersion: "group/version", Kind: "Unknown"}, Raw: []byte("hello world")}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + target := NewSerializer(nil, nil) + + writer := &bytes.Buffer{} + if err := target.Encode(tc.obj, writer); err != nil { + t.Fatal(err) + } + + writer2 := &bytes.Buffer{} + alloc := &testAllocator{} + if err := target.EncodeWithAllocator(tc.obj, writer2, alloc); err != nil { + t.Fatal(err) + } + if alloc.allocateCount != 1 { + t.Fatalf("expected the Allocate method to be called exactly 1 but it was executed: %v times ", alloc.allocateCount) + } + + // to ensure compatibility of the new method with the old one, serialized data must be equal + // also we are not testing decoding since "roundtripping" is tested elsewhere for all known types + if !reflect.DeepEqual(writer.Bytes(), writer2.Bytes()) { + t.Fatal("data mismatch, data serialized with the Encode method is different than serialized with the EncodeWithAllocator method") + } + }) + } +} + +func TestRawSerializerEncodeWithAllocator(t *testing.T) { + testCases := []struct { + name string + obj runtime.Object + }{ + { + name: "encode a bufferedReverseMarshaller obj", + obj: &testapigroupv1.Carp{ + TypeMeta: metav1.TypeMeta{APIVersion: "group/version", Kind: "Carp"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + Spec: testapigroupv1.CarpSpec{ + Subdomain: "carp.k8s.io", + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + writer := &bytes.Buffer{} + target := NewRawSerializer(nil, nil) + + if err := target.Encode(tc.obj, writer); err != nil { + t.Fatal(err) + } + + writer2 := &bytes.Buffer{} + alloc := &testAllocator{} + if err := target.EncodeWithAllocator(tc.obj, writer2, alloc); err != nil { + t.Fatal(err) + } + if alloc.allocateCount != 1 { + t.Fatalf("expected the Allocate method to be called exactly 1 but it was executed: %v times ", alloc.allocateCount) + } + + // to ensure compatibility of the new method with the old one, serialized data must be equal + // also we are not testing decoding since "roundtripping" is tested elsewhere for all known types + if !reflect.DeepEqual(writer.Bytes(), writer2.Bytes()) { + t.Fatal("data mismatch, data serialized with the Encode method is different than serialized with the EncodeWithAllocator method") + } + }) + } +} + +type testAllocator struct { + buf []byte + allocateCount int +} + +func (ta *testAllocator) Allocate(n uint64) []byte { + ta.buf = make([]byte, n) + ta.allocateCount++ + return ta.buf +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/recognizer.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/recognizer.go new file mode 100644 index 0000000000..5a6c200dd1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/recognizer.go @@ -0,0 +1,128 @@ +/* +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 recognizer + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type RecognizingDecoder interface { + runtime.Decoder + // RecognizesData should return true if the input provided in the provided reader + // belongs to this decoder, or an error if the data could not be read or is ambiguous. + // Unknown is true if the data could not be determined to match the decoder type. + // Decoders should assume that they can read as much of peek as they need (as the caller + // provides) and may return unknown if the data provided is not sufficient to make a + // a determination. When peek returns EOF that may mean the end of the input or the + // end of buffered input - recognizers should return the best guess at that time. + RecognizesData(peek []byte) (ok, unknown bool, err error) +} + +// NewDecoder creates a decoder that will attempt multiple decoders in an order defined +// by: +// +// 1. The decoder implements RecognizingDecoder and identifies the data +// 2. All other decoders, and any decoder that returned true for unknown. +// +// The order passed to the constructor is preserved within those priorities. +func NewDecoder(decoders ...runtime.Decoder) runtime.Decoder { + return &decoder{ + decoders: decoders, + } +} + +type decoder struct { + decoders []runtime.Decoder +} + +var _ RecognizingDecoder = &decoder{} + +func (d *decoder) RecognizesData(data []byte) (bool, bool, error) { + var ( + lastErr error + anyUnknown bool + ) + for _, r := range d.decoders { + switch t := r.(type) { + case RecognizingDecoder: + ok, unknown, err := t.RecognizesData(data) + if err != nil { + lastErr = err + continue + } + anyUnknown = anyUnknown || unknown + if !ok { + continue + } + return true, false, nil + } + } + return false, anyUnknown, lastErr +} + +func (d *decoder) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + var ( + lastErr error + skipped []runtime.Decoder + ) + + // try recognizers, record any decoders we need to give a chance later + for _, r := range d.decoders { + switch t := r.(type) { + case RecognizingDecoder: + ok, unknown, err := t.RecognizesData(data) + if err != nil { + lastErr = err + continue + } + if unknown { + skipped = append(skipped, t) + continue + } + if !ok { + continue + } + return r.Decode(data, gvk, into) + default: + skipped = append(skipped, t) + } + } + + // try recognizers that returned unknown or didn't recognize their data + for _, r := range skipped { + out, actual, err := r.Decode(data, gvk, into) + if err != nil { + // if we got an object back from the decoder, and the + // error was a strict decoding error (e.g. unknown or + // duplicate fields), we still consider the recognizer + // to have understood the object + if out == nil || !runtime.IsStrictDecodingError(err) { + lastErr = err + continue + } + } + return out, actual, err + } + + if lastErr == nil { + lastErr = fmt.Errorf("no serialization format matched the provided data") + } + return nil, nil, lastErr +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/testing/recognizer_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/testing/recognizer_test.go new file mode 100644 index 0000000000..ac882f8388 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/testing/recognizer_test.go @@ -0,0 +1,61 @@ +/* +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 testing + +import ( + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" +) + +type A struct{} + +func (A) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (a A) DeepCopyObject() runtime.Object { + return a +} + +func TestRecognizer(t *testing.T) { + s := runtime.NewScheme() + s.AddKnownTypes(schema.GroupVersion{Version: "v1"}, &A{}) + d := recognizer.NewDecoder( + json.NewSerializerWithOptions(json.DefaultMetaFactory, s, s, json.SerializerOptions{}), + json.NewSerializerWithOptions(json.DefaultMetaFactory, s, s, json.SerializerOptions{Yaml: true}), + ) + out, _, err := d.Decode([]byte(` +kind: A +apiVersion: v1 +`), nil, nil) + if err != nil { + t.Fatal(err) + } + t.Logf("%#v", out) + + out, _, err = d.Decode([]byte(` +{ + "kind":"A", + "apiVersion":"v1" +} +`), nil, nil) + if err != nil { + t.Fatal(err) + } + t.Logf("%#v", out) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/sparse_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/sparse_test.go new file mode 100644 index 0000000000..406bc3615d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/sparse_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2018 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 serializer + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type FakeV1Obj struct { + metav1.TypeMeta + metav1.ObjectMeta +} + +func (*FakeV1Obj) DeepCopyObject() runtime.Object { + panic("not supported") +} + +type FakeV2DifferentObj struct { + metav1.TypeMeta + metav1.ObjectMeta +} + +func (*FakeV2DifferentObj) DeepCopyObject() runtime.Object { + panic("not supported") +} +func TestSparse(t *testing.T) { + v1 := schema.GroupVersion{Group: "mygroup", Version: "v1"} + v2 := schema.GroupVersion{Group: "mygroup", Version: "v2"} + + scheme := runtime.NewScheme() + scheme.AddKnownTypes(v1, &FakeV1Obj{}) + scheme.AddKnownTypes(v2, &FakeV2DifferentObj{}) + codecs := NewCodecFactory(scheme) + + srcObj1 := &FakeV1Obj{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} + srcObj2 := &FakeV2DifferentObj{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} + + encoder := codecs.LegacyCodec(v2, v1) + decoder := codecs.UniversalDecoder(v2, v1) + + srcObj1Bytes, err := runtime.Encode(encoder, srcObj1) + if err != nil { + t.Fatal(err) + } + t.Log(string(srcObj1Bytes)) + srcObj2Bytes, err := runtime.Encode(encoder, srcObj2) + if err != nil { + t.Fatal(err) + } + t.Log(string(srcObj2Bytes)) + + uncastDstObj1, err := runtime.Decode(decoder, srcObj1Bytes) + if err != nil { + t.Fatal(err) + } + uncastDstObj2, err := runtime.Decode(decoder, srcObj2Bytes) + if err != nil { + t.Fatal(err) + } + + // clear typemeta + uncastDstObj1.(*FakeV1Obj).TypeMeta = metav1.TypeMeta{} + uncastDstObj2.(*FakeV2DifferentObj).TypeMeta = metav1.TypeMeta{} + + if !equality.Semantic.DeepEqual(srcObj1, uncastDstObj1) { + t.Fatal(cmp.Diff(srcObj1, uncastDstObj1)) + } + if !equality.Semantic.DeepEqual(srcObj2, uncastDstObj2) { + t.Fatal(cmp.Diff(srcObj2, uncastDstObj2)) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming.go new file mode 100644 index 0000000000..971c46d496 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming.go @@ -0,0 +1,136 @@ +/* +Copyright 2015 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 streaming implements encoder and decoder for streams +// of runtime.Objects over io.Writer/Readers. +package streaming + +import ( + "bytes" + "fmt" + "io" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Encoder is a runtime.Encoder on a stream. +type Encoder interface { + // Encode will write the provided object to the stream or return an error. It obeys the same + // contract as runtime.VersionedEncoder. + Encode(obj runtime.Object) error +} + +// Decoder is a runtime.Decoder from a stream. +type Decoder interface { + // Decode will return io.EOF when no more objects are available. + Decode(defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) + // Close closes the underlying stream. + Close() error +} + +// Serializer is a factory for creating encoders and decoders that work over streams. +type Serializer interface { + NewEncoder(w io.Writer) Encoder + NewDecoder(r io.ReadCloser) Decoder +} + +type decoder struct { + reader io.ReadCloser + decoder runtime.Decoder + buf []byte + maxBytes int + resetRead bool +} + +// NewDecoder creates a streaming decoder that reads object chunks from r and decodes them with d. +// The reader is expected to return ErrShortRead if the provided buffer is not large enough to read +// an entire object. +func NewDecoder(r io.ReadCloser, d runtime.Decoder) Decoder { + return &decoder{ + reader: r, + decoder: d, + buf: make([]byte, 1024), + maxBytes: 16 * 1024 * 1024, + } +} + +var ErrObjectTooLarge = fmt.Errorf("object to decode was longer than maximum allowed size") + +// Decode reads the next object from the stream and decodes it. +func (d *decoder) Decode(defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + base := 0 + for { + n, err := d.reader.Read(d.buf[base:]) + if err == io.ErrShortBuffer { + if n == 0 { + return nil, nil, fmt.Errorf("got short buffer with n=0, base=%d, cap=%d", base, cap(d.buf)) + } + if d.resetRead { + continue + } + // double the buffer size up to maxBytes + if len(d.buf) < d.maxBytes { + base += n + d.buf = append(d.buf, make([]byte, len(d.buf))...) + continue + } + // must read the rest of the frame (until we stop getting ErrShortBuffer) + d.resetRead = true + return nil, nil, ErrObjectTooLarge + } + if err != nil { + return nil, nil, err + } + if d.resetRead { + // now that we have drained the large read, continue + d.resetRead = false + continue + } + base += n + break + } + return d.decoder.Decode(d.buf[:base], defaults, into) +} + +func (d *decoder) Close() error { + return d.reader.Close() +} + +type encoder struct { + writer io.Writer + encoder runtime.Encoder + buf *bytes.Buffer +} + +// NewEncoder returns a new streaming encoder. +func NewEncoder(w io.Writer, e runtime.Encoder) Encoder { + return &encoder{ + writer: w, + encoder: e, + buf: &bytes.Buffer{}, + } +} + +// Encode writes the provided object to the nested writer. +func (e *encoder) Encode(obj runtime.Object) error { + if err := e.encoder.Encode(obj, e.buf); err != nil { + return err + } + _, err := e.writer.Write(e.buf.Bytes()) + e.buf.Reset() + return err +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming_test.go new file mode 100644 index 0000000000..1721423acc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2016 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 streaming + +import ( + "bytes" + "io" + "io/ioutil" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/framer" +) + +type fakeDecoder struct { + got []byte + obj runtime.Object + err error +} + +func (d *fakeDecoder) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + d.got = data + return d.obj, nil, d.err +} + +func TestEmptyDecoder(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + d := &fakeDecoder{} + _, _, err := NewDecoder(ioutil.NopCloser(buf), d).Decode(nil, nil) + if err != io.EOF { + t.Fatal(err) + } +} + +func TestDecoder(t *testing.T) { + frames := [][]byte{ + make([]byte, 1025), + make([]byte, 1024*5), + make([]byte, 1024*1024*17), + make([]byte, 1025), + } + pr, pw := io.Pipe() + fw := framer.NewLengthDelimitedFrameWriter(pw) + go func() { + for i := range frames { + fw.Write(frames[i]) + } + pw.Close() + }() + + r := framer.NewLengthDelimitedFrameReader(pr) + d := &fakeDecoder{} + dec := NewDecoder(r, d) + if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[0]) { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } + if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[1]) { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } + if _, _, err := dec.Decode(nil, nil); err != ErrObjectTooLarge || !bytes.Equal(d.got, frames[1]) { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } + if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[3]) { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } + if _, _, err := dec.Decode(nil, nil); err != io.EOF { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go new file mode 100644 index 0000000000..6c86d5e15c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go @@ -0,0 +1,292 @@ +/* +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 versioning + +import ( + "encoding/json" + "io" + "reflect" + "sync" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" +) + +// NewDefaultingCodecForScheme is a convenience method for callers that are using a scheme. +func NewDefaultingCodecForScheme( + // TODO: I should be a scheme interface? + scheme *runtime.Scheme, + encoder runtime.Encoder, + decoder runtime.Decoder, + encodeVersion runtime.GroupVersioner, + decodeVersion runtime.GroupVersioner, +) runtime.Codec { + return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion, scheme.Name()) +} + +// NewCodec takes objects in their internal versions and converts them to external versions before +// serializing them. It assumes the serializer provided to it only deals with external versions. +// This class is also a serializer, but is generally used with a specific version. +func NewCodec( + encoder runtime.Encoder, + decoder runtime.Decoder, + convertor runtime.ObjectConvertor, + creater runtime.ObjectCreater, + typer runtime.ObjectTyper, + defaulter runtime.ObjectDefaulter, + encodeVersion runtime.GroupVersioner, + decodeVersion runtime.GroupVersioner, + originalSchemeName string, +) runtime.Codec { + internal := &codec{ + encoder: encoder, + decoder: decoder, + convertor: convertor, + creater: creater, + typer: typer, + defaulter: defaulter, + + encodeVersion: encodeVersion, + decodeVersion: decodeVersion, + + identifier: identifier(encodeVersion, encoder), + + originalSchemeName: originalSchemeName, + } + return internal +} + +type codec struct { + encoder runtime.Encoder + decoder runtime.Decoder + convertor runtime.ObjectConvertor + creater runtime.ObjectCreater + typer runtime.ObjectTyper + defaulter runtime.ObjectDefaulter + + encodeVersion runtime.GroupVersioner + decodeVersion runtime.GroupVersioner + + identifier runtime.Identifier + + // originalSchemeName is optional, but when filled in it holds the name of the scheme from which this codec originates + originalSchemeName string +} + +var _ runtime.EncoderWithAllocator = &codec{} + +var identifiersMap sync.Map + +type codecIdentifier struct { + EncodeGV string `json:"encodeGV,omitempty"` + Encoder string `json:"encoder,omitempty"` + Name string `json:"name,omitempty"` +} + +// identifier computes Identifier of Encoder based on codec parameters. +func identifier(encodeGV runtime.GroupVersioner, encoder runtime.Encoder) runtime.Identifier { + result := codecIdentifier{ + Name: "versioning", + } + + if encodeGV != nil { + result.EncodeGV = encodeGV.Identifier() + } + if encoder != nil { + result.Encoder = string(encoder.Identifier()) + } + if id, ok := identifiersMap.Load(result); ok { + return id.(runtime.Identifier) + } + identifier, err := json.Marshal(result) + if err != nil { + //nolint:logcheck // Should not be reached. + klog.Fatalf("Failed marshaling identifier for codec: %v", err) + } + identifiersMap.Store(result, runtime.Identifier(identifier)) + return runtime.Identifier(identifier) +} + +// Decode attempts a decode of the object, then tries to convert it to the internal version. If into is provided and the decoding is +// successful, the returned runtime.Object will be the value passed as into. Note that this may bypass conversion if you pass an +// into that matches the serialized version. +func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + // If the into object is unstructured and expresses an opinion about its group/version, + // create a new instance of the type so we always exercise the conversion path (skips short-circuiting on `into == obj`) + decodeInto := into + if into != nil { + if _, ok := into.(runtime.Unstructured); ok && !into.GetObjectKind().GroupVersionKind().GroupVersion().Empty() { + decodeInto = reflect.New(reflect.TypeOf(into).Elem()).Interface().(runtime.Object) + } + } + + var strictDecodingErrs []error + obj, gvk, err := c.decoder.Decode(data, defaultGVK, decodeInto) + if err != nil { + if strictErr, ok := runtime.AsStrictDecodingError(err); obj != nil && ok { + // save the strictDecodingError and let the caller decide what to do with it + strictDecodingErrs = append(strictDecodingErrs, strictErr.Errors()...) + } else { + return nil, gvk, err + } + } + + if d, ok := obj.(runtime.NestedObjectDecoder); ok { + if err := d.DecodeNestedObjects(runtime.WithoutVersionDecoder{Decoder: c.decoder}); err != nil { + if strictErr, ok := runtime.AsStrictDecodingError(err); ok { + // save the strictDecodingError let and the caller decide what to do with it + strictDecodingErrs = append(strictDecodingErrs, strictErr.Errors()...) + } else { + return nil, gvk, err + + } + } + } + + // aggregate the strict decoding errors into one + var strictDecodingErr error + if len(strictDecodingErrs) > 0 { + strictDecodingErr = runtime.NewStrictDecodingError(strictDecodingErrs) + } + // if we specify a target, use generic conversion. + if into != nil { + // perform defaulting if requested + if c.defaulter != nil { + c.defaulter.Default(obj) + } + + // Short-circuit conversion if the into object is same object + if into == obj { + return into, gvk, strictDecodingErr + } + + if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil { + return nil, gvk, err + } + + return into, gvk, strictDecodingErr + } + + // perform defaulting if requested + if c.defaulter != nil { + c.defaulter.Default(obj) + } + + out, err := c.convertor.ConvertToVersion(obj, c.decodeVersion) + if err != nil { + return nil, gvk, err + } + return out, gvk, strictDecodingErr +} + +// EncodeWithAllocator ensures the provided object is output in the appropriate group and version, invoking +// conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is. +// In addition, it allows for providing a memory allocator for efficient memory usage during object serialization. +func (c *codec) EncodeWithAllocator(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + return c.encode(obj, w, memAlloc) +} + +// Encode ensures the provided object is output in the appropriate group and version, invoking +// conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is. +func (c *codec) Encode(obj runtime.Object, w io.Writer) error { + return c.encode(obj, w, nil) +} + +func (c *codec) encode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + if co, ok := obj.(runtime.CacheableObject); ok { + return co.CacheEncode(c.Identifier(), func(obj runtime.Object, w io.Writer) error { return c.doEncode(obj, w, memAlloc) }, w) + } + return c.doEncode(obj, w, memAlloc) +} + +func (c *codec) doEncode(obj runtime.Object, w io.Writer, memAlloc runtime.MemoryAllocator) error { + encodeFn := c.encoder.Encode + if memAlloc != nil { + if encoder, supportsAllocator := c.encoder.(runtime.EncoderWithAllocator); supportsAllocator { + encodeFn = func(obj runtime.Object, w io.Writer) error { + return encoder.EncodeWithAllocator(obj, w, memAlloc) + } + } else { + //nolint:logcheck // Extending the API is not worth it for contextual, structured logging of this. + klog.V(6).Infof("a memory allocator was provided but the encoder %s doesn't implement the runtime.EncoderWithAllocator, using regular encoder.Encode method", c.encoder.Identifier()) + } + } + switch obj := obj.(type) { + case *runtime.Unknown: + return encodeFn(obj, w) + case runtime.Unstructured: + // An unstructured list can contain objects of multiple group version kinds. don't short-circuit just + // because the top-level type matches our desired destination type. actually send the object to the converter + // to give it a chance to convert the list items if needed. + if _, ok := obj.(*unstructured.UnstructuredList); !ok { + // avoid conversion roundtrip if GVK is the right one already or is empty (yes, this is a hack, but the old behaviour we rely on in kubectl) + objGVK := obj.GetObjectKind().GroupVersionKind() + if len(objGVK.Version) == 0 { + return encodeFn(obj, w) + } + targetGVK, ok := c.encodeVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{objGVK}) + if !ok { + return runtime.NewNotRegisteredGVKErrForTarget(c.originalSchemeName, objGVK, c.encodeVersion) + } + if targetGVK == objGVK { + return encodeFn(obj, w) + } + } + } + + gvks, isUnversioned, err := c.typer.ObjectKinds(obj) + if err != nil { + return err + } + + objectKind := obj.GetObjectKind() + old := objectKind.GroupVersionKind() + // restore the old GVK after encoding + defer objectKind.SetGroupVersionKind(old) + + if c.encodeVersion == nil || isUnversioned { + if e, ok := obj.(runtime.NestedObjectEncoder); ok { + if err := e.EncodeNestedObjects(runtime.WithVersionEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { + return err + } + } + objectKind.SetGroupVersionKind(gvks[0]) + return encodeFn(obj, w) + } + + // Perform a conversion if necessary + out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion) + if err != nil { + return err + } + + if e, ok := out.(runtime.NestedObjectEncoder); ok { + if err := e.EncodeNestedObjects(runtime.WithVersionEncoder{Version: c.encodeVersion, Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { + return err + } + } + + // Conversion is responsible for setting the proper group, version, and kind onto the outgoing object + return encodeFn(out, w) +} + +// Identifier implements runtime.Encoder interface. +func (c *codec) Identifier() runtime.Identifier { + return c.identifier +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning_test.go new file mode 100644 index 0000000000..ee12a69adf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning_test.go @@ -0,0 +1,405 @@ +/* +Copyright 2015 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 versioning + +import ( + "fmt" + "io" + "io/ioutil" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + runtimetesting "k8s.io/apimachinery/pkg/runtime/testing" + "k8s.io/apimachinery/pkg/util/diff" +) + +type testDecodable struct { + Other string + Value int `json:"value"` + gvk schema.GroupVersionKind +} + +func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d } +func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } +func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } +func (d *testDecodable) DeepCopyObject() runtime.Object { + // no real deepcopy because these tests check for pointer equality + return d +} + +type testNestedDecodable struct { + Other string + Value int `json:"value"` + + gvk schema.GroupVersionKind + nestedCalled bool + nestedErr error +} + +func (d *testNestedDecodable) GetObjectKind() schema.ObjectKind { return d } +func (d *testNestedDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } +func (d *testNestedDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } +func (d *testNestedDecodable) DeepCopyObject() runtime.Object { + // no real deepcopy because these tests check for pointer equality + return d +} + +func (d *testNestedDecodable) EncodeNestedObjects(e runtime.Encoder) error { + d.nestedCalled = true + return d.nestedErr +} + +func (d *testNestedDecodable) DecodeNestedObjects(_ runtime.Decoder) error { + d.nestedCalled = true + return d.nestedErr +} + +func TestNestedDecode(t *testing.T) { + n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} + decoder := &mockSerializer{obj: n} + codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode") + if _, _, err := codec.Decode([]byte(`{}`), nil, n); err != n.nestedErr { + t.Errorf("unexpected error: %v", err) + } + if !n.nestedCalled { + t.Errorf("did not invoke nested decoder") + } +} + +func TestNestedDecodeStrictDecodingError(t *testing.T) { + strictErr := runtime.NewStrictDecodingError([]error{fmt.Errorf("duplicate field")}) + n := &testNestedDecodable{nestedErr: strictErr} + decoder := &mockSerializer{obj: n} + codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode") + o, _, err := codec.Decode([]byte(`{}`), nil, n) + if strictErr, ok := runtime.AsStrictDecodingError(err); !ok || err != strictErr { + t.Errorf("unexpected error: %v", err) + } + if o != n { + t.Errorf("did not successfully decode with strict decoding error: %v", o) + } + if !n.nestedCalled { + t.Errorf("did not invoke nested decoder") + } +} + +func TestNestedEncode(t *testing.T) { + n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} + n2 := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode 2")} + encoder := &mockSerializer{obj: n} + codec := NewCodec( + encoder, nil, + &checkConvertor{obj: n2, groupVersion: schema.GroupVersion{Group: "other"}}, + nil, + &mockTyper{gvks: []schema.GroupVersionKind{{Kind: "test"}}}, + nil, + schema.GroupVersion{Group: "other"}, nil, + "TestNestedEncode", + ) + if err := codec.Encode(n, ioutil.Discard); err != n2.nestedErr { + t.Errorf("unexpected error: %v", err) + } + if n.nestedCalled || !n2.nestedCalled { + t.Errorf("did not invoke correct nested decoder") + } +} + +func TestNestedEncodeError(t *testing.T) { + n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to encode")} + gvk1 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v1"} + gvk2 := schema.GroupVersionKind{Kind: "test", Group: "other", Version: "v2"} + n.SetGroupVersionKind(gvk1) + encoder := &mockSerializer{obj: n} + codec := NewCodec( + encoder, nil, + &mockConvertor{}, + nil, + &mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, + nil, + schema.GroupVersion{Group: "other", Version: "v2"}, nil, + "TestNestedEncodeError", + ) + if err := codec.Encode(n, ioutil.Discard); err != n.nestedErr { + t.Errorf("unexpected error: %v", err) + } + if n.GroupVersionKind() != gvk1 { + t.Errorf("unexpected gvk of input object: %v", n.GroupVersionKind()) + } +} + +func TestDecode(t *testing.T) { + gvk1 := &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"} + decodable1 := &testDecodable{} + decodable2 := &testDecodable{} + decodable3 := &testDecodable{} + + testCases := []struct { + serializer runtime.Serializer + convertor runtime.ObjectConvertor + creater runtime.ObjectCreater + typer runtime.ObjectTyper + defaulter runtime.ObjectDefaulter + yaml bool + pretty bool + + encodes, decodes runtime.GroupVersioner + + defaultGVK *schema.GroupVersionKind + into runtime.Object + + errFn func(error) bool + expectedObject runtime.Object + sameObject runtime.Object + expectedGVK *schema.GroupVersionKind + }{ + { + serializer: &mockSerializer{actual: gvk1}, + convertor: &checkConvertor{groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, + expectedGVK: gvk1, + decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, + }, + { + serializer: &mockSerializer{actual: gvk1, obj: decodable1}, + convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}}, + expectedGVK: gvk1, + sameObject: decodable2, + decodes: schema.GroupVersion{Group: "other", Version: runtime.APIVersionInternal}, + }, + // defaultGVK.Group is allowed to force a conversion to the destination group + { + serializer: &mockSerializer{actual: gvk1, obj: decodable1}, + defaultGVK: &schema.GroupVersionKind{Group: "force"}, + convertor: &checkConvertor{in: decodable1, obj: decodable2, groupVersion: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}}, + expectedGVK: gvk1, + sameObject: decodable2, + decodes: schema.GroupVersion{Group: "force", Version: runtime.APIVersionInternal}, + }, + // uses direct conversion for into when objects differ + { + into: decodable3, + serializer: &mockSerializer{actual: gvk1, obj: decodable1}, + convertor: &checkConvertor{in: decodable1, obj: decodable3, directConvert: true}, + expectedGVK: gvk1, + sameObject: decodable3, + }, + // decode into the same version as the serialized object + { + decodes: schema.GroupVersions{gvk1.GroupVersion()}, + + serializer: &mockSerializer{actual: gvk1, obj: decodable1}, + convertor: &checkConvertor{in: decodable1, obj: decodable1, groupVersion: schema.GroupVersions{{Group: "other", Version: "blah"}}}, + expectedGVK: gvk1, + expectedObject: decodable1, + }, + } + + for i, test := range testCases { + t.Logf("%d", i) + s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.typer, test.defaulter, test.encodes, test.decodes, fmt.Sprintf("mock-%d", i)) + obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into) + + if !reflect.DeepEqual(test.expectedGVK, gvk) { + t.Errorf("%d: unexpected GVK: %v", i, gvk) + } + + switch { + case err == nil && test.errFn != nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil && test.errFn == nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + t.Errorf("%d: failed: %v", i, err) + } + if obj != nil { + t.Errorf("%d: should have returned nil object", i) + } + continue + } + + if test.into != nil && test.into != obj { + t.Errorf("%d: expected into to be returned: %v", i, obj) + continue + } + + switch { + case test.expectedObject != nil: + if !reflect.DeepEqual(test.expectedObject, obj) { + t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) + } + case test.sameObject != nil: + if test.sameObject != obj { + t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj)) + } + case obj != nil: + t.Errorf("%d: unexpected object: %#v", i, obj) + } + } +} + +type checkConvertor struct { + err error + in, obj runtime.Object + groupVersion runtime.GroupVersioner + directConvert bool +} + +func (c *checkConvertor) Convert(in, out, context interface{}) error { + if !c.directConvert { + return fmt.Errorf("unexpected call to Convert") + } + if c.in != nil && c.in != in { + return fmt.Errorf("unexpected in: %s", in) + } + if c.obj != nil && c.obj != out { + return fmt.Errorf("unexpected out: %s", out) + } + return c.err +} +func (c *checkConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { + if c.directConvert { + return nil, fmt.Errorf("unexpected call to ConvertToVersion") + } + if c.in != nil && c.in != in { + return nil, fmt.Errorf("unexpected in: %s", in) + } + if !reflect.DeepEqual(c.groupVersion, outVersion) { + return nil, fmt.Errorf("unexpected outversion: %s (%s)", outVersion, c.groupVersion) + } + return c.obj, c.err +} +func (c *checkConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { + return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") +} + +type mockConvertor struct { +} + +func (c *mockConvertor) Convert(in, out, context interface{}) error { + return fmt.Errorf("unexpect call to Convert") +} + +func (c *mockConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) { + objectKind := in.GetObjectKind() + inGVK := objectKind.GroupVersionKind() + if out, ok := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{inGVK}); ok { + objectKind.SetGroupVersionKind(out) + } else { + return nil, fmt.Errorf("unexpected conversion") + } + return in, nil +} + +func (c *mockConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { + return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") +} + +type mockSerializer struct { + err error + obj runtime.Object + encodingObjGVK schema.GroupVersionKind + + defaults, actual *schema.GroupVersionKind + into runtime.Object +} + +func (s *mockSerializer) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + s.defaults = defaults + s.into = into + return s.obj, s.actual, s.err +} + +func (s *mockSerializer) Encode(obj runtime.Object, w io.Writer) error { + s.obj = obj + s.encodingObjGVK = obj.GetObjectKind().GroupVersionKind() + return s.err +} + +func (s *mockSerializer) Identifier() runtime.Identifier { + return runtime.Identifier("mock") +} + +type mockTyper struct { + gvks []schema.GroupVersionKind + unversioned bool + err error +} + +func (t *mockTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { + return t.gvks, t.unversioned, t.err +} + +func (t *mockTyper) Recognizes(_ schema.GroupVersionKind) bool { + return true +} + +func TestDirectCodecEncode(t *testing.T) { + serializer := mockSerializer{} + typer := mockTyper{ + gvks: []schema.GroupVersionKind{ + { + Group: "wrong_group", + Kind: "some_kind", + }, + { + Group: "expected_group", + Kind: "some_kind", + }, + }, + } + + c := runtime.WithVersionEncoder{ + Version: schema.GroupVersion{Group: "expected_group"}, + Encoder: &serializer, + ObjectTyper: &typer, + } + c.Encode(&testDecodable{}, ioutil.Discard) + if e, a := "expected_group", serializer.encodingObjGVK.Group; e != a { + t.Errorf("expected group to be %v, got %v", e, a) + } +} + +func TestCacheableObject(t *testing.T) { + gvk1 := schema.GroupVersionKind{Group: "group", Version: "version1", Kind: "MockCacheableObject"} + gvk2 := schema.GroupVersionKind{Group: "group", Version: "version2", Kind: "MockCacheableObject"} + + encoder := NewCodec( + &mockSerializer{}, &mockSerializer{}, + &mockConvertor{}, nil, + &mockTyper{gvks: []schema.GroupVersionKind{gvk1, gvk2}}, nil, + gvk1.GroupVersion(), gvk2.GroupVersion(), + "TestCacheableObject") + + runtimetesting.CacheableObjectTest(t, encoder) +} + +func BenchmarkIdentifier(b *testing.B) { + encoder := &mockSerializer{} + gv := schema.GroupVersion{Group: "group", Version: "version"} + + for i := 0; i < b.N; i++ { + id := identifier(gv, encoder) + // Avoid optimizing by compiler. + if id[0] != '{' { + b.Errorf("unexpected identifier: %s", id) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning_unstructured_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning_unstructured_test.go new file mode 100644 index 0000000000..e47a259f69 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning_unstructured_test.go @@ -0,0 +1,338 @@ +/* +Copyright 2015 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 versioning + +import ( + "fmt" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func buildUnstructuredDecodable(gvk schema.GroupVersionKind) runtime.Object { + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(gvk) + return obj +} + +func buildUnstructuredListDecodable(gvk schema.GroupVersionKind) runtime.Object { + obj := &unstructured.UnstructuredList{} + obj.SetGroupVersionKind(gvk) + return obj +} + +func TestEncodeUnstructured(t *testing.T) { + v1GVK := schema.GroupVersionKind{ + Group: "crispy", + Version: "v1", + Kind: "Noxu", + } + v2GVK := schema.GroupVersionKind{ + Group: "crispy", + Version: "v2", + Kind: "Noxu", + } + elseGVK := schema.GroupVersionKind{ + Group: "crispy2", + Version: "else", + Kind: "Noxu", + } + elseUnstructuredDecodable := buildUnstructuredDecodable(elseGVK) + elseUnstructuredDecodableList := buildUnstructuredListDecodable(elseGVK) + v1UnstructuredDecodable := buildUnstructuredDecodable(v1GVK) + v1UnstructuredDecodableList := buildUnstructuredListDecodable(v1GVK) + v2UnstructuredDecodable := buildUnstructuredDecodable(v2GVK) + + testCases := []struct { + name string + convertor runtime.ObjectConvertor + targetVersion runtime.GroupVersioner + outObj runtime.Object + typer runtime.ObjectTyper + + errFunc func(error) bool + expectedObj runtime.Object + }{ + { + name: "encode v1 unstructured with v2 encode version", + typer: &mockTyper{ + gvks: []schema.GroupVersionKind{v1GVK}, + }, + outObj: v1UnstructuredDecodable, + targetVersion: v2GVK.GroupVersion(), + convertor: &checkConvertor{ + obj: v2UnstructuredDecodable, + groupVersion: v2GVK.GroupVersion(), + }, + expectedObj: v2UnstructuredDecodable, + }, + { + name: "both typer and conversion are bypassed when unstructured gvk matches encode gvk", + typer: &mockTyper{ + err: fmt.Errorf("unexpected typer call"), + }, + outObj: v1UnstructuredDecodable, + targetVersion: v1GVK.GroupVersion(), + convertor: &checkConvertor{ + err: fmt.Errorf("unexpected conversion happened"), + }, + expectedObj: v1UnstructuredDecodable, + }, + { + name: "encode will fail when unstructured object's gvk and encode gvk mismatches", + outObj: elseUnstructuredDecodable, + targetVersion: v1GVK.GroupVersion(), + errFunc: func(err error) bool { + return assert.Equal(t, runtime.NewNotRegisteredGVKErrForTarget("noxu-scheme", elseGVK, v1GVK.GroupVersion()), err) + }, + }, + { + name: "encode with unstructured list's gvk regardless of its elements' gvk", + outObj: elseUnstructuredDecodableList, + targetVersion: elseGVK.GroupVersion(), + }, + { + name: "typer fail to recognize unstructured object gvk will fail the encoding", + outObj: elseUnstructuredDecodable, + targetVersion: v1GVK.GroupVersion(), + typer: &mockTyper{ + err: fmt.Errorf("invalid obj gvk"), + }, + }, + { + name: "encoding unstructured object without encode version will fallback to typer suggested version", + targetVersion: v1GVK.GroupVersion(), + convertor: &checkConvertor{ + obj: v1UnstructuredDecodableList, + groupVersion: v1GVK.GroupVersion(), + }, + outObj: elseUnstructuredDecodable, + typer: &mockTyper{ + gvks: []schema.GroupVersionKind{v1GVK}, + }, + }, + } + for _, testCase := range testCases { + serializer := &mockSerializer{} + codec := NewCodec(serializer, serializer, testCase.convertor, nil, testCase.typer, nil, testCase.targetVersion, nil, "noxu-scheme") + err := codec.Encode(testCase.outObj, ioutil.Discard) + if testCase.errFunc != nil { + if !testCase.errFunc(err) { + t.Errorf("%v: failed: %v", testCase.name, err) + } + return + } + assert.NoError(t, err) + assert.Equal(t, testCase.expectedObj, serializer.obj) + } +} + +type errNotRecognizedGVK struct { + failedGVK schema.GroupVersionKind + claimingGVKs []schema.GroupVersionKind +} + +func (e errNotRecognizedGVK) Error() string { + return fmt.Sprintf("unrecognized gvk %v, should be one of %v", e.failedGVK, e.claimingGVKs) +} + +type mockUnstructuredNopConvertor struct { + claimingGVKs []schema.GroupVersionKind +} + +func (c *mockUnstructuredNopConvertor) recognizeGVK(gvkToCheck schema.GroupVersionKind) error { + matched := false + for _, gvk := range c.claimingGVKs { + if gvk == gvkToCheck { + matched = true + } + } + if !matched { + return errNotRecognizedGVK{ + failedGVK: gvkToCheck, + claimingGVKs: c.claimingGVKs, + } + } + return nil +} + +func (c *mockUnstructuredNopConvertor) Convert(in, out, context interface{}) error { + inObj := in.(*unstructured.Unstructured) + outObj := out.(*unstructured.Unstructured) + if err := c.recognizeGVK(outObj.GroupVersionKind()); err != nil { + return err + } + outGVK := outObj.GetObjectKind().GroupVersionKind() + *outObj = *inObj.DeepCopy() + outObj.GetObjectKind().SetGroupVersionKind(outGVK) + return nil +} + +func (c *mockUnstructuredNopConvertor) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (runtime.Object, error) { + out := in.DeepCopyObject() + targetGVK, matched := outVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{in.GetObjectKind().GroupVersionKind()}) + if !matched { + return nil, fmt.Errorf("attempt to convert to mismatched gv %v", outVersion) + } + if err := c.recognizeGVK(out.GetObjectKind().GroupVersionKind()); err != nil { + return nil, err + } + out.GetObjectKind().SetGroupVersionKind(targetGVK) + return out, nil +} + +func (c *mockUnstructuredNopConvertor) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { + return "", "", fmt.Errorf("unexpected call to ConvertFieldLabel") +} + +func TestDecodeUnstructured(t *testing.T) { + internalGVK := schema.GroupVersionKind{ + Group: "crispy", + Version: runtime.APIVersionInternal, + Kind: "Noxu", + } + v1GVK := schema.GroupVersionKind{ + Group: "crispy", + Version: "v1", + Kind: "Noxu", + } + v2GVK := schema.GroupVersionKind{ + Group: "crispy", + Version: "v2", + Kind: "Noxu", + } + internalUnstructuredDecodable := buildUnstructuredDecodable(internalGVK) + v1UnstructuredDecodable := buildUnstructuredDecodable(v1GVK) + v2UnstructuredDecodable := buildUnstructuredDecodable(v2GVK) + + testCases := []struct { + name string + serializer runtime.Serializer + convertor runtime.ObjectConvertor + suggestedConvertVersion runtime.GroupVersioner + defaultGVK *schema.GroupVersionKind + intoObj runtime.Object + + errFunc func(error) bool + expectedGVKOfSerializedData *schema.GroupVersionKind + expectedOut runtime.Object + }{ + { + name: "decode v1 unstructured into non-nil v2 unstructured", + serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, + convertor: &mockUnstructuredNopConvertor{ + claimingGVKs: []schema.GroupVersionKind{ + v1GVK, v2GVK, + }, + }, + suggestedConvertVersion: v2GVK.GroupVersion(), + intoObj: v2UnstructuredDecodable, + expectedGVKOfSerializedData: &v1GVK, + expectedOut: v2UnstructuredDecodable, + }, + { + name: "decode v1 unstructured into nil object with v2 version", + serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, + convertor: &mockUnstructuredNopConvertor{ + claimingGVKs: []schema.GroupVersionKind{ + v1GVK, v2GVK, + }, + }, + suggestedConvertVersion: v2GVK.GroupVersion(), + intoObj: nil, + expectedGVKOfSerializedData: &v1GVK, + expectedOut: v2UnstructuredDecodable, + }, + { + name: "decode v1 unstructured into non-nil internal unstructured", + serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, + convertor: &mockUnstructuredNopConvertor{ + claimingGVKs: []schema.GroupVersionKind{ + v1GVK, v2GVK, + }, + }, + suggestedConvertVersion: internalGVK.GroupVersion(), + intoObj: internalUnstructuredDecodable, + errFunc: func(err error) bool { + notRecognized, ok := err.(errNotRecognizedGVK) + if !ok { + return false + } + return assert.Equal(t, notRecognized.failedGVK, internalGVK) + }, + }, + { + name: "decode v1 unstructured into nil object with internal version", + serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, + convertor: &mockUnstructuredNopConvertor{ + claimingGVKs: []schema.GroupVersionKind{ + v1GVK, v2GVK, + }, + }, + suggestedConvertVersion: internalGVK.GroupVersion(), + intoObj: nil, + errFunc: func(err error) bool { + notRecognized, ok := err.(errNotRecognizedGVK) + if !ok { + return false + } + return assert.Equal(t, notRecognized.failedGVK, internalGVK) + }, + }, + { + name: "skip conversion if serializer returns the same unstructured as into", + serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, + convertor: &checkConvertor{ + err: fmt.Errorf("unexpected conversion happened"), + }, + suggestedConvertVersion: internalGVK.GroupVersion(), + intoObj: v1UnstructuredDecodable, + expectedGVKOfSerializedData: &v1GVK, + expectedOut: v1UnstructuredDecodable, + }, + { + name: "invalid convert version makes decoding unstructured fail", + serializer: &mockSerializer{actual: &v1GVK, obj: v1UnstructuredDecodable}, + convertor: &checkConvertor{ + in: v1UnstructuredDecodable, + groupVersion: internalGVK.GroupVersion(), + err: fmt.Errorf("no matching decode version"), + }, + suggestedConvertVersion: internalGVK.GroupVersion(), + errFunc: func(err error) bool { + return assert.Equal(t, err, fmt.Errorf("no matching decode version")) + }, + }, + } + for _, testCase := range testCases { + codec := NewCodec(testCase.serializer, testCase.serializer, testCase.convertor, nil, nil, nil, nil, testCase.suggestedConvertVersion, "noxu-scheme") + actualObj, actualSerializedGVK, err := codec.Decode([]byte(`{}`), testCase.defaultGVK, testCase.intoObj) + if testCase.errFunc != nil { + if !testCase.errFunc(err) { + t.Errorf("%v: failed: %v", testCase.name, err) + } + return + } + assert.NoError(t, err) + assert.Equal(t, testCase.expectedOut, actualObj, "%v failed", testCase.name) + assert.Equal(t, testCase.expectedGVKOfSerializedData, actualSerializedGVK, "%v failed", testCase.name) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/meta.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/meta.go new file mode 100644 index 0000000000..407a7419a6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/meta.go @@ -0,0 +1,50 @@ +/* +Copyright 2019 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 yaml + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/yaml" +) + +// DefaultMetaFactory is a default factory for versioning objects in JSON or +// YAML. The object in memory and in the default serialization will use the +// "kind" and "apiVersion" fields. +var DefaultMetaFactory = SimpleMetaFactory{} + +// SimpleMetaFactory provides default methods for retrieving the type and version of objects +// that are identified with an "apiVersion" and "kind" fields in their JSON +// serialization. It may be parameterized with the names of the fields in memory, or an +// optional list of base structs to search for those fields in memory. +type SimpleMetaFactory struct{} + +// Interpret will return the APIVersion and Kind of the JSON wire-format +// encoding of an object, or an error. +func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) { + gvk := runtime.TypeMeta{} + if err := yaml.Unmarshal(data, &gvk); err != nil { + return nil, fmt.Errorf("could not interpret GroupVersionKind; unmarshal error: %v", err) + } + gv, err := schema.ParseGroupVersion(gvk.APIVersion) + if err != nil { + return nil, err + } + return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvk.Kind}, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/meta_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/meta_test.go new file mode 100644 index 0000000000..ba560008ea --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/meta_test.go @@ -0,0 +1,160 @@ +/* +Copyright 2019 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 yaml + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestInterpret(t *testing.T) { + testCases := []struct { + name string + input string + expected *schema.GroupVersionKind + errFn func(error) bool + }{ + { + name: "YAMLSuccessfullyInterpretVK", + input: `apiVersion: v1 +kind: Service`, + expected: &schema.GroupVersionKind{Version: "v1", Kind: "Service"}, + }, + { + name: "YAMLSuccessfullyInterpretGVK", + input: `apiVersion: core/v2 +kind: Deployment`, + expected: &schema.GroupVersionKind{Group: "core", Version: "v2", Kind: "Deployment"}, + }, + { + name: "YAMLSuccessfullyInterpretV", + input: `apiVersion: v1`, + expected: &schema.GroupVersionKind{Version: "v1"}, + }, + { + name: "YAMLSuccessfullyInterpretK", + input: `kind: Service`, + expected: &schema.GroupVersionKind{Kind: "Service"}, + }, + { + name: "YAMLSuccessfullyInterpretEmptyString", + input: ``, + expected: &schema.GroupVersionKind{}, + }, + { + name: "YAMLSuccessfullyInterpretEmptyDoc", + input: `---`, + expected: &schema.GroupVersionKind{}, + }, + { + name: "YAMLSuccessfullyInterpretMultiDoc", + input: `--- +apiVersion: v1 +kind: Service +--- +apiVersion: v2 +kind: Deployment`, + expected: &schema.GroupVersionKind{Version: "v1", Kind: "Service"}, + }, + { + name: "YAMLSuccessfullyInterpretOnlyG", + input: `apiVersion: core/`, + expected: &schema.GroupVersionKind{Group: "core"}, + }, + { + name: "YAMLSuccessfullyWrongFormat", + input: `foo: bar`, + expected: &schema.GroupVersionKind{}, + }, + { + name: "YAMLFailInterpretWrongSyntax", + input: `foo`, + errFn: func(err error) bool { return err != nil }, + }, + { + name: "JSONSuccessfullyInterpretVK", + input: `{"apiVersion": "v3", "kind": "DaemonSet"}`, + expected: &schema.GroupVersionKind{Version: "v3", Kind: "DaemonSet"}, + }, + { + name: "JSONSuccessfullyInterpretGVK", + input: `{"apiVersion": "core/v2", "kind": "Deployment"}`, + expected: &schema.GroupVersionKind{Group: "core", Version: "v2", Kind: "Deployment"}, + }, + { + name: "JSONSuccessfullyInterpretV", + input: `{"apiVersion": "v1"}`, + expected: &schema.GroupVersionKind{Version: "v1"}, + }, + { + name: "JSONSuccessfullyInterpretK", + input: `{"kind": "Service"}`, + expected: &schema.GroupVersionKind{Kind: "Service"}, + }, + { + name: "JSONSuccessfullyInterpretEmptyString", + input: ``, + expected: &schema.GroupVersionKind{}, + }, + { + name: "JSONSuccessfullyInterpretEmptyObject", + input: `{}`, + expected: &schema.GroupVersionKind{}, + }, + { + name: "JSONSuccessfullyInterpretMultiDoc", + input: `{"apiVersion": "v1", "kind": "Service"}, +{"apiVersion": "v2", "kind": "Deployment"}`, + expected: &schema.GroupVersionKind{Version: "v1", Kind: "Service"}, + }, + { + name: "JSONSuccessfullyWrongFormat", + input: `{"foo": "bar"}`, + expected: &schema.GroupVersionKind{}, + }, + { + name: "JSONFailInterpretArray", + input: `[]`, + errFn: func(err error) bool { return err != nil }, + }, + { + name: "JSONFailInterpretWrongSyntax", + input: `{"foo"`, + errFn: func(err error) bool { return err != nil }, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actual, err := DefaultMetaFactory.Interpret([]byte(test.input)) + switch { + case test.errFn != nil: + if !test.errFn(err) { + t.Errorf("unexpected error: %v", err) + } + case err != nil: + t.Errorf("unexpected error: %v", err) + case !reflect.DeepEqual(test.expected, actual): + t.Errorf("outcome mismatch -- expected: %#v, actual: %#v", + test.expected, actual, + ) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml.go new file mode 100644 index 0000000000..2fdd1d43d5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml.go @@ -0,0 +1,46 @@ +/* +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 yaml + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/yaml" +) + +// yamlSerializer converts YAML passed to the Decoder methods to JSON. +type yamlSerializer struct { + // the nested serializer + runtime.Serializer +} + +// yamlSerializer implements Serializer +var _ runtime.Serializer = yamlSerializer{} + +// NewDecodingSerializer adds YAML decoding support to a serializer that supports JSON. +func NewDecodingSerializer(jsonSerializer runtime.Serializer) runtime.Serializer { + return &yamlSerializer{jsonSerializer} +} + +func (c yamlSerializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + out, err := yaml.ToJSON(data) + if err != nil { + return nil, nil, err + } + data = out + return c.Serializer.Decode(data, gvk, into) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml_test.go new file mode 100644 index 0000000000..9a94e4af7a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml_test.go @@ -0,0 +1,414 @@ +/* +Copyright 2019 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 yaml + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/yaml" + sigsyaml "sigs.k8s.io/yaml" +) + +type testcase struct { + name string + data []byte + error string + + benchmark bool +} + +func testcases() []testcase { + return []testcase{ + { + name: "arrays of string aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a ["webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb","webwebwebwebwebweb"] +b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] +c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] +d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] +e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] +f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] +g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] +h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] +i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + }, + { + name: "arrays of empty string aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a ["","","","","","","","",""] +b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] +c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] +d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] +e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] +f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] +g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] +h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] +i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "arrays of null aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a [null,null,null,null,null,null,null,null,null] +b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] +c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] +d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] +e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] +f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] +g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] +h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] +i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "arrays of zero int aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a [0,0,0,0,0,0,0,0,0] +b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] +c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] +d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] +e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] +f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] +g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] +h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] +i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "arrays of zero float aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0] +b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] +c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] +d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] +e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] +f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] +g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] +h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] +i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "arrays of big float aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a [1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678,1234567890.12345678] +b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] +c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] +d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] +e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] +f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] +g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] +h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] +i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "arrays of bool aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a [true,true,true,true,true,true,true,true,true] +b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a,*a] +c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b,*b] +d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c,*c] +e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d,*d] +f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e,*e] +g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f,*f] +h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g,*g] +i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h,*h] +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "map key aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a {"verylongkey1":"","verylongkey2":"","verylongkey3":"","verylongkey4":"","verylongkey5":"","verylongkey6":"","verylongkey7":"","verylongkey8":"","verylongkey9":""} +b: &b {"verylongkey1":*a,"verylongkey2":*a,"verylongkey3":*a,"verylongkey4":*a,"verylongkey5":*a,"verylongkey6":*a,"verylongkey7":*a,"verylongkey8":*a,"verylongkey9":*a} +c: &c {"verylongkey1":*b,"verylongkey2":*b,"verylongkey3":*b,"verylongkey4":*b,"verylongkey5":*b,"verylongkey6":*b,"verylongkey7":*b,"verylongkey8":*b,"verylongkey9":*b} +d: &d {"verylongkey1":*c,"verylongkey2":*c,"verylongkey3":*c,"verylongkey4":*c,"verylongkey5":*c,"verylongkey6":*c,"verylongkey7":*c,"verylongkey8":*c,"verylongkey9":*c} +e: &e {"verylongkey1":*d,"verylongkey2":*d,"verylongkey3":*d,"verylongkey4":*d,"verylongkey5":*d,"verylongkey6":*d,"verylongkey7":*d,"verylongkey8":*d,"verylongkey9":*d} +f: &f {"verylongkey1":*e,"verylongkey2":*e,"verylongkey3":*e,"verylongkey4":*e,"verylongkey5":*e,"verylongkey6":*e,"verylongkey7":*e,"verylongkey8":*e,"verylongkey9":*e} +g: &g {"verylongkey1":*f,"verylongkey2":*f,"verylongkey3":*f,"verylongkey4":*f,"verylongkey5":*f,"verylongkey6":*f,"verylongkey7":*f,"verylongkey8":*f,"verylongkey9":*f} +h: &h {"verylongkey1":*g,"verylongkey2":*g,"verylongkey3":*g,"verylongkey4":*g,"verylongkey5":*g,"verylongkey6":*g,"verylongkey7":*g,"verylongkey8":*g,"verylongkey9":*g} +i: &i {"verylongkey1":*h,"verylongkey2":*h,"verylongkey3":*h,"verylongkey4":*h,"verylongkey5":*h,"verylongkey6":*h,"verylongkey7":*h,"verylongkey8":*h,"verylongkey9":*h} +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "map value aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a {"1":"verylongmapvalue","2":"verylongmapvalue","3":"verylongmapvalue","4":"verylongmapvalue","5":"verylongmapvalue","6":"verylongmapvalue","7":"verylongmapvalue","8":"verylongmapvalue","9":"verylongmapvalue"} +b: &b {"1":*a,"2":*a,"3":*a,"4":*a,"5":*a,"6":*a,"7":*a,"8":*a,"9":*a} +c: &c {"1":*b,"2":*b,"3":*b,"4":*b,"5":*b,"6":*b,"7":*b,"8":*b,"9":*b} +d: &d {"1":*c,"2":*c,"3":*c,"4":*c,"5":*c,"6":*c,"7":*c,"8":*c,"9":*c} +e: &e {"1":*d,"2":*d,"3":*d,"4":*d,"5":*d,"6":*d,"7":*d,"8":*d,"9":*d} +f: &f {"1":*e,"2":*e,"3":*e,"4":*e,"5":*e,"6":*e,"7":*e,"8":*e,"9":*e} +g: &g {"1":*f,"2":*f,"3":*f,"4":*f,"5":*f,"6":*f,"7":*f,"8":*f,"9":*f} +h: &h {"1":*g,"2":*g,"3":*g,"4":*g,"5":*g,"6":*g,"7":*g,"8":*g,"9":*g} +i: &i {"1":*h,"2":*h,"3":*h,"4":*h,"5":*h,"6":*h,"7":*h,"8":*h,"9":*h} +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "nested map aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a {"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{"":{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} +b: &b {"1":*a,"2":*a,"3":*a,"4":*a,"5":*a,"6":*a,"7":*a,"8":*a,"9":*a} +c: &c {"1":*b,"2":*b,"3":*b,"4":*b,"5":*b,"6":*b,"7":*b,"8":*b,"9":*b} +d: &d {"1":*c,"2":*c,"3":*c,"4":*c,"5":*c,"6":*c,"7":*c,"8":*c,"9":*c} +e: &e {"1":*d,"2":*d,"3":*d,"4":*d,"5":*d,"6":*d,"7":*d,"8":*d,"9":*d} +f: &f {"1":*e,"2":*e,"3":*e,"4":*e,"5":*e,"6":*e,"7":*e,"8":*e,"9":*e} +g: &g {"1":*f,"2":*f,"3":*f,"4":*f,"5":*f,"6":*f,"7":*f,"8":*f,"9":*f} +h: &h {"1":*g,"2":*g,"3":*g,"4":*g,"5":*g,"6":*g,"7":*g,"8":*g,"9":*g} +i: &i {"1":*h,"2":*h,"3":*h,"4":*h,"5":*h,"6":*h,"7":*h,"8":*h,"9":*h} +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "nested slice aliases", + error: "excessive aliasing", + data: []byte(` +apiVersion: v1 +data: +a: &a [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[""]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] +b: &b [[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]],[[[[[[[[[*a]]]]]]]]]] +c: &c [[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]],[[[[[[[[[*b]]]]]]]]]] +d: &d [[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]],[[[[[[[[[*c]]]]]]]]]] +e: &e [[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]],[[[[[[[[[*d]]]]]]]]]] +f: &f [[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]],[[[[[[[[[*e]]]]]]]]]] +g: &g [[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]],[[[[[[[[[*f]]]]]]]]]] +h: &h [[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]],[[[[[[[[[*g]]]]]]]]]] +i: &i [[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]],[[[[[[[[[*h]]]]]]]]]] +kind: ConfigMap +metadata: +name: yaml-bomb +namespace: default +`), + benchmark: true, + }, + { + name: "3MB map without alias", + data: []byte(`a: &a [{a}` + strings.Repeat(`,{a}`, 3*1024*1024/4) + `]`), + benchmark: true, + }, + { + name: "3MB map with alias", + error: "excessive aliasing", + data: []byte(` +a: &a [{a}` + strings.Repeat(`,{a}`, 3*1024*1024/4) + `] +b: &b [*a]`), + benchmark: true, + }, + { + name: "deeply nested slices", + error: "max depth", + data: []byte(strings.Repeat(`[`, 3*1024*1024)), + }, + { + name: "deeply nested maps", + error: "max depth", + data: []byte("x: " + strings.Repeat(`{`, 3*1024*1024)), + }, + { + name: "deeply nested indents", + error: "max depth", + data: []byte(strings.Repeat(`- `, 3*1024*1024)), + }, + { + name: "3MB of 1000-indent lines", + data: []byte(strings.Repeat(strings.Repeat(`- `, 1000)+"\n", 3*1024/2)), + benchmark: true, + }, + { + name: "3MB of empty slices", + data: []byte(`[` + strings.Repeat(`[],`, 3*1024*1024/3-2) + `[]]`), + benchmark: true, + }, + { + name: "3MB of slices", + data: []byte(`[` + strings.Repeat(`[0],`, 3*1024*1024/4-2) + `[0]]`), + benchmark: true, + }, + { + name: "3MB of empty maps", + data: []byte(`[` + strings.Repeat(`{},`, 3*1024*1024/3-2) + `{}]`), + benchmark: true, + }, + { + name: "3MB of maps", + data: []byte(`[` + strings.Repeat(`{a},`, 3*1024*1024/4-2) + `{a}]`), + benchmark: true, + }, + { + name: "3MB of ints", + data: []byte(`[` + strings.Repeat(`0,`, 3*1024*1024/2-2) + `0]`), + benchmark: true, + }, + { + name: "3MB of floats", + data: []byte(`[` + strings.Repeat(`0.0,`, 3*1024*1024/4-2) + `0.0]`), + benchmark: true, + }, + { + name: "3MB of bools", + data: []byte(`[` + strings.Repeat(`true,`, 3*1024*1024/5-2) + `true]`), + benchmark: true, + }, + { + name: "3MB of empty strings", + data: []byte(`[` + strings.Repeat(`"",`, 3*1024*1024/3-2) + `""]`), + benchmark: true, + }, + { + name: "3MB of strings", + data: []byte(`[` + strings.Repeat(`"abcdefghijklmnopqrstuvwxyz012",`, 3*1024*1024/30-2) + `"abcdefghijklmnopqrstuvwxyz012"]`), + benchmark: true, + }, + { + name: "3MB of nulls", + data: []byte(`[` + strings.Repeat(`null,`, 3*1024*1024/5-2) + `null]`), + benchmark: true, + }, + } +} + +var decoders = map[string]func([]byte) ([]byte, error){ + "sigsyaml": sigsyaml.YAMLToJSON, + "utilyaml": yaml.ToJSON, +} + +func TestYAMLLimits(t *testing.T) { + for _, tc := range testcases() { + if tc.benchmark { + continue + } + t.Run(tc.name, func(t *testing.T) { + for decoderName, decoder := range decoders { + t.Run(decoderName, func(t *testing.T) { + _, err := decoder(tc.data) + if len(tc.error) == 0 { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + } else { + if err == nil || !strings.Contains(err.Error(), tc.error) { + t.Errorf("expected %q error, got %v", tc.error, err) + } + } + }) + } + }) + } +} + +func BenchmarkYAMLLimits(b *testing.B) { + for _, tc := range testcases() { + b.Run(tc.name, func(b *testing.B) { + for decoderName, decoder := range decoders { + b.Run(decoderName, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := decoder(tc.data) + if len(tc.error) == 0 { + if err != nil { + b.Errorf("unexpected error: %v", err) + } + } else { + if err == nil || !strings.Contains(err.Error(), tc.error) { + b.Errorf("expected %q error, got %v", tc.error, err) + } + } + } + }) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/splice.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/splice.go new file mode 100644 index 0000000000..2badb7b97f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/splice.go @@ -0,0 +1,76 @@ +/* +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 runtime + +import ( + "bytes" + "io" +) + +// Splice is the interface that wraps the Splice method. +// +// Splice moves data from given slice without copying the underlying data for +// efficiency purpose. Therefore, the caller should make sure the underlying +// data is not changed later. +type Splice interface { + Splice([]byte) + io.Writer + Reset() + Bytes() []byte +} + +// A spliceBuffer implements Splice and io.Writer interfaces. +type spliceBuffer struct { + raw []byte + buf *bytes.Buffer +} + +func NewSpliceBuffer() Splice { + return &spliceBuffer{} +} + +// Splice implements the Splice interface. +func (sb *spliceBuffer) Splice(raw []byte) { + sb.raw = raw +} + +// Write implements the io.Writer interface. +func (sb *spliceBuffer) Write(p []byte) (n int, err error) { + if sb.buf == nil { + sb.buf = &bytes.Buffer{} + } + return sb.buf.Write(p) +} + +// Reset resets the buffer to be empty. +func (sb *spliceBuffer) Reset() { + if sb.buf != nil { + sb.buf.Reset() + } + sb.raw = nil +} + +// Bytes returns the data held by the buffer. +func (sb *spliceBuffer) Bytes() []byte { + if sb.buf != nil && len(sb.buf.Bytes()) > 0 { + return sb.buf.Bytes() + } + if sb.raw != nil { + return sb.raw + } + return []byte{} +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/splice_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/splice_test.go new file mode 100644 index 0000000000..9d8ca5102d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/splice_test.go @@ -0,0 +1,121 @@ +/* +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 runtime_test + +import ( + "bytes" + "testing" + + "k8s.io/apimachinery/pkg/runtime" +) + +func TestSpliceBuffer(t *testing.T) { + testBytes0 := []byte{0x01, 0x02, 0x03, 0x04} + testBytes1 := []byte{0x04, 0x03, 0x02, 0x02} + + testCases := []struct { + name string + run func(sb runtime.Splice, buf *bytes.Buffer) + }{ + { + name: "Basic Write", + run: func(sb runtime.Splice, buf *bytes.Buffer) { + sb.Write(testBytes0) + buf.Write(testBytes0) + }, + }, + { + name: "Multiple Writes", + run: func(sb runtime.Splice, buf *bytes.Buffer) { + for _, b := range testBytes0 { + sb.Write([]byte{b}) + buf.Write([]byte{b}) + } + }, + }, + { + name: "Write and Reset", + run: func(sb runtime.Splice, buf *bytes.Buffer) { + sb.Write(testBytes0) + buf.Write(testBytes0) + + sb.Reset() + buf.Reset() + }, + }, + { + name: "Write/Splice", + run: func(sb runtime.Splice, buf *bytes.Buffer) { + sb.Splice(testBytes0) + buf.Write(testBytes0) + }, + }, + { + name: "Write/Splice and Reset", + run: func(sb runtime.Splice, buf *bytes.Buffer) { + sb.Splice(testBytes0) + buf.Write(testBytes0) + + sb.Reset() + buf.Reset() + }, + }, + { + name: "Write/Splice, Reset, Write/Splice", + run: func(sb runtime.Splice, buf *bytes.Buffer) { + sb.Splice(testBytes0) + buf.Write(testBytes0) + + sb.Reset() + buf.Reset() + + sb.Splice(testBytes1) + buf.Write(testBytes1) + }, + }, + { + name: "Write, Reset, Splice", + run: func(sb runtime.Splice, buf *bytes.Buffer) { + sb.Write(testBytes0) + buf.Write(testBytes0) + + sb.Reset() + buf.Reset() + + sb.Splice(testBytes1) + buf.Write(testBytes1) + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + sb := runtime.NewSpliceBuffer() + buf := &bytes.Buffer{} + tt.run(sb, buf) + + if sb.Bytes() == nil { + t.Errorf("Unexpected nil") + } + if string(sb.Bytes()) != string(buf.Bytes()) { + t.Errorf("Expected sb.Bytes() == %q, buf.Bytes() == %q", sb.Bytes(), buf.Bytes()) + } + }) + + } + +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/swagger_doc_generator.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/swagger_doc_generator.go new file mode 100644 index 0000000000..0d5a3e709d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/swagger_doc_generator.go @@ -0,0 +1,274 @@ +/* +Copyright 2015 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 runtime + +import ( + "bytes" + "fmt" + "go/ast" + "go/doc" + "go/parser" + "go/token" + "io" + "reflect" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/util/errors" +) + +// Pair of strings. We keed the name of fields and the doc +type Pair struct { + Name, Doc string +} + +// KubeTypes is an array to represent all available types in a parsed file. [0] is for the type itself +type KubeTypes []Pair + +func astFrom(filePath string) *doc.Package { + fset := token.NewFileSet() + m := make(map[string]*ast.File) + + f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) + if err != nil { + fmt.Println(err) + return nil + } + + m[filePath] = f + apkg, _ := ast.NewPackage(fset, m, nil, nil) + + return doc.New(apkg, "", 0) +} + +func fmtRawDoc(rawDoc string) string { + var buffer bytes.Buffer + delPrevChar := func() { + if buffer.Len() > 0 { + buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n" + } + } + + // Ignore all lines after --- + rawDoc = strings.Split(rawDoc, "---")[0] + + for _, line := range strings.Split(rawDoc, "\n") { + line = strings.TrimRight(line, " ") + leading := strings.TrimLeft(line, " ") + switch { + case len(line) == 0: // Keep paragraphs + delPrevChar() + buffer.WriteString("\n\n") + case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs + case strings.HasPrefix(leading, "+"): // Ignore instructions to the generators + default: + if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { + delPrevChar() + line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..." + } else { + line += " " + } + buffer.WriteString(line) + } + } + + postDoc := strings.TrimRight(buffer.String(), "\n") + postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to " + postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape " + postDoc = strings.Replace(postDoc, "\n", "\\n", -1) + postDoc = strings.Replace(postDoc, "\t", "\\t", -1) + + return postDoc +} + +// fieldName returns the name of the field as it should appear in JSON format +// "-" indicates that this field is not part of the JSON representation +func fieldName(field *ast.Field) (string, error) { + jsonTag := "" + if field.Tag != nil { + var jsonTagExists bool + tagValue, err := strconv.Unquote(field.Tag.Value) + if err != nil { + return "", err + } + jsonTag, jsonTagExists = reflect.StructTag(tagValue).Lookup("json") // Delete first and last quotation + // field is embedded, json tag is declared and has an empty name + if field.Names == nil && jsonTagExists && (jsonTag == "" || strings.HasPrefix(jsonTag, ",")) { + return "-", nil + } + } + + jsonTag = strings.Split(jsonTag, ",")[0] // This can return "-" + if jsonTag == "" { + if field.Names != nil { + return field.Names[0].Name, nil + } + return field.Type.(*ast.Ident).Name, nil + } + return jsonTag, nil +} + +// A buffer of lines that will be written. +type bufferedLine struct { + line string + indentation int +} + +type buffer struct { + lines []bufferedLine +} + +func newBuffer() *buffer { + return &buffer{ + lines: make([]bufferedLine, 0), + } +} + +func (b *buffer) addLine(line string, indent int) { + b.lines = append(b.lines, bufferedLine{line, indent}) +} + +func (b *buffer) flushLines(w io.Writer) error { + for _, line := range b.lines { + indentation := strings.Repeat("\t", line.indentation) + fullLine := fmt.Sprintf("%s%s", indentation, line.line) + if _, err := io.WriteString(w, fullLine); err != nil { + return err + } + } + return nil +} + +func writeFuncHeader(b *buffer, structName string, indent int) { + s := fmt.Sprintf("var map_%s = map[string]string {\n", structName) + b.addLine(s, indent) +} + +func writeFuncFooter(b *buffer, structName string, indent int) { + b.addLine("}\n", indent) // Closes the map definition + + s := fmt.Sprintf("func (%s) SwaggerDoc() map[string]string {\n", structName) + b.addLine(s, indent) + s = fmt.Sprintf("return map_%s\n", structName) + b.addLine(s, indent+1) + b.addLine("}\n", indent) // Closes the function definition +} + +func writeMapBody(b *buffer, kubeType []Pair, indent int) { + format := "\"%s\": \"%s\",\n" + for _, pair := range kubeType { + s := fmt.Sprintf(format, pair.Name, pair.Doc) + b.addLine(s, indent+2) + } +} + +// ParseDocumentationFrom gets all types' documentation and returns them as an +// array. Each type is again represented as an array (we have to use arrays as we +// need to be sure for the order of the fields). This function returns fields and +// struct definitions that have no documentation as {name, ""}. +func ParseDocumentationFrom(src string) ([]KubeTypes, error) { + var docForTypes []KubeTypes + var errs []error + + pkg := astFrom(src) + + for _, kubType := range pkg.Types { + if structType, ok := kubType.Decl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType); ok { + var ks KubeTypes + ks = append(ks, Pair{kubType.Name, fmtRawDoc(kubType.Doc)}) + + for _, field := range structType.Fields.List { + if n, err := fieldName(field); err != nil { + errs = append(errs, err) + } else if n != "-" { + fieldDoc := fmtRawDoc(field.Doc.Text()) + ks = append(ks, Pair{n, fieldDoc}) + } + } + docForTypes = append(docForTypes, ks) + } + } + + return docForTypes, errors.NewAggregate(errs) +} + +// WriteSwaggerDocFunc writes a declaration of a function as a string. This function is used in +// Swagger as a documentation source for structs and theirs fields +func WriteSwaggerDocFunc(kubeTypes []KubeTypes, w io.Writer) error { + for _, kubeType := range kubeTypes { + structName := kubeType[0].Name + kubeType[0].Name = "" + + // Ignore empty documentation + docfulTypes := make(KubeTypes, 0, len(kubeType)) + for _, pair := range kubeType { + if pair.Doc != "" { + docfulTypes = append(docfulTypes, pair) + } + } + + if len(docfulTypes) == 0 { + continue // If no fields and the struct have documentation, skip the function definition + } + + indent := 0 + buffer := newBuffer() + + writeFuncHeader(buffer, structName, indent) + writeMapBody(buffer, docfulTypes, indent) + writeFuncFooter(buffer, structName, indent) + buffer.addLine("\n", 0) + + if err := buffer.flushLines(w); err != nil { + return err + } + } + + return nil +} + +// VerifySwaggerDocsExist writes in a io.Writer a list of structs and fields that +// are missing of documentation. +func VerifySwaggerDocsExist(kubeTypes []KubeTypes, w io.Writer) (int, error) { + missingDocs := 0 + buffer := newBuffer() + + for _, kubeType := range kubeTypes { + structName := kubeType[0].Name + if kubeType[0].Doc == "" { + format := "Missing documentation for the struct itself: %s\n" + s := fmt.Sprintf(format, structName) + buffer.addLine(s, 0) + missingDocs++ + } + kubeType = kubeType[1:] // Skip struct definition + + for _, pair := range kubeType { // Iterate only the fields + if pair.Doc == "" { + format := "In struct: %s, field documentation is missing: %s\n" + s := fmt.Sprintf(format, structName, pair.Name) + buffer.addLine(s, 0) + missingDocs++ + } + } + } + + if err := buffer.flushLines(w); err != nil { + return -1, err + } + return missingDocs, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/swagger_doc_generator_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/swagger_doc_generator_test.go new file mode 100644 index 0000000000..a6f338d311 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/swagger_doc_generator_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2015 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 runtime + +import ( + "testing" +) + +func TestFmtRawDoc(t *testing.T) { + tests := []struct { + t, expected string + }{ + {"aaa\n --- asd\n TODO: tooooodo\n toooodoooooo\n", "aaa"}, + {"aaa\nasd\n TODO: tooooodo\nbbbb\n --- toooodoooooo\n", "aaa asd bbbb"}, + {" TODO: tooooodo\n", ""}, + {"Par1\n\nPar2\n\n", "Par1\\n\\nPar2"}, + {"", ""}, + {" ", ""}, + {" \n", ""}, + {" \n\n ", ""}, + {"Example:\n\tl1\n\t\tl2\n", "Example:\\n\\tl1\\n\\t\\tl2"}, + } + + for _, test := range tests { + if o := fmtRawDoc(test.t); o != test.expected { + t.Fatalf("Expected: %q, got %q", test.expected, o) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/cacheable_object.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/cacheable_object.go new file mode 100644 index 0000000000..04c9dc8e08 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/cacheable_object.go @@ -0,0 +1,220 @@ +/* +Copyright 2019 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 testing + +import ( + "bytes" + "fmt" + "io" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// nonCacheableTestObject implements json.Marshaler and proto.Marshaler interfaces +// for mocking purpose. +// +k8s:deepcopy-gen=false +type noncacheableTestObject struct { + gvk schema.GroupVersionKind +} + +// MarshalJSON implements json.Marshaler interface. +func (*noncacheableTestObject) MarshalJSON() ([]byte, error) { + return []byte("\"json-result\""), nil +} + +// Marshal implements proto.Marshaler interface. +func (*noncacheableTestObject) Marshal() ([]byte, error) { + return []byte("\"proto-result\""), nil +} + +// DeepCopyObject implements runtime.Object interface. +func (*noncacheableTestObject) DeepCopyObject() runtime.Object { + panic("DeepCopy unimplemented for noncacheableTestObject") +} + +// GetObjectKind implements runtime.Object interface. +func (o *noncacheableTestObject) GetObjectKind() schema.ObjectKind { + return o +} + +// GroupVersionKind implements schema.ObjectKind interface. +func (o *noncacheableTestObject) GroupVersionKind() schema.GroupVersionKind { + return o.gvk +} + +// SetGroupVersionKind implements schema.ObjectKind interface. +func (o *noncacheableTestObject) SetGroupVersionKind(gvk schema.GroupVersionKind) { + o.gvk = gvk +} + +var _ runtime.CacheableObject = &MockCacheableObject{} + +// MochCacheableObject is used to test CacheableObject interface. +// +k8s:deepcopy-gen=false +type MockCacheableObject struct { + gvk schema.GroupVersionKind + + t *testing.T + + runEncode bool + returnSelf bool + expectedResult string + expectedError error + + intercepted []runtime.Identifier +} + +// DeepCopyObject implements runtime.Object interface. +func (m *MockCacheableObject) DeepCopyObject() runtime.Object { + panic("DeepCopy unimplemented for MockCacheableObject") +} + +// GetObjectKind implements runtime.Object interface. +func (m *MockCacheableObject) GetObjectKind() schema.ObjectKind { + return m +} + +// GroupVersionKind implements schema.ObjectKind interface. +func (m *MockCacheableObject) GroupVersionKind() schema.GroupVersionKind { + return m.gvk +} + +// SetGroupVersionKind implements schema.ObjectKind interface. +func (m *MockCacheableObject) SetGroupVersionKind(gvk schema.GroupVersionKind) { + m.gvk = gvk +} + +// Marshal implements proto.Marshaler interface. +// This is implemented to avoid errors from protobuf serializer. +func (*MockCacheableObject) Marshal() ([]byte, error) { + return []byte("\"proto-result\""), nil +} + +// CacheEncode implements runtime.CacheableObject interface. +func (m *MockCacheableObject) CacheEncode(id runtime.Identifier, encode func(runtime.Object, io.Writer) error, w io.Writer) error { + m.intercepted = append(m.intercepted, id) + if m.runEncode { + return encode(m.GetObject(), w) + } + if _, err := w.Write([]byte(m.expectedResult)); err != nil { + m.t.Errorf("couldn't write to io.Writer: %v", err) + } + return m.expectedError +} + +// GetObject implements runtime.CacheableObject interface. +func (m *MockCacheableObject) GetObject() runtime.Object { + if m.returnSelf { + return m + } + gvk := schema.GroupVersionKind{Group: "group", Version: "version", Kind: "noncacheableTestObject"} + return &noncacheableTestObject{gvk: gvk} +} + +func (m *MockCacheableObject) interceptedCalls() []runtime.Identifier { + return m.intercepted +} + +type testBuffer struct { + writer io.Writer + t *testing.T + object *MockCacheableObject +} + +// Write implements io.Writer interface. +func (b *testBuffer) Write(p []byte) (int, error) { + // Before writing any byte, check if has already + // intercepted any CacheEncode operation. + if len(b.object.interceptedCalls()) == 0 { + b.t.Errorf("writing to buffer without handling MockCacheableObject") + } + return b.writer.Write(p) +} + +// CacheableObjectTest implements a test that should be run for every +// runtime.Encoder interface implementation. +// It checks whether CacheableObject is properly supported by it. +func CacheableObjectTest(t *testing.T, e runtime.Encoder) { + gvk1 := schema.GroupVersionKind{Group: "group", Version: "version1", Kind: "MockCacheableObject"} + + testCases := []struct { + desc string + runEncode bool + returnSelf bool + expectedResult string + expectedError error + }{ + { + desc: "delegate", + runEncode: true, + }, + { + desc: "delegate return self", + runEncode: true, + returnSelf: true, + }, + { + desc: "cached success", + runEncode: false, + expectedResult: "result", + expectedError: nil, + }, + { + desc: "cached failure", + runEncode: false, + expectedResult: "", + expectedError: fmt.Errorf("encoding error"), + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + obj := &MockCacheableObject{ + gvk: gvk1, + t: t, + runEncode: test.runEncode, + returnSelf: test.returnSelf, + expectedResult: test.expectedResult, + expectedError: test.expectedError, + } + buffer := bytes.NewBuffer(nil) + w := &testBuffer{ + writer: buffer, + t: t, + object: obj, + } + + if err := e.Encode(obj, w); err != test.expectedError { + t.Errorf("unexpected error: %v, expected: %v", err, test.expectedError) + } + if !test.runEncode { + if result := buffer.String(); result != test.expectedResult { + t.Errorf("unexpected result: %s, expected: %s", result, test.expectedResult) + } + } + intercepted := obj.interceptedCalls() + if len(intercepted) != 1 { + t.Fatalf("unexpected number of intercepted calls: %v", intercepted) + } + if intercepted[0] != e.Identifier() { + t.Errorf("unexpected intercepted call: %v, expected: %v", intercepted, e.Identifier()) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/conversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/conversion.go new file mode 100644 index 0000000000..384929b17c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/conversion.go @@ -0,0 +1,300 @@ +/* +Copyright 2020 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 testing + +import ( + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" +) + +func convertEmbeddedTestToEmbeddedTestExternal(in *EmbeddedTest, out *EmbeddedTestExternal, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + out.ID = in.ID + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Object, &out.Object, s); err != nil { + return err + } + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.EmptyObject, &out.EmptyObject, s); err != nil { + return err + } + return nil +} + +func convertEmbeddedTestExternalToEmbeddedTest(in *EmbeddedTestExternal, out *EmbeddedTest, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + out.ID = in.ID + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Object, &out.Object, s); err != nil { + return err + } + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.EmptyObject, &out.EmptyObject, s); err != nil { + return err + } + return nil +} + +func convertObjectTestToObjectTestExternal(in *ObjectTest, out *ObjectTestExternal, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + out.ID = in.ID + if in.Items != nil { + out.Items = make([]runtime.RawExtension, len(in.Items)) + for i := range in.Items { + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func convertObjectTestExternalToObjectTest(in *ObjectTestExternal, out *ObjectTest, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + out.ID = in.ID + if in.Items != nil { + out.Items = make([]runtime.Object, len(in.Items)) + for i := range in.Items { + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func convertInternalSimpleToExternalSimple(in *InternalSimple, out *ExternalSimple, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + out.TestString = in.TestString + return nil +} + +func convertExternalSimpleToInternalSimple(in *ExternalSimple, out *InternalSimple, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + out.TestString = in.TestString + return nil +} + +func convertInternalExtensionTypeToExternalExtensionType(in *InternalExtensionType, out *ExternalExtensionType, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Extension, &out.Extension, s); err != nil { + return err + } + return nil +} + +func convertExternalExtensionTypeToInternalExtensionType(in *ExternalExtensionType, out *InternalExtensionType, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Extension, &out.Extension, s); err != nil { + return err + } + return nil +} + +func convertInternalOptionalExtensionTypeToExternalOptionalExtensionType(in *InternalOptionalExtensionType, out *ExternalOptionalExtensionType, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Extension, &out.Extension, s); err != nil { + return err + } + return nil +} + +func convertExternalOptionalExtensionTypeToInternalOptionalExtensionType(in *ExternalOptionalExtensionType, out *InternalOptionalExtensionType, s conversion.Scope) error { + out.TypeMeta = in.TypeMeta + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Extension, &out.Extension, s); err != nil { + return err + } + return nil +} + +func convertTestType1ToExternalTestType1(in *TestType1, out *ExternalTestType1, s conversion.Scope) error { + out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField + out.A = in.A + out.B = in.B + out.C = in.C + out.D = in.D + out.E = in.E + out.F = in.F + out.G = in.G + out.H = in.H + out.I = in.I + out.J = in.J + out.K = in.K + out.L = in.L + out.M = in.M + if in.N != nil { + out.N = make(map[string]ExternalTestType2) + for key := range in.N { + in, tmp := in.N[key], ExternalTestType2{} + if err := convertTestType2ToExternalTestType2(&in, &tmp, s); err != nil { + return err + } + out.N[key] = tmp + } + } else { + out.N = nil + } + if in.O != nil { + out.O = new(ExternalTestType2) + if err := convertTestType2ToExternalTestType2(in.O, out.O, s); err != nil { + return err + } + } else { + out.O = nil + } + if in.P != nil { + out.P = make([]ExternalTestType2, len(in.P)) + for i := range in.P { + if err := convertTestType2ToExternalTestType2(&in.P[i], &out.P[i], s); err != nil { + return err + } + } + } + return nil +} + +func convertExternalTestType1ToTestType1(in *ExternalTestType1, out *TestType1, s conversion.Scope) error { + out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField + out.A = in.A + out.B = in.B + out.C = in.C + out.D = in.D + out.E = in.E + out.F = in.F + out.G = in.G + out.H = in.H + out.I = in.I + out.J = in.J + out.K = in.K + out.L = in.L + out.M = in.M + if in.N != nil { + out.N = make(map[string]TestType2) + for key := range in.N { + in, tmp := in.N[key], TestType2{} + if err := convertExternalTestType2ToTestType2(&in, &tmp, s); err != nil { + return err + } + out.N[key] = tmp + } + } else { + out.N = nil + } + if in.O != nil { + out.O = new(TestType2) + if err := convertExternalTestType2ToTestType2(in.O, out.O, s); err != nil { + return err + } + } else { + out.O = nil + } + if in.P != nil { + out.P = make([]TestType2, len(in.P)) + for i := range in.P { + if err := convertExternalTestType2ToTestType2(&in.P[i], &out.P[i], s); err != nil { + return err + } + } + } + return nil +} + +func convertTestType2ToExternalTestType2(in *TestType2, out *ExternalTestType2, s conversion.Scope) error { + out.A = in.A + out.B = in.B + return nil +} + +func convertExternalTestType2ToTestType2(in *ExternalTestType2, out *TestType2, s conversion.Scope) error { + out.A = in.A + out.B = in.B + return nil +} + +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddConversionFunc((*EmbeddedTest)(nil), (*EmbeddedTestExternal)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertEmbeddedTestToEmbeddedTestExternal(a.(*EmbeddedTest), b.(*EmbeddedTestExternal), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*EmbeddedTestExternal)(nil), (*EmbeddedTest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertEmbeddedTestExternalToEmbeddedTest(a.(*EmbeddedTestExternal), b.(*EmbeddedTest), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ObjectTest)(nil), (*ObjectTestExternal)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertObjectTestToObjectTestExternal(a.(*ObjectTest), b.(*ObjectTestExternal), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ObjectTestExternal)(nil), (*ObjectTest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertObjectTestExternalToObjectTest(a.(*ObjectTestExternal), b.(*ObjectTest), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*InternalSimple)(nil), (*ExternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertInternalSimpleToExternalSimple(a.(*InternalSimple), b.(*ExternalSimple), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ExternalSimple)(nil), (*InternalSimple)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertExternalSimpleToInternalSimple(a.(*ExternalSimple), b.(*InternalSimple), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*InternalExtensionType)(nil), (*ExternalExtensionType)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertInternalExtensionTypeToExternalExtensionType(a.(*InternalExtensionType), b.(*ExternalExtensionType), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ExternalExtensionType)(nil), (*InternalExtensionType)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertExternalExtensionTypeToInternalExtensionType(a.(*ExternalExtensionType), b.(*InternalExtensionType), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*InternalOptionalExtensionType)(nil), (*ExternalOptionalExtensionType)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertInternalOptionalExtensionTypeToExternalOptionalExtensionType(a.(*InternalOptionalExtensionType), b.(*ExternalOptionalExtensionType), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ExternalOptionalExtensionType)(nil), (*InternalOptionalExtensionType)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertExternalOptionalExtensionTypeToInternalOptionalExtensionType(a.(*ExternalOptionalExtensionType), b.(*InternalOptionalExtensionType), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*TestType1)(nil), (*ExternalTestType1)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertTestType1ToExternalTestType1(a.(*TestType1), b.(*ExternalTestType1), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ExternalTestType1)(nil), (*TestType1)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertExternalTestType1ToTestType1(a.(*ExternalTestType1), b.(*TestType1), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*TestType2)(nil), (*ExternalTestType2)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertTestType2ToExternalTestType2(a.(*TestType2), b.(*ExternalTestType2), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*ExternalTestType2)(nil), (*TestType2)(nil), func(a, b interface{}, scope conversion.Scope) error { + return convertExternalTestType2ToTestType2(a.(*ExternalTestType2), b.(*TestType2), scope) + }); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/doc.go new file mode 100644 index 0000000000..a490327854 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2016 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. +*/ + +// +k8s:deepcopy-gen=package + +package testing diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/types.go new file mode 100644 index 0000000000..82abf13982 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/types.go @@ -0,0 +1,336 @@ +/* +Copyright 2016 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 testing + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/json" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type EmbeddedTest struct { + runtime.TypeMeta + ID string + Object runtime.Object + EmptyObject runtime.Object +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type EmbeddedTestExternal struct { + runtime.TypeMeta `json:""` + ID string `json:"id,omitempty"` + Object runtime.RawExtension `json:"object,omitempty"` + EmptyObject runtime.RawExtension `json:"emptyObject,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ObjectTest struct { + runtime.TypeMeta + + ID string + Items []runtime.Object +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ObjectTestExternal struct { + runtime.TypeMeta `yaml:",inline" json:""` + + ID string `json:"id,omitempty"` + Items []runtime.RawExtension `json:"items,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type InternalSimple struct { + runtime.TypeMeta `json:""` + TestString string `json:"testString"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalSimple struct { + runtime.TypeMeta `json:""` + TestString string `json:"testString"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExtensionA struct { + runtime.TypeMeta `json:""` + TestString string `json:"testString"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExtensionB struct { + runtime.TypeMeta `json:""` + TestString string `json:"testString"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalExtensionType struct { + runtime.TypeMeta `json:""` + Extension runtime.RawExtension `json:"extension"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type InternalExtensionType struct { + runtime.TypeMeta `json:""` + Extension runtime.Object `json:"extension"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalOptionalExtensionType struct { + runtime.TypeMeta `json:""` + Extension runtime.RawExtension `json:"extension,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type InternalOptionalExtensionType struct { + runtime.TypeMeta `json:""` + Extension runtime.Object `json:"extension,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type InternalComplex struct { + runtime.TypeMeta + String string + Integer int + Integer64 int64 + Int64 int64 + Bool bool +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalComplex struct { + runtime.TypeMeta `json:""` + String string `json:"string" description:"testing"` + Integer int `json:"int"` + Integer64 int64 `json:",omitempty"` + Int64 int64 + Bool bool `json:"bool"` +} + +// Test a weird version/kind embedding format. +// +k8s:deepcopy-gen=false +type MyWeirdCustomEmbeddedVersionKindField struct { + ID string `json:"ID,omitempty"` + APIVersion string `json:"myVersionKey,omitempty"` + ObjectKind string `json:"myKindKey,omitempty"` + Z string `json:"Z,omitempty"` + Y uint64 `json:"Y,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TestType1 struct { + MyWeirdCustomEmbeddedVersionKindField `json:""` + A string `json:"A,omitempty"` + B int `json:"B,omitempty"` + C int8 `json:"C,omitempty"` + D int16 `json:"D,omitempty"` + E int32 `json:"E,omitempty"` + F int64 `json:"F,omitempty"` + G uint `json:"G,omitempty"` + H uint8 `json:"H,omitempty"` + I uint16 `json:"I,omitempty"` + J uint32 `json:"J,omitempty"` + K uint64 `json:"K,omitempty"` + L bool `json:"L,omitempty"` + M map[string]int `json:"M,omitempty"` + N map[string]TestType2 `json:"N,omitempty"` + O *TestType2 `json:"O,omitempty"` + P []TestType2 `json:"Q,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TestType2 struct { + A string `json:"A,omitempty"` + B int `json:"B,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalTestType2 struct { + A string `json:"A,omitempty"` + B int `json:"B,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalTestType1 struct { + MyWeirdCustomEmbeddedVersionKindField `json:""` + A string `json:"A,omitempty"` + B int `json:"B,omitempty"` + C int8 `json:"C,omitempty"` + D int16 `json:"D,omitempty"` + E int32 `json:"E,omitempty"` + F int64 `json:"F,omitempty"` + G uint `json:"G,omitempty"` + H uint8 `json:"H,omitempty"` + I uint16 `json:"I,omitempty"` + J uint32 `json:"J,omitempty"` + K uint64 `json:"K,omitempty"` + L bool `json:"L,omitempty"` + M map[string]int `json:"M,omitempty"` + N map[string]ExternalTestType2 `json:"N,omitempty"` + O *ExternalTestType2 `json:"O,omitempty"` + P []ExternalTestType2 `json:"Q,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalInternalSame struct { + MyWeirdCustomEmbeddedVersionKindField `json:""` + A TestType2 `json:"A,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type UnversionedType struct { + MyWeirdCustomEmbeddedVersionKindField `json:""` + A string `json:"A,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type UnknownType struct { + MyWeirdCustomEmbeddedVersionKindField `json:""` + A string `json:"A,omitempty"` +} + +func (obj *MyWeirdCustomEmbeddedVersionKindField) GetObjectKind() schema.ObjectKind { return obj } +func (obj *MyWeirdCustomEmbeddedVersionKindField) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.ObjectKind = gvk.ToAPIVersionAndKind() +} +func (obj *MyWeirdCustomEmbeddedVersionKindField) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.ObjectKind) +} + +func (obj *TestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (obj *ExternalTestType2) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +// +k8s:deepcopy-gen=false +type Unstructured struct { + // Object is a JSON compatible map with string, float, int, bool, []interface{}, or + // map[string]interface{} + // children. + Object map[string]interface{} +} + +var _ runtime.Unstructured = &Unstructured{} + +func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } + +func (obj *Unstructured) IsList() bool { + if obj.Object != nil { + _, ok := obj.Object["items"] + return ok + } + return false +} + +func (obj *Unstructured) EachListItem(fn func(runtime.Object) error) error { + if obj.Object == nil { + return fmt.Errorf("content is not a list") + } + field, ok := obj.Object["items"] + if !ok { + return fmt.Errorf("content is not a list") + } + items, ok := field.([]interface{}) + if !ok { + return nil + } + for _, item := range items { + child, ok := item.(map[string]interface{}) + if !ok { + return fmt.Errorf("items member is not an object") + } + if err := fn(&Unstructured{Object: child}); err != nil { + return err + } + } + return nil +} + +func (obj *Unstructured) EachListItemWithAlloc(fn func(runtime.Object) error) error { + // EachListItem has allocated a new Object for the user, we can use it directly. + return obj.EachListItem(fn) +} + +func (obj *Unstructured) NewEmptyInstance() runtime.Unstructured { + out := new(Unstructured) + if obj != nil { + out.SetGroupVersionKind(obj.GroupVersionKind()) + } + return out +} + +func (obj *Unstructured) UnstructuredContent() map[string]interface{} { + if obj.Object == nil { + return make(map[string]interface{}) + } + return obj.Object +} + +func (obj *Unstructured) SetUnstructuredContent(content map[string]interface{}) { + obj.Object = content +} + +// MarshalJSON ensures that the unstructured object produces proper +// JSON when passed to Go's standard JSON library. +func (u *Unstructured) MarshalJSON() ([]byte, error) { + return json.Marshal(u.Object) +} + +// UnmarshalJSON ensures that the unstructured object properly decodes +// JSON when passed to Go's standard JSON library. +func (u *Unstructured) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &u.Object) +} + +func (in *Unstructured) DeepCopyObject() runtime.Object { + return in.DeepCopy() +} + +func (in *Unstructured) DeepCopy() *Unstructured { + if in == nil { + return nil + } + out := new(Unstructured) + *out = *in + out.Object = runtime.DeepCopyJSON(in.Object) + return out +} + +func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind { + apiVersion, ok := u.Object["apiVersion"].(string) + if !ok { + return schema.GroupVersionKind{} + } + gv, err := schema.ParseGroupVersion(apiVersion) + if err != nil { + return schema.GroupVersionKind{} + } + kind, ok := u.Object["kind"].(string) + if ok { + return gv.WithKind(kind) + } + return schema.GroupVersionKind{} +} + +func (u *Unstructured) SetGroupVersionKind(gvk schema.GroupVersionKind) { + if u.Object == nil { + u.Object = make(map[string]interface{}) + } + u.Object["apiVersion"] = gvk.GroupVersion().String() + u.Object["kind"] = gvk.Kind +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/doc.go new file mode 100644 index 0000000000..229ce237de --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:deepcopy-gen=package + +package v1 diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/types.go new file mode 100644 index 0000000000..a6c5fef30f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/types.go @@ -0,0 +1,27 @@ +/* +Copyright 2025 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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ExternalSimple struct { + runtime.TypeMeta `json:""` + TestString string `json:"testString"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..29f1f7a0a8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/zz_generated.deepcopy.go @@ -0,0 +1,51 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalSimple) DeepCopyInto(out *ExternalSimple) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSimple. +func (in *ExternalSimple) DeepCopy() *ExternalSimple { + if in == nil { + return nil + } + out := new(ExternalSimple) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalSimple) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/zz_generated.model_name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/zz_generated.model_name.go new file mode 100644 index 0000000000..c68af71298 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/v1/zz_generated.model_name.go @@ -0,0 +1,27 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package v1 + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in ExternalSimple) OpenAPIModelName() string { + return "io.k8s.api.testing.v1.ExternalSimple" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/validation.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/validation.go new file mode 100644 index 0000000000..bbdfc296cc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/validation.go @@ -0,0 +1,126 @@ +/* +Copyright 2024 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 testing + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +type VersionValidationRunner func(t *testing.T, gv string, versionValidationErrors field.ErrorList) + +// RunValidationForEachVersion runs f as a subtest of t for each version of the given unversioned object. +// Each subtest is named by GroupVersionKind. Each call to f is provided the field.ErrorList results +// of converting the unversioned object to a version and validating it. +// +// Only autogenerated validation is run. To test both handwritten and autogenerated validation: +// +// RunValidationForEachVersion(t, testCase.pod, func(t *testing.T, versionValidationErrors field.ErrorList) { +// errs := ValidatePod(testCase.obj) // hand written validation +// errs = append(errs, versionValidationErrors...) // generated declarative validation +// // Validate that the errors are what was expected for this test case. +// }) +func RunValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options map[string]bool, unversioned runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { + runValidation(t, scheme, options, unversioned, fn, ignoreConversionErrors, subresources...) +} + +// RunUpdateValidationForEachVersion is like RunValidationForEachVersion but for update validation. +func RunUpdateValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options map[string]bool, unversioned, unversionedOld runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { + runUpdateValidation(t, scheme, options, unversioned, unversionedOld, fn, ignoreConversionErrors, subresources...) +} + +func runValidation(t *testing.T, scheme *runtime.Scheme, options map[string]bool, unversioned runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { + unversionedGVKs, _, err := scheme.ObjectKinds(unversioned) + if err != nil { + t.Fatal(err) + } + for _, unversionedGVK := range unversionedGVKs { + // skip if passed in unversioned object is not internal. + if unversionedGVK.Version != runtime.APIVersionInternal { + continue + } + gvs := scheme.VersionsForGroupKind(unversionedGVK.GroupKind()) + for _, gv := range gvs { + gvk := gv.WithKind(unversionedGVK.Kind) + t.Run(gvk.String(), func(t *testing.T) { + if gvk.Version != runtime.APIVersionInternal { // skip internal + versioned, err := scheme.New(gvk) + if err != nil { + t.Fatal(err) + } + err = scheme.Convert(unversioned, versioned, nil) + if ignoreConversionErrors && err != nil { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { + t.Fatal(err) + } + fn(t, gv.String(), scheme.Validate(context.Background(), options, versioned, subresources...)) + } + }) + } + } +} + +func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options map[string]bool, unversionedNew, unversionedOld runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { + unversionedGVKs, _, err := scheme.ObjectKinds(unversionedNew) + if err != nil { + t.Fatal(err) + } + for _, unversionedGVK := range unversionedGVKs { + // skip if passed in unversioned object is not internal. + if unversionedGVK.Version != runtime.APIVersionInternal { + continue + } + gvs := scheme.VersionsForGroupKind(unversionedGVK.GroupKind()) + for _, gv := range gvs { + gvk := gv.WithKind(unversionedGVK.Kind) + t.Run(gvk.String(), func(t *testing.T) { + if gvk.Version != runtime.APIVersionInternal { // skip internal + versionedNew, err := scheme.New(gvk) + if err != nil { + t.Fatal(err) + } + err = scheme.Convert(unversionedNew, versionedNew, nil) + if ignoreConversionErrors && err != nil { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { + t.Fatal(err) + } + var versionedOld runtime.Object + if unversionedOld != nil { + versionedOld, err = scheme.New(gvk) + if err != nil { + t.Fatal(err) + } + + err = scheme.Convert(unversionedOld, versionedOld, nil) + if ignoreConversionErrors && err != nil { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { + t.Fatal(err) + } + } + + fn(t, gv.String(), scheme.ValidateUpdate(context.Background(), options, versionedNew, versionedOld, subresources...)) + } + }) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/zz_generated.deepcopy.go new file mode 100644 index 0000000000..c0d4419575 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/testing/zz_generated.deepcopy.go @@ -0,0 +1,630 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package testing + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmbeddedTest) DeepCopyInto(out *EmbeddedTest) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Object != nil { + out.Object = in.Object.DeepCopyObject() + } + if in.EmptyObject != nil { + out.EmptyObject = in.EmptyObject.DeepCopyObject() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmbeddedTest. +func (in *EmbeddedTest) DeepCopy() *EmbeddedTest { + if in == nil { + return nil + } + out := new(EmbeddedTest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EmbeddedTest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmbeddedTestExternal) DeepCopyInto(out *EmbeddedTestExternal) { + *out = *in + out.TypeMeta = in.TypeMeta + in.Object.DeepCopyInto(&out.Object) + in.EmptyObject.DeepCopyInto(&out.EmptyObject) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmbeddedTestExternal. +func (in *EmbeddedTestExternal) DeepCopy() *EmbeddedTestExternal { + if in == nil { + return nil + } + out := new(EmbeddedTestExternal) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EmbeddedTestExternal) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionA) DeepCopyInto(out *ExtensionA) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionA. +func (in *ExtensionA) DeepCopy() *ExtensionA { + if in == nil { + return nil + } + out := new(ExtensionA) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExtensionA) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionB) DeepCopyInto(out *ExtensionB) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionB. +func (in *ExtensionB) DeepCopy() *ExtensionB { + if in == nil { + return nil + } + out := new(ExtensionB) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExtensionB) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalComplex) DeepCopyInto(out *ExternalComplex) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalComplex. +func (in *ExternalComplex) DeepCopy() *ExternalComplex { + if in == nil { + return nil + } + out := new(ExternalComplex) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalComplex) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalExtensionType) DeepCopyInto(out *ExternalExtensionType) { + *out = *in + out.TypeMeta = in.TypeMeta + in.Extension.DeepCopyInto(&out.Extension) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalExtensionType. +func (in *ExternalExtensionType) DeepCopy() *ExternalExtensionType { + if in == nil { + return nil + } + out := new(ExternalExtensionType) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalExtensionType) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalInternalSame) DeepCopyInto(out *ExternalInternalSame) { + *out = *in + out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField + out.A = in.A + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalInternalSame. +func (in *ExternalInternalSame) DeepCopy() *ExternalInternalSame { + if in == nil { + return nil + } + out := new(ExternalInternalSame) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalInternalSame) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalOptionalExtensionType) DeepCopyInto(out *ExternalOptionalExtensionType) { + *out = *in + out.TypeMeta = in.TypeMeta + in.Extension.DeepCopyInto(&out.Extension) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalOptionalExtensionType. +func (in *ExternalOptionalExtensionType) DeepCopy() *ExternalOptionalExtensionType { + if in == nil { + return nil + } + out := new(ExternalOptionalExtensionType) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalOptionalExtensionType) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalSimple) DeepCopyInto(out *ExternalSimple) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSimple. +func (in *ExternalSimple) DeepCopy() *ExternalSimple { + if in == nil { + return nil + } + out := new(ExternalSimple) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalSimple) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalTestType1) DeepCopyInto(out *ExternalTestType1) { + *out = *in + out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField + if in.M != nil { + in, out := &in.M, &out.M + *out = make(map[string]int, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.N != nil { + in, out := &in.N, &out.N + *out = make(map[string]ExternalTestType2, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.O != nil { + in, out := &in.O, &out.O + *out = new(ExternalTestType2) + **out = **in + } + if in.P != nil { + in, out := &in.P, &out.P + *out = make([]ExternalTestType2, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalTestType1. +func (in *ExternalTestType1) DeepCopy() *ExternalTestType1 { + if in == nil { + return nil + } + out := new(ExternalTestType1) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalTestType1) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalTestType2) DeepCopyInto(out *ExternalTestType2) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalTestType2. +func (in *ExternalTestType2) DeepCopy() *ExternalTestType2 { + if in == nil { + return nil + } + out := new(ExternalTestType2) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ExternalTestType2) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InternalComplex) DeepCopyInto(out *InternalComplex) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalComplex. +func (in *InternalComplex) DeepCopy() *InternalComplex { + if in == nil { + return nil + } + out := new(InternalComplex) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InternalComplex) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InternalExtensionType) DeepCopyInto(out *InternalExtensionType) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Extension != nil { + out.Extension = in.Extension.DeepCopyObject() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalExtensionType. +func (in *InternalExtensionType) DeepCopy() *InternalExtensionType { + if in == nil { + return nil + } + out := new(InternalExtensionType) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InternalExtensionType) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InternalOptionalExtensionType) DeepCopyInto(out *InternalOptionalExtensionType) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Extension != nil { + out.Extension = in.Extension.DeepCopyObject() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalOptionalExtensionType. +func (in *InternalOptionalExtensionType) DeepCopy() *InternalOptionalExtensionType { + if in == nil { + return nil + } + out := new(InternalOptionalExtensionType) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InternalOptionalExtensionType) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InternalSimple) DeepCopyInto(out *InternalSimple) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalSimple. +func (in *InternalSimple) DeepCopy() *InternalSimple { + if in == nil { + return nil + } + out := new(InternalSimple) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InternalSimple) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectTest) DeepCopyInto(out *ObjectTest) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.Object, len(*in)) + for i := range *in { + if (*in)[i] != nil { + (*out)[i] = (*in)[i].DeepCopyObject() + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectTest. +func (in *ObjectTest) DeepCopy() *ObjectTest { + if in == nil { + return nil + } + out := new(ObjectTest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectTest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectTestExternal) DeepCopyInto(out *ObjectTestExternal) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.RawExtension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectTestExternal. +func (in *ObjectTestExternal) DeepCopy() *ObjectTestExternal { + if in == nil { + return nil + } + out := new(ObjectTestExternal) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectTestExternal) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestType1) DeepCopyInto(out *TestType1) { + *out = *in + out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField + if in.M != nil { + in, out := &in.M, &out.M + *out = make(map[string]int, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.N != nil { + in, out := &in.N, &out.N + *out = make(map[string]TestType2, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.O != nil { + in, out := &in.O, &out.O + *out = new(TestType2) + **out = **in + } + if in.P != nil { + in, out := &in.P, &out.P + *out = make([]TestType2, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestType1. +func (in *TestType1) DeepCopy() *TestType1 { + if in == nil { + return nil + } + out := new(TestType1) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TestType1) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestType2) DeepCopyInto(out *TestType2) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestType2. +func (in *TestType2) DeepCopy() *TestType2 { + if in == nil { + return nil + } + out := new(TestType2) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TestType2) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UnknownType) DeepCopyInto(out *UnknownType) { + *out = *in + out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnknownType. +func (in *UnknownType) DeepCopy() *UnknownType { + if in == nil { + return nil + } + out := new(UnknownType) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UnknownType) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UnversionedType) DeepCopyInto(out *UnversionedType) { + *out = *in + out.MyWeirdCustomEmbeddedVersionKindField = in.MyWeirdCustomEmbeddedVersionKindField + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnversionedType. +func (in *UnversionedType) DeepCopy() *UnversionedType { + if in == nil { + return nil + } + out := new(UnversionedType) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UnversionedType) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types.go new file mode 100644 index 0000000000..6cded88997 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types.go @@ -0,0 +1,135 @@ +/* +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 runtime + +// Note that the types provided in this file are not versioned and are intended to be +// safe to use from within all versions of every API object. + +// TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, +// like this: +// +// type MyAwesomeAPIObject struct { +// runtime.TypeMeta `json:""` +// ... // other fields +// } +// +// func (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind +// +// TypeMeta is provided here for convenience. You may use it directly from this package or define +// your own with the same fields. +// +// +k8s:deepcopy-gen=false +// +protobuf=true +// +k8s:openapi-gen=true +type TypeMeta struct { + // +optional + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty" protobuf:"bytes,1,opt,name=apiVersion"` + // +optional + Kind string `json:"kind,omitempty" yaml:"kind,omitempty" protobuf:"bytes,2,opt,name=kind"` +} + +const ( + ContentTypeJSON string = "application/json" + ContentTypeYAML string = "application/yaml" + ContentTypeProtobuf string = "application/vnd.kubernetes.protobuf" + ContentTypeCBOR string = "application/cbor" // RFC 8949 + ContentTypeCBORSequence string = "application/cbor-seq" // RFC 8742 +) + +// RawExtension is used to hold extensions in external versions. +// +// To use this, make a field which has RawExtension as its type in your external, versioned +// struct, and Object in your internal struct. You also need to register your +// various plugin types. +// +// // Internal package: +// +// type MyAPIObject struct { +// runtime.TypeMeta `json:""` +// MyPlugin runtime.Object `json:"myPlugin"` +// } +// +// type PluginA struct { +// AOption string `json:"aOption"` +// } +// +// // External package: +// +// type MyAPIObject struct { +// runtime.TypeMeta `json:""` +// MyPlugin runtime.RawExtension `json:"myPlugin"` +// } +// +// type PluginA struct { +// AOption string `json:"aOption"` +// } +// +// // On the wire, the JSON will look something like this: +// +// { +// "kind":"MyAPIObject", +// "apiVersion":"v1", +// "myPlugin": { +// "kind":"PluginA", +// "aOption":"foo", +// }, +// } +// +// So what happens? Decode first uses json or yaml to unmarshal the serialized data into +// your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. +// The next step is to copy (using pkg/conversion) into the internal struct. The runtime +// package's DefaultScheme has conversion functions installed which will unpack the +// JSON stored in RawExtension, turning it into the correct object type, and storing it +// in the Object. (TODO: In the case where the object is of an unknown type, a +// runtime.Unknown object will be created and stored.) +// +// +k8s:deepcopy-gen=true +// +protobuf=true +// +k8s:openapi-gen=true +type RawExtension struct { + // Raw is the underlying serialization of this object. + // + // TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data. + Raw []byte `json:"-" protobuf:"bytes,1,opt,name=raw"` + // Object can hold a representation of this extension - useful for working with versioned + // structs. + Object Object `json:"-"` +} + +// Unknown allows api objects with unknown types to be passed-through. This can be used +// to deal with the API objects from a plug-in. Unknown objects still have functioning +// TypeMeta features-- kind, version, etc. +// TODO: Make this object have easy access to field based accessors and settors for +// metadata and field mutatation. +// +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +protobuf=true +// +k8s:openapi-gen=true +type Unknown struct { + TypeMeta `json:"" protobuf:"bytes,1,opt,name=typeMeta"` + // Raw will hold the complete serialized object which couldn't be matched + // with a registered type. Most likely, nothing should be done with this + // except for passing it through the system. + Raw []byte `json:"-" protobuf:"bytes,2,opt,name=raw"` + // ContentEncoding is encoding used to encode 'Raw' data. + // Unspecified means no encoding. + ContentEncoding string `protobuf:"bytes,3,opt,name=contentEncoding"` + // ContentType is serialization method used to serialize 'Raw'. + // Unspecified means ContentTypeJSON. + ContentType string `protobuf:"bytes,4,opt,name=contentType"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types_proto.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types_proto.go new file mode 100644 index 0000000000..70c4ea8c56 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types_proto.go @@ -0,0 +1,218 @@ +/* +Copyright 2015 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 runtime + +import ( + "fmt" + "io" +) + +// ProtobufReverseMarshaller can precompute size, and marshals to the start of the provided data buffer. +type ProtobufMarshaller interface { + // Size returns the number of bytes a call to MarshalTo would consume. + Size() int + // MarshalTo marshals to the start of the data buffer, which must be at least as big as Size(), + // and returns the number of bytes written, which must be identical to the return value of Size(). + MarshalTo(data []byte) (int, error) +} + +// ProtobufReverseMarshaller can precompute size, and marshals to the end of the provided data buffer. +type ProtobufReverseMarshaller interface { + // Size returns the number of bytes a call to MarshalToSizedBuffer would consume. + Size() int + // MarshalToSizedBuffer marshals to the end of the data buffer, which must be at least as big as Size(), + // and returns the number of bytes written, which must be identical to the return value of Size(). + MarshalToSizedBuffer(data []byte) (int, error) +} + +const ( + typeMetaTag = 0xa + rawTag = 0x12 + contentEncodingTag = 0x1a + contentTypeTag = 0x22 + + // max length of a varint for a uint64 + maxUint64VarIntLength = 10 +) + +// MarshalToWriter allows a caller to provide a streaming writer for raw bytes, +// instead of populating them inside the Unknown struct. +// rawSize is the number of bytes rawWriter will write in a success case. +// writeRaw is called when it is time to write the raw bytes. It must return `rawSize, nil` or an error. +func (m *Unknown) MarshalToWriter(w io.Writer, rawSize int, writeRaw func(io.Writer) (int, error)) (int, error) { + size := 0 + + // reuse the buffer for varint marshaling + varintBuffer := make([]byte, maxUint64VarIntLength) + writeVarint := func(i int) (int, error) { + offset := encodeVarintGenerated(varintBuffer, len(varintBuffer), uint64(i)) + return w.Write(varintBuffer[offset:]) + } + + // TypeMeta + { + n, err := w.Write([]byte{typeMetaTag}) + size += n + if err != nil { + return size, err + } + + typeMetaBytes, err := m.TypeMeta.Marshal() + if err != nil { + return size, err + } + + n, err = writeVarint(len(typeMetaBytes)) + size += n + if err != nil { + return size, err + } + + n, err = w.Write(typeMetaBytes) + size += n + if err != nil { + return size, err + } + } + + // Raw, delegating write to writeRaw() + { + n, err := w.Write([]byte{rawTag}) + size += n + if err != nil { + return size, err + } + + n, err = writeVarint(rawSize) + size += n + if err != nil { + return size, err + } + + n, err = writeRaw(w) + size += n + if err != nil { + return size, err + } + if n != int(rawSize) { + return size, fmt.Errorf("the size value was %d, but encoding wrote %d bytes to data", rawSize, n) + } + } + + // ContentEncoding + { + n, err := w.Write([]byte{contentEncodingTag}) + size += n + if err != nil { + return size, err + } + + n, err = writeVarint(len(m.ContentEncoding)) + size += n + if err != nil { + return size, err + } + + n, err = w.Write([]byte(m.ContentEncoding)) + size += n + if err != nil { + return size, err + } + } + + // ContentEncoding + { + n, err := w.Write([]byte{contentTypeTag}) + size += n + if err != nil { + return size, err + } + + n, err = writeVarint(len(m.ContentType)) + size += n + if err != nil { + return size, err + } + + n, err = w.Write([]byte(m.ContentType)) + size += n + if err != nil { + return size, err + } + } + return size, nil +} + +// NestedMarshalTo allows a caller to avoid extra allocations during serialization of an Unknown +// that will contain an object that implements ProtobufMarshaller or ProtobufReverseMarshaller. +func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64) (int, error) { + // Calculate the full size of the message. + msgSize := m.Size() + if b != nil { + msgSize += int(size) + sovGenerated(size) + 1 + } + + // Reverse marshal the fields of m. + i := msgSize + i -= len(m.ContentType) + copy(data[i:], m.ContentType) + i = encodeVarintGenerated(data, i, uint64(len(m.ContentType))) + i-- + data[i] = contentTypeTag + i -= len(m.ContentEncoding) + copy(data[i:], m.ContentEncoding) + i = encodeVarintGenerated(data, i, uint64(len(m.ContentEncoding))) + i-- + data[i] = contentEncodingTag + if b != nil { + if r, ok := b.(ProtobufReverseMarshaller); ok { + n1, err := r.MarshalToSizedBuffer(data[:i]) + if err != nil { + return 0, err + } + i -= int(size) + if uint64(n1) != size { + // programmer error: the Size() method for protobuf does not match the results of LashramOt, which means the proto + // struct returned would be wrong. + return 0, fmt.Errorf("the Size() value of %T was %d, but NestedMarshalTo wrote %d bytes to data", b, size, n1) + } + } else { + i -= int(size) + n1, err := b.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + if uint64(n1) != size { + // programmer error: the Size() method for protobuf does not match the results of MarshalTo, which means the proto + // struct returned would be wrong. + return 0, fmt.Errorf("the Size() value of %T was %d, but NestedMarshalTo wrote %d bytes to data", b, size, n1) + } + } + i = encodeVarintGenerated(data, i, size) + i-- + data[i] = rawTag + } + n2, err := m.TypeMeta.MarshalToSizedBuffer(data[:i]) + if err != nil { + return 0, err + } + i -= n2 + i = encodeVarintGenerated(data, i, uint64(n2)) + i-- + data[i] = typeMetaTag + return msgSize - i, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types_proto_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types_proto_test.go new file mode 100644 index 0000000000..50535f0417 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/types_proto_test.go @@ -0,0 +1,111 @@ +/* +Copyright 2025 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 runtime + +import ( + "bytes" + "io" + "math" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestVarint(t *testing.T) { + varintBuffer := make([]byte, maxUint64VarIntLength) + offset := encodeVarintGenerated(varintBuffer, len(varintBuffer), math.MaxUint64) + used := len(varintBuffer) - offset + if used != maxUint64VarIntLength { + t.Fatalf("expected encodeVarintGenerated to use %d bytes to encode MaxUint64, got %d", maxUint64VarIntLength, used) + } +} + +func TestNestedMarshalToWriter(t *testing.T) { + testcases := []struct { + name string + raw []byte + }{ + { + name: "zero-length", + raw: []byte{}, + }, + { + name: "simple", + raw: []byte{0x00, 0x01, 0x02, 0x03}, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + u := &Unknown{ + ContentType: "ct", + ContentEncoding: "ce", + TypeMeta: TypeMeta{ + APIVersion: "v1", + Kind: "k", + }, + } + + // Marshal normally with Raw inlined + u.Raw = tc.raw + marshalData, err := u.Marshal() + if err != nil { + t.Fatal(err) + } + u.Raw = nil + + // Marshal with NestedMarshalTo + nestedMarshalData := make([]byte, len(marshalData)) + n, err := u.NestedMarshalTo(nestedMarshalData, copyMarshaler(tc.raw), uint64(len(tc.raw))) + if err != nil { + t.Fatal(err) + } + if n != len(marshalData) { + t.Errorf("NestedMarshalTo returned %d, expected %d", n, len(marshalData)) + } + if e, a := marshalData, nestedMarshalData; !bytes.Equal(e, a) { + t.Errorf("NestedMarshalTo and Marshal differ:\n%s", cmp.Diff(e, a)) + } + + // Streaming marshal with MarshalToWriter + buf := bytes.NewBuffer(nil) + n, err = u.MarshalToWriter(buf, len(tc.raw), func(w io.Writer) (int, error) { + return w.Write(tc.raw) + }) + if err != nil { + t.Fatal(err) + } + if n != len(marshalData) { + t.Errorf("MarshalToWriter returned %d, expected %d", n, len(marshalData)) + } + if e, a := marshalData, buf.Bytes(); !bytes.Equal(e, a) { + t.Errorf("MarshalToWriter and Marshal differ:\n%s", cmp.Diff(e, a)) + } + }) + } +} + +type copyMarshaler []byte + +func (c copyMarshaler) Size() int { + return len(c) +} + +func (c copyMarshaler) MarshalTo(dest []byte) (int, error) { + n := copy(dest, []byte(c)) + return n, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/zz_generated.deepcopy.go new file mode 100644 index 0000000000..069ea4f92d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/zz_generated.deepcopy.go @@ -0,0 +1,76 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package runtime + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RawExtension) DeepCopyInto(out *RawExtension) { + *out = *in + if in.Raw != nil { + in, out := &in.Raw, &out.Raw + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.Object != nil { + out.Object = in.Object.DeepCopyObject() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RawExtension. +func (in *RawExtension) DeepCopy() *RawExtension { + if in == nil { + return nil + } + out := new(RawExtension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Unknown) DeepCopyInto(out *Unknown) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Raw != nil { + in, out := &in.Raw, &out.Raw + *out = make([]byte, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Unknown. +func (in *Unknown) DeepCopy() *Unknown { + if in == nil { + return nil + } + out := new(Unknown) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new Object. +func (in *Unknown) DeepCopyObject() Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/zz_generated.model_name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/zz_generated.model_name.go new file mode 100644 index 0000000000..cf3ec4dceb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/runtime/zz_generated.model_name.go @@ -0,0 +1,92 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package runtime + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Allocator) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.Allocator" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in NegotiateError) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.NegotiateError" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in NoopDecoder) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.NoopDecoder" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in NoopEncoder) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.NoopEncoder" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Pair) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.Pair" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in RawExtension) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.RawExtension" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Scheme) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.Scheme" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SerializerInfo) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.SerializerInfo" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in SimpleAllocator) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.SimpleAllocator" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in StreamSerializerInfo) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.StreamSerializerInfo" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in TypeMeta) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.TypeMeta" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Unknown) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.Unknown" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in WithVersionEncoder) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.WithVersionEncoder" +} + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in WithoutVersionDecoder) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.runtime.WithoutVersionDecoder" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/selection/operator.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/selection/operator.go new file mode 100644 index 0000000000..298f798c43 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/selection/operator.go @@ -0,0 +1,33 @@ +/* +Copyright 2016 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 selection + +// Operator represents a key/field's relationship to value(s). +// See labels.Requirement and fields.Requirement for more details. +type Operator string + +const ( + DoesNotExist Operator = "!" + Equals Operator = "=" + DoubleEquals Operator = "==" + In Operator = "in" + NotEquals Operator = "!=" + NotIn Operator = "notin" + Exists Operator = "exists" + GreaterThan Operator = "gt" + LessThan Operator = "lt" +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/accessor.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/accessor.go new file mode 100644 index 0000000000..fab98ffca4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/accessor.go @@ -0,0 +1,50 @@ +/* +Copyright 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 sharding + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" +) + +// ResolveFieldValue extracts a metadata field value from a runtime.Object +// based on the given field path. +// +// Field paths use CEL-style object-rooted syntax ("object.metadata."), +// which differs from the fieldSelector format ("metadata."). The +// "object." prefix anchors the path to the resource being filtered. +// +// Supported field paths: +// - "object.metadata.uid" +// - "object.metadata.namespace" +func ResolveFieldValue(obj runtime.Object, fieldPath string) (string, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return "", fmt.Errorf("failed to access object metadata: %w", err) + } + + switch fieldPath { + case "object.metadata.uid": + return string(accessor.GetUID()), nil + case "object.metadata.namespace": + return accessor.GetNamespace(), nil + default: + return "", fmt.Errorf("unsupported field path: %q", fieldPath) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/accessor_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/accessor_test.go new file mode 100644 index 0000000000..bee6d39025 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/accessor_test.go @@ -0,0 +1,64 @@ +/* +Copyright 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 sharding + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestResolveFieldValue(t *testing.T) { + obj := &testObject{ + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("test-uid-123"), + Name: "test-name", + Namespace: "test-namespace", + }, + } + + tests := []struct { + fieldPath string + want string + wantErr bool + }{ + {"object.metadata.uid", "test-uid-123", false}, + {"object.metadata.namespace", "test-namespace", false}, + {"object.metadata.name", "", true}, + {"object.metadata.labels", "", true}, + {"invalid.path", "", true}, + } + + for _, tt := range tests { + t.Run(tt.fieldPath, func(t *testing.T) { + got, err := ResolveFieldValue(obj, tt.fieldPath) + if tt.wantErr { + if err == nil { + t.Errorf("expected error for fieldPath %q", tt.fieldPath) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("ResolveFieldValue(%q) = %q, want %q", tt.fieldPath, got, tt.want) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/hash.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/hash.go new file mode 100644 index 0000000000..ae4dcfe2f6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/hash.go @@ -0,0 +1,30 @@ +/* +Copyright 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 sharding + +import ( + "fmt" + "hash/fnv" +) + +// HashField computes a hash of value and returns it +// as a 16-character lowercase hex string (no "0x" prefix). +func HashField(value string) string { + h := fnv.New64a() + h.Write([]byte(value)) + return fmt.Sprintf("%016x", h.Sum64()) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/hash_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/hash_test.go new file mode 100644 index 0000000000..750352eef1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/hash_test.go @@ -0,0 +1,58 @@ +/* +Copyright 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 sharding + +import ( + "testing" +) + +func TestHashField(t *testing.T) { + tests := []struct { + input string + }{ + {""}, + {"abc"}, + {"test-uid-12345"}, + {"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}, + } + + for _, tt := range tests { + result := HashField(tt.input) + if len(result) != 16 { + t.Errorf("HashField(%q) returned %q (len %d), expected 16 hex chars", tt.input, result, len(result)) + } + // Verify all chars are hex + for _, c := range result { + if (c < '0' || c > '9') && (c < 'a' || c > 'f') { + t.Errorf("HashField(%q) returned %q which contains non-hex char %q", tt.input, result, string(c)) + } + } + } + + // Determinism + h1 := HashField("test") + h2 := HashField("test") + if h1 != h2 { + t.Errorf("HashField is not deterministic: %q != %q", h1, h2) + } + + // Different inputs produce different outputs + h3 := HashField("different") + if h1 == h3 { + t.Errorf("HashField produced same hash for different inputs: %q", h1) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/selector.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/selector.go new file mode 100644 index 0000000000..841388a0d7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/selector.go @@ -0,0 +1,137 @@ +/* +Copyright 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 sharding + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/runtime" +) + +// Selector represents a shard selector that can match objects based on +// hash ranges of their metadata fields. It follows the labels.Selector +// pattern from the Kubernetes API. +type Selector interface { + // Matches returns true if the given object matches the shard selector. + Matches(obj runtime.Object) (bool, error) + + // Empty returns true if the selector matches everything (no filtering). + Empty() bool + + // String returns the wire-format string representation that can be + // round-tripped through Parse. + String() string + + // Requirements returns the list of shard range requirements. + Requirements() []ShardRangeRequirement + + // DeepCopySelector returns a deep copy of the selector. + DeepCopySelector() Selector +} + +// Everything returns a selector that matches all objects. +func Everything() Selector { + return &everythingSelector{} +} + +type everythingSelector struct{} + +func (s *everythingSelector) Matches(_ runtime.Object) (bool, error) { return true, nil } +func (s *everythingSelector) Empty() bool { return true } +func (s *everythingSelector) String() string { return "" } +func (s *everythingSelector) Requirements() []ShardRangeRequirement { return nil } +func (s *everythingSelector) DeepCopySelector() Selector { return &everythingSelector{} } + +// shardSelector implements Selector with one or more shard range requirements. +type shardSelector struct { + requirements []ShardRangeRequirement +} + +func (s *shardSelector) Matches(obj runtime.Object) (bool, error) { + if len(s.requirements) == 0 { + return true, nil + } + // All requirements must share the same key so we resolve the field value + // and compute the hash once. The parser enforces this, but we verify here + // to guard against selectors constructed through other means. + key := s.requirements[0].Key + for _, req := range s.requirements[1:] { + if req.Key != key { + return false, fmt.Errorf("inconsistent shard keys: %q vs %q", key, req.Key) + } + } + + value, err := ResolveFieldValue(obj, key) + if err != nil { + return false, err + } + hash := "0x" + HashField(value) + + for _, req := range s.requirements { + if !HexLess(hash, req.Start) && HexLess(hash, req.End) { + return true, nil + } + } + return false, nil +} + +// HexLess compares two 0x-prefixed lowercase hex strings numerically. +// Both values must be normalized to 16 hex digits (e.g. "0x0000000000000000"), +// except for the special upper bound "0x10000000000000000" (2^64) which has 17. +func HexLess(a, b string) bool { + if len(a) != len(b) { + return len(a) < len(b) + } + return a < b +} + +func (s *shardSelector) Empty() bool { + return len(s.requirements) == 0 +} + +func (s *shardSelector) String() string { + parts := make([]string, 0, len(s.requirements)) + for _, req := range s.requirements { + parts = append(parts, fmt.Sprintf("shardRange(%s, '%s', '%s')", req.Key, req.Start, req.End)) + } + return strings.Join(parts, " || ") +} + +func (s *shardSelector) Requirements() []ShardRangeRequirement { + result := make([]ShardRangeRequirement, len(s.requirements)) + copy(result, s.requirements) + return result +} + +func (s *shardSelector) DeepCopySelector() Selector { + reqs := make([]ShardRangeRequirement, len(s.requirements)) + copy(reqs, s.requirements) + return &shardSelector{requirements: reqs} +} + +// NewSelector creates a Selector from the given requirements. +// All requirements must use the same Key; this is validated at match time. +// If no requirements are provided, returns Everything(). +func NewSelector(reqs ...ShardRangeRequirement) Selector { + if len(reqs) == 0 { + return Everything() + } + return &shardSelector{ + requirements: reqs, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/selector_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/selector_test.go new file mode 100644 index 0000000000..eb5ea63fe9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/selector_test.go @@ -0,0 +1,141 @@ +/* +Copyright 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 sharding + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +// testObject is a minimal runtime.Object for testing. +type testObject struct { + metav1.TypeMeta `json:""` + metav1.ObjectMeta `json:"metadata"` +} + +func (t *testObject) DeepCopyObject() runtime.Object { + return &testObject{ + TypeMeta: t.TypeMeta, + ObjectMeta: *t.ObjectMeta.DeepCopy(), + } +} + +func TestSelectorMatches(t *testing.T) { + obj := &testObject{ + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID("test-uid-123"), + Name: "test-name", + Namespace: "test-namespace", + }, + } + + hash := "0x" + HashField("test-uid-123") + + tests := []struct { + name string + selector Selector + wantMatch bool + }{ + { + name: "everything matches", + selector: Everything(), + wantMatch: true, + }, + { + name: "empty selector matches", + selector: NewSelector(), + wantMatch: true, + }, + { + name: "full range matches", + selector: NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0x0000000000000000", + End: "0x10000000000000000", + }), + wantMatch: true, + }, + { + name: "hash in specific range", + selector: NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: hash, + End: hash + "f", // hash + "f" is always > hash + }), + wantMatch: true, + }, + { + name: "hash below start", + selector: NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0xffffffffffffffff", + End: "0x10000000000000000", + }), + wantMatch: hash >= "0xffffffffffffffff", + }, + { + name: "hash at or above end", + selector: NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0x0000000000000000", + End: "0x0000000000000001", + }), + wantMatch: hash < "0x0000000000000001", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matched, err := tt.selector.Matches(obj) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if matched != tt.wantMatch { + t.Errorf("Matches() = %v, want %v (hash=%s)", matched, tt.wantMatch, hash) + } + }) + } +} + +func TestSelectorEmpty(t *testing.T) { + if !Everything().Empty() { + t.Error("Everything() should be empty") + } + if !NewSelector().Empty() { + t.Error("NewSelector() with no args should be empty") + } + + sel := NewSelector(ShardRangeRequirement{Key: "object.metadata.uid", Start: "0x0000000000000000", End: "0x8000000000000000"}) + if sel.Empty() { + t.Error("selector with requirement should not be empty") + } +} + +func TestSelectorString(t *testing.T) { + sel := NewSelector(ShardRangeRequirement{ + Key: "object.metadata.uid", + Start: "0x00", + End: "0x80", + }) + expected := "shardRange(object.metadata.uid, '0x00', '0x80')" + if sel.String() != expected { + t.Errorf("String() = %q, want %q", sel.String(), expected) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/types.go new file mode 100644 index 0000000000..4684fc497f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/sharding/types.go @@ -0,0 +1,32 @@ +/* +Copyright 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 sharding + +// ShardRangeRequirement represents a single shard range requirement. +// It specifies a field path to hash and a hex range [Start, End) for filtering. +// The hash space is FNV-1a 64-bit: [0x0000000000000000, 0x10000000000000000). +// Both Start and End must be specified (no empty/unbounded values). +type ShardRangeRequirement struct { + // Key is the field path, e.g. "object.metadata.uid" + Key string + // Start is the inclusive lower bound as a 0x-prefixed lowercase hex string. + // Minimum value is "0x0000000000000000". + Start string + // End is the exclusive upper bound as a 0x-prefixed lowercase hex string. + // Maximum value is "0x10000000000000000" (2^64). + End string +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/api_meta_help_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/api_meta_help_test.go new file mode 100644 index 0000000000..e6be133111 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/api_meta_help_test.go @@ -0,0 +1,335 @@ +/* +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 test + +import ( + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/randfill" + + "k8s.io/apimachinery/pkg/api/meta" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/apis/testapigroup" + v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +func TestIsList(t *testing.T) { + tests := []struct { + obj runtime.Object + isList bool + }{ + {&testapigroup.CarpList{}, true}, + {&testapigroup.Carp{}, false}, + } + for _, item := range tests { + if e, a := item.isList, meta.IsListType(item.obj); e != a { + t.Errorf("%v: Expected %v, got %v", reflect.TypeOf(item.obj), e, a) + } + } +} + +func TestExtractList(t *testing.T) { + list1 := []runtime.Object{ + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "2"}}, + } + list2 := &ListV1{ + Items: []runtime.RawExtension{ + {Raw: []byte("foo")}, + {Raw: []byte("bar")}, + {Object: &v1.Carp{ObjectMeta: metav1.ObjectMeta{Name: "other"}}}, + }, + } + list3 := &fakePtrValueList{ + Items: []*testapigroup.Carp{ + {ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "2"}}, + }, + } + list4 := &testapigroup.CarpList{ + Items: []testapigroup.Carp{ + {ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "2"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "3"}}, + }, + } + + testCases := []struct { + in runtime.Object + out []interface{} + equal bool + }{ + { + in: &List{}, + out: []interface{}{}, + }, + { + in: &ListV1{}, + out: []interface{}{}, + }, + { + in: &List{Items: list1}, + out: []interface{}{list1[0], list1[1]}, + }, + { + in: list2, + out: []interface{}{&runtime.Unknown{Raw: list2.Items[0].Raw}, &runtime.Unknown{Raw: list2.Items[1].Raw}, list2.Items[2].Object}, + equal: true, + }, + { + in: list3, + out: []interface{}{list3.Items[0], list3.Items[1]}, + }, + { + in: list4, + out: []interface{}{&list4.Items[0], &list4.Items[1], &list4.Items[2]}, + }, + } + for i, test := range testCases { + list, err := meta.ExtractList(test.in) + if err != nil { + t.Fatalf("%d: extract: Unexpected error %v", i, err) + } + if e, a := len(test.out), len(list); e != a { + t.Fatalf("%d: extract: Expected %v, got %v", i, e, a) + } + for j, e := range test.out { + if e != list[j] { + if !test.equal { + t.Fatalf("%d: extract: Expected list[%d] to be %#v, but found %#v", i, j, e, list[j]) + } + if !reflect.DeepEqual(e, list[j]) { + t.Fatalf("%d: extract: Expected list[%d] to be %#v, but found %#v", i, j, e, list[j]) + } + } + } + } +} + +func TestEachListItem(t *testing.T) { + list1 := []runtime.Object{ + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "2"}}, + } + list2 := &ListV1{ + Items: []runtime.RawExtension{ + {Raw: []byte("foo")}, + {Raw: []byte("bar")}, + {Object: &v1.Carp{ObjectMeta: metav1.ObjectMeta{Name: "other"}}}, + }, + } + list3 := &fakePtrValueList{ + Items: []*testapigroup.Carp{ + {ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "2"}}, + }, + } + list4 := &testapigroup.CarpList{ + Items: []testapigroup.Carp{ + {ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "2"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "3"}}, + }, + } + + testCases := []struct { + in runtime.Object + out []interface{} + }{ + { + in: &List{}, + out: []interface{}{}, + }, + { + in: &ListV1{}, + out: []interface{}{}, + }, + { + in: &List{Items: list1}, + out: []interface{}{list1[0], list1[1]}, + }, + { + in: list2, + out: []interface{}{nil, nil, list2.Items[2].Object}, + }, + { + in: list3, + out: []interface{}{list3.Items[0], list3.Items[1]}, + }, + { + in: list4, + out: []interface{}{&list4.Items[0], &list4.Items[1], &list4.Items[2]}, + }, + } + for i, test := range testCases { + list := []runtime.Object{} + err := meta.EachListItem(test.in, func(obj runtime.Object) error { + list = append(list, obj) + return nil + }) + if err != nil { + t.Fatalf("%d: each: Unexpected error %v", i, err) + } + if e, a := len(test.out), len(list); e != a { + t.Fatalf("%d: each: Expected %v, got %v", i, e, a) + } + for j, e := range test.out { + if e != list[j] { + t.Fatalf("%d: each: Expected list[%d] to be %#v, but found %#v", i, j, e, list[j]) + } + } + } +} + +type fakePtrInterfaceList struct { + Items *[]runtime.Object +} + +func (obj fakePtrInterfaceList) GetObjectKind() schema.ObjectKind { + return schema.EmptyObjectKind +} +func (obj fakePtrInterfaceList) DeepCopyObject() runtime.Object { + panic("fakePtrInterfaceList does not support DeepCopy") +} + +func TestExtractListOfInterfacePtrs(t *testing.T) { + pl := &fakePtrInterfaceList{ + Items: &[]runtime.Object{}, + } + list, err := meta.ExtractList(pl) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if len(list) > 0 { + t.Fatalf("Expected empty list, got %#v", list) + } +} + +type fakePtrValueList struct { + Items []*testapigroup.Carp +} + +func (obj fakePtrValueList) GetObjectKind() schema.ObjectKind { + return schema.EmptyObjectKind +} +func (obj *fakePtrValueList) DeepCopyObject() runtime.Object { + if obj == nil { + return nil + } + clone := fakePtrValueList{ + Items: make([]*testapigroup.Carp, len(obj.Items)), + } + for i, carp := range obj.Items { + clone.Items[i] = carp.DeepCopy() + } + return &clone +} + +func TestSetList(t *testing.T) { + pl := &testapigroup.CarpList{} + list := []runtime.Object{ + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "2"}}, + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "3"}}, + } + err := meta.SetList(pl, list) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if e, a := len(list), len(pl.Items); e != a { + t.Fatalf("Expected %v, got %v", e, a) + } + for i := range list { + if e, a := list[i].(*testapigroup.Carp).Name, pl.Items[i].Name; e != a { + t.Fatalf("Expected %v, got %v", e, a) + } + } +} + +func TestSetListToRuntimeObjectArray(t *testing.T) { + pl := &List{} + list := []runtime.Object{ + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "2"}}, + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "3"}}, + } + err := meta.SetList(pl, list) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if e, a := len(list), len(pl.Items); e != a { + t.Fatalf("Expected %v, got %v", e, a) + } + for i := range list { + if e, a := list[i], pl.Items[i]; e != a { + t.Fatalf("%d: unmatched: %s", i, cmp.Diff(e, a)) + } + } +} + +func TestSetListToMatchingType(t *testing.T) { + pl := &unstructured.UnstructuredList{} + list := []runtime.Object{ + &unstructured.Unstructured{Object: map[string]interface{}{"foo": 1}}, + &unstructured.Unstructured{Object: map[string]interface{}{"foo": 2}}, + &unstructured.Unstructured{Object: map[string]interface{}{"foo": 3}}, + } + err := meta.SetList(pl, list) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if e, a := len(list), len(pl.Items); e != a { + t.Fatalf("Expected %v, got %v", e, a) + } + for i := range list { + if e, a := list[i], &pl.Items[i]; !reflect.DeepEqual(e, a) { + t.Fatalf("%d: unmatched: %s", i, cmp.Diff(e, a)) + } + } +} + +func TestSetExtractListRoundTrip(t *testing.T) { + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + fuzzer := randfill.New().NilChance(0).NumElements(1, 5).Funcs(metafuzzer.Funcs(codecs)...).MaxDepth(10) + for i := 0; i < 5; i++ { + start := &testapigroup.CarpList{} + fuzzer.Fill(&start.Items) + + list, err := meta.ExtractList(start) + if err != nil { + t.Errorf("Unexpected error %v", err) + continue + } + got := &testapigroup.CarpList{} + err = meta.SetList(got, list) + if err != nil { + t.Errorf("Unexpected error %v", err) + continue + } + if e, a := start, got; !reflect.DeepEqual(e, a) { + t.Fatalf("Expected %#v, got %#v", e, a) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/api_meta_meta_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/api_meta_meta_test.go new file mode 100644 index 0000000000..e46c7bc97e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/api_meta_meta_test.go @@ -0,0 +1,390 @@ +/* +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 test + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/meta" + metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/testapigroup" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/randfill" +) + +func TestAPIObjectMeta(t *testing.T) { + j := &testapigroup.Carp{ + TypeMeta: metav1.TypeMeta{APIVersion: "/a", Kind: "b"}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "bar", + Name: "foo", + GenerateName: "prefix", + UID: "uid", + ResourceVersion: "1", + SelfLink: "some/place/only/we/know", + Labels: map[string]string{"foo": "bar"}, + Annotations: map[string]string{"x": "y"}, + Finalizers: []string{ + "finalizer.1", + "finalizer.2", + }, + }, + } + var _ metav1.Object = &j.ObjectMeta + var _ metav1.ObjectMetaAccessor = j + accessor, err := meta.Accessor(j) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if accessor != metav1.Object(j) { + t.Fatalf("should have returned the same pointer: %#v\n\n%#v", accessor, j) + } + if e, a := "bar", accessor.GetNamespace(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "foo", accessor.GetName(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "prefix", accessor.GetGenerateName(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "uid", string(accessor.GetUID()); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "1", accessor.GetResourceVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "some/place/only/we/know", accessor.GetSelfLink(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := []string{"finalizer.1", "finalizer.2"}, accessor.GetFinalizers(); !reflect.DeepEqual(e, a) { + t.Errorf("expected %v, got %v", e, a) + } + + typeAccessor, err := meta.TypeAccessor(j) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if e, a := "a", typeAccessor.GetAPIVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "b", typeAccessor.GetKind(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + + accessor.SetNamespace("baz") + accessor.SetName("bar") + accessor.SetGenerateName("generate") + accessor.SetUID("other") + typeAccessor.SetAPIVersion("c") + typeAccessor.SetKind("d") + accessor.SetResourceVersion("2") + accessor.SetSelfLink("google.com") + accessor.SetFinalizers([]string{"finalizer.3"}) + + // Prove that accessor changes the original object. + if e, a := "baz", j.Namespace; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "bar", j.Name; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "generate", j.GenerateName; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := types.UID("other"), j.UID; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "c", j.APIVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "d", j.Kind; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "2", j.ResourceVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "google.com", j.SelfLink; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := []string{"finalizer.3"}, j.Finalizers; !reflect.DeepEqual(e, a) { + t.Errorf("expected %v, got %v", e, a) + } + + typeAccessor.SetAPIVersion("d") + typeAccessor.SetKind("e") + if e, a := "d", j.APIVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "e", j.Kind; e != a { + t.Errorf("expected %v, got %v", e, a) + } +} + +func TestGenericTypeMeta(t *testing.T) { + type TypeMeta struct { + Kind string `json:"kind,omitempty"` + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` + GenerateName string `json:"generateName,omitempty"` + UID string `json:"uid,omitempty"` + CreationTimestamp metav1.Time `json:"creationTimestamp,omitempty,omitzero"` + SelfLink string `json:"selfLink,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty"` + APIVersion string `json:"apiVersion,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` + OwnerReferences []metav1.OwnerReference `json:"ownerReferences,omitempty"` + Finalizers []string `json:"finalizers,omitempty"` + } + + j := struct{ TypeMeta }{TypeMeta{APIVersion: "a", Kind: "b"}} + + typeAccessor, err := meta.TypeAccessor(&j) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if e, a := "a", typeAccessor.GetAPIVersion(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "b", typeAccessor.GetKind(); e != a { + t.Errorf("expected %v, got %v", e, a) + } + + typeAccessor.SetAPIVersion("c") + typeAccessor.SetKind("d") + + if e, a := "c", j.APIVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "d", j.Kind; e != a { + t.Errorf("expected %v, got %v", e, a) + } + + typeAccessor.SetAPIVersion("d") + typeAccessor.SetKind("e") + + if e, a := "d", j.APIVersion; e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := "e", j.Kind; e != a { + t.Errorf("expected %v, got %v", e, a) + } +} + +type InternalTypeMeta struct { + Kind string `json:"kind,omitempty"` + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` + GenerateName string `json:"generateName,omitempty"` + UID string `json:"uid,omitempty"` + CreationTimestamp metav1.Time `json:"creationTimestamp,omitempty"` + SelfLink string `json:"selfLink,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty"` + Continue string `json:"next,omitempty"` + RemainingItemCount *int64 `json:"remainingItemCount,omitempty"` + APIVersion string `json:"apiVersion,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` + Finalizers []string `json:"finalizers,omitempty"` + OwnerReferences []metav1.OwnerReference `json:"ownerReferences,omitempty"` +} + +func (m *InternalTypeMeta) GetResourceVersion() string { return m.ResourceVersion } +func (m *InternalTypeMeta) SetResourceVersion(rv string) { m.ResourceVersion = rv } +func (m *InternalTypeMeta) GetSelfLink() string { return m.SelfLink } +func (m *InternalTypeMeta) SetSelfLink(link string) { m.SelfLink = link } +func (m *InternalTypeMeta) GetContinue() string { return m.Continue } +func (m *InternalTypeMeta) SetContinue(c string) { m.Continue = c } +func (m *InternalTypeMeta) GetRemainingItemCount() *int64 { return m.RemainingItemCount } +func (m *InternalTypeMeta) SetRemainingItemCount(c *int64) { m.RemainingItemCount = c } + +type MyAPIObject struct { + TypeMeta InternalTypeMeta `json:""` +} + +func (obj *MyAPIObject) GetListMeta() metav1.ListInterface { return &obj.TypeMeta } + +func (obj *MyAPIObject) GetObjectKind() schema.ObjectKind { return obj } +func (obj *MyAPIObject) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.TypeMeta.APIVersion, obj.TypeMeta.Kind = gvk.ToAPIVersionAndKind() +} +func (obj *MyAPIObject) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.TypeMeta.APIVersion, obj.TypeMeta.Kind) +} +func (obj *MyAPIObject) DeepCopyObject() runtime.Object { + panic("MyAPIObject does not support DeepCopy") +} + +type MyIncorrectlyMarkedAsAPIObject struct{} + +func (obj *MyIncorrectlyMarkedAsAPIObject) GetObjectKind() schema.ObjectKind { + return schema.EmptyObjectKind +} +func (obj *MyIncorrectlyMarkedAsAPIObject) DeepCopyObject() runtime.Object { + panic("MyIncorrectlyMarkedAsAPIObject does not support DeepCopy") +} + +func TestResourceVersionerOfAPI(t *testing.T) { + type T struct { + runtime.Object + Expected string + } + testCases := map[string]T{ + "empty api object": {&MyAPIObject{}, ""}, + "api object with version": {&MyAPIObject{TypeMeta: InternalTypeMeta{ResourceVersion: "1"}}, "1"}, + "pointer to api object with version": {&MyAPIObject{TypeMeta: InternalTypeMeta{ResourceVersion: "1"}}, "1"}, + } + versioning := meta.NewAccessor() + for key, testCase := range testCases { + actual, err := versioning.ResourceVersion(testCase.Object) + if err != nil { + t.Errorf("%s: unexpected error %#v", key, err) + } + if actual != testCase.Expected { + t.Errorf("%s: expected %v, got %v", key, testCase.Expected, actual) + } + } + + failingCases := map[string]struct { + runtime.Object + Expected string + }{ + "not a valid object to try": {&MyIncorrectlyMarkedAsAPIObject{}, "1"}, + } + for key, testCase := range failingCases { + _, err := versioning.ResourceVersion(testCase.Object) + if err == nil { + t.Errorf("%s: expected error, got nil", key) + } + } + + setCases := map[string]struct { + runtime.Object + Expected string + }{ + "pointer to api object with version": {&MyAPIObject{TypeMeta: InternalTypeMeta{ResourceVersion: "1"}}, "1"}, + } + for key, testCase := range setCases { + if err := versioning.SetResourceVersion(testCase.Object, "5"); err != nil { + t.Errorf("%s: unexpected error %#v", key, err) + } + actual, err := versioning.ResourceVersion(testCase.Object) + if err != nil { + t.Errorf("%s: unexpected error %#v", key, err) + } + if actual != "5" { + t.Errorf("%s: expected %v, got %v", key, "5", actual) + } + } +} + +type MyAPIObject2 struct { + metav1.TypeMeta + metav1.ObjectMeta +} + +func getObjectMetaAndOwnerReferences() (myAPIObject2 MyAPIObject2, metaOwnerReferences []metav1.OwnerReference) { + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + randfill.New().NilChance(.5).NumElements(1, 5).Funcs(metafuzzer.Funcs(codecs)...).MaxDepth(10).Fill(&myAPIObject2) + references := myAPIObject2.ObjectMeta.OwnerReferences + // This is necessary for the test to pass because the getter will return a + // non-nil slice. + metaOwnerReferences = make([]metav1.OwnerReference, 0) + for i := 0; i < len(references); i++ { + metaOwnerReferences = append(metaOwnerReferences, metav1.OwnerReference{ + Kind: references[i].Kind, + Name: references[i].Name, + UID: references[i].UID, + APIVersion: references[i].APIVersion, + Controller: references[i].Controller, + BlockOwnerDeletion: references[i].BlockOwnerDeletion, + }) + } + if len(references) == 0 { + // This is necessary for the test to pass because the setter will make a + // non-nil slice. + myAPIObject2.ObjectMeta.OwnerReferences = make([]metav1.OwnerReference, 0) + } + return myAPIObject2, metaOwnerReferences +} + +func testGetOwnerReferences(t *testing.T) { + obj, expected := getObjectMetaAndOwnerReferences() + accessor, err := meta.Accessor(&obj) + if err != nil { + t.Error(err) + } + references := accessor.GetOwnerReferences() + if !reflect.DeepEqual(references, expected) { + t.Errorf("expect %#v\n got %#v", expected, references) + } +} + +func testSetOwnerReferences(t *testing.T) { + expected, references := getObjectMetaAndOwnerReferences() + obj := MyAPIObject2{} + accessor, err := meta.Accessor(&obj) + if err != nil { + t.Error(err) + } + accessor.SetOwnerReferences(references) + if e, a := expected.ObjectMeta.OwnerReferences, obj.ObjectMeta.OwnerReferences; !reflect.DeepEqual(e, a) { + t.Errorf("expect %#v\n got %#v", e, a) + } +} + +func TestAccessOwnerReferences(t *testing.T) { + fuzzIter := 5 + for i := 0; i < fuzzIter; i++ { + testGetOwnerReferences(t) + testSetOwnerReferences(t) + } +} + +// BenchmarkAccessorSetFastPath shows the interface fast path +func BenchmarkAccessorSetFastPath(b *testing.B) { + obj := &testapigroup.Carp{ + TypeMeta: metav1.TypeMeta{APIVersion: "/a", Kind: "b"}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "bar", + Name: "foo", + GenerateName: "prefix", + UID: "uid", + ResourceVersion: "1", + SelfLink: "some/place/only/we/know", + Labels: map[string]string{"foo": "bar"}, + Annotations: map[string]string{"x": "y"}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + acc, err := meta.Accessor(obj) + if err != nil { + b.Fatal(err) + } + acc.SetNamespace("something") + } + b.StopTimer() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/apis_meta_v1_unstructed_unstructure_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/apis_meta_v1_unstructed_unstructure_test.go new file mode 100644 index 0000000000..6b487d3e40 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/apis_meta_v1_unstructed_unstructure_test.go @@ -0,0 +1,560 @@ +/* +Copyright 2015 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 test + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "testing" + "time" + + apitesting "k8s.io/apimachinery/pkg/api/apitesting" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/apis/testapigroup" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" +) + +func TestDecodeUnstructured(t *testing.T) { + groupVersionString := "v1" + rawJson := fmt.Sprintf(`{"kind":"Pod","apiVersion":"%s","metadata":{"name":"test"}}`, groupVersionString) + pl := &List{ + Items: []runtime.Object{ + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: groupVersionString}, + Raw: []byte(rawJson), + ContentType: runtime.ContentTypeJSON, + }, + &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "", APIVersion: groupVersionString}, + Raw: []byte(rawJson), + ContentType: runtime.ContentTypeJSON, + }, + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Foo", + "apiVersion": "Bar", + "test": "value", + }, + }, + }, + } + if errs := runtime.DecodeList(pl.Items, unstructured.UnstructuredJSONScheme); len(errs) == 1 { + t.Fatalf("unexpected error %v", errs) + } + if pod, ok := pl.Items[1].(*unstructured.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" { + t.Errorf("object not converted: %#v", pl.Items[1]) + } + if pod, ok := pl.Items[2].(*unstructured.Unstructured); !ok || pod.Object["kind"] != "Pod" || pod.Object["metadata"].(map[string]interface{})["name"] != "test" { + t.Errorf("object not converted: %#v", pl.Items[2]) + } +} + +func TestDecode(t *testing.T) { + tcs := []struct { + json []byte + want runtime.Object + }{ + { + json: []byte(`{"apiVersion": "test", "kind": "test_kind"}`), + want: &unstructured.Unstructured{ + Object: map[string]interface{}{"apiVersion": "test", "kind": "test_kind"}, + }, + }, + { + json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`), + want: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"}, + Items: []unstructured.Unstructured{}, + }, + }, + { + json: []byte(`{"items": [{"metadata": {"name": "object1", "deletionGracePeriodSeconds": 10}, "apiVersion": "test", "kind": "test_kind"}, {"metadata": {"name": "object2"}, "apiVersion": "test", "kind": "test_kind"}], "apiVersion": "test", "kind": "test_list"}`), + want: &unstructured.UnstructuredList{ + Object: map[string]interface{}{"apiVersion": "test", "kind": "test_list"}, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{"name": "object1", "deletionGracePeriodSeconds": int64(10)}, + "apiVersion": "test", + "kind": "test_kind", + }, + }, + { + Object: map[string]interface{}{ + "metadata": map[string]interface{}{"name": "object2"}, + "apiVersion": "test", + "kind": "test_kind", + }, + }, + }, + }, + }, + } + + for _, tc := range tcs { + got, _, err := unstructured.UnstructuredJSONScheme.Decode(tc.json, nil, nil) + if err != nil { + t.Errorf("Unexpected error for %q: %v", string(tc.json), err) + continue + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("Decode(%q) want: %v\ngot: %v", string(tc.json), tc.want, got) + } + } +} + +func TestUnstructuredGetters(t *testing.T) { + trueVar := true + ten := int64(10) + unstruct := unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "test_kind", + "apiVersion": "test_version", + "metadata": map[string]interface{}{ + "name": "test_name", + "namespace": "test_namespace", + "generateName": "test_generateName", + "uid": "test_uid", + "resourceVersion": "test_resourceVersion", + "generation": ten, + "deletionGracePeriodSeconds": ten, + "selfLink": "test_selfLink", + "creationTimestamp": "2009-11-10T23:00:00Z", + "deletionTimestamp": "2010-11-10T23:00:00Z", + "labels": map[string]interface{}{ + "test_label": "test_value", + }, + "annotations": map[string]interface{}{ + "test_annotation": "test_value", + }, + "ownerReferences": []interface{}{ + map[string]interface{}{ + "kind": "Pod", + "name": "poda", + "apiVersion": "v1", + "uid": "1", + }, + map[string]interface{}{ + "kind": "Pod", + "name": "podb", + "apiVersion": "v1", + "uid": "2", + // though these fields are of type *bool, but when + // decoded from JSON, they are unmarshalled as bool. + "controller": true, + "blockOwnerDeletion": true, + }, + }, + "finalizers": []interface{}{ + "finalizer.1", + "finalizer.2", + }, + }, + }, + } + + if got, want := unstruct.GetAPIVersion(), "test_version"; got != want { + t.Errorf("GetAPIVersions() = %s, want %s", got, want) + } + + if got, want := unstruct.GetKind(), "test_kind"; got != want { + t.Errorf("GetKind() = %s, want %s", got, want) + } + + if got, want := unstruct.GetNamespace(), "test_namespace"; got != want { + t.Errorf("GetNamespace() = %s, want %s", got, want) + } + + if got, want := unstruct.GetName(), "test_name"; got != want { + t.Errorf("GetName() = %s, want %s", got, want) + } + + if got, want := unstruct.GetGenerateName(), "test_generateName"; got != want { + t.Errorf("GetGenerateName() = %s, want %s", got, want) + } + + if got, want := unstruct.GetUID(), types.UID("test_uid"); got != want { + t.Errorf("GetUID() = %s, want %s", got, want) + } + + if got, want := unstruct.GetResourceVersion(), "test_resourceVersion"; got != want { + t.Errorf("GetResourceVersion() = %s, want %s", got, want) + } + + if got, want := unstruct.GetSelfLink(), "test_selfLink"; got != want { + t.Errorf("GetSelfLink() = %s, want %s", got, want) + } + + if got, want := unstruct.GetCreationTimestamp(), metav1.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC); !got.Equal(&want) { + t.Errorf("GetCreationTimestamp() = %s, want %s", got, want) + } + + if got, want := unstruct.GetDeletionTimestamp(), metav1.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC); got == nil || !got.Equal(&want) { + t.Errorf("GetDeletionTimestamp() = %s, want %s", got, want) + } + + if got, want := unstruct.GetLabels(), map[string]string{"test_label": "test_value"}; !reflect.DeepEqual(got, want) { + t.Errorf("GetLabels() = %s, want %s", got, want) + } + + if got, want := unstruct.GetAnnotations(), map[string]string{"test_annotation": "test_value"}; !reflect.DeepEqual(got, want) { + t.Errorf("GetAnnotations() = %s, want %s", got, want) + } + refs := unstruct.GetOwnerReferences() + expectedOwnerReferences := []metav1.OwnerReference{ + { + Kind: "Pod", + Name: "poda", + APIVersion: "v1", + UID: "1", + }, + { + Kind: "Pod", + Name: "podb", + APIVersion: "v1", + UID: "2", + Controller: &trueVar, + BlockOwnerDeletion: &trueVar, + }, + } + if got, want := refs, expectedOwnerReferences; !reflect.DeepEqual(got, want) { + t.Errorf("GetOwnerReferences()=%v, want %v", got, want) + } + if got, want := unstruct.GetFinalizers(), []string{"finalizer.1", "finalizer.2"}; !reflect.DeepEqual(got, want) { + t.Errorf("GetFinalizers()=%v, want %v", got, want) + } + if got, want := unstruct.GetDeletionGracePeriodSeconds(), &ten; !reflect.DeepEqual(got, want) { + t.Errorf("GetDeletionGracePeriodSeconds()=%v, want %v", got, want) + } + if got, want := unstruct.GetGeneration(), ten; !reflect.DeepEqual(got, want) { + t.Errorf("GetGeneration()=%v, want %v", got, want) + } +} + +func TestUnstructuredSetters(t *testing.T) { + unstruct := unstructured.Unstructured{} + trueVar := true + ten := int64(10) + + want := unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "test_kind", + "apiVersion": "test_version", + "metadata": map[string]interface{}{ + "name": "test_name", + "namespace": "test_namespace", + "generateName": "test_generateName", + "uid": "test_uid", + "resourceVersion": "test_resourceVersion", + "selfLink": "test_selfLink", + "creationTimestamp": "2009-11-10T23:00:00Z", + "deletionTimestamp": "2010-11-10T23:00:00Z", + "deletionGracePeriodSeconds": ten, + "generation": ten, + "labels": map[string]interface{}{ + "test_label": "test_value", + }, + "annotations": map[string]interface{}{ + "test_annotation": "test_value", + }, + "ownerReferences": []interface{}{ + map[string]interface{}{ + "kind": "Pod", + "name": "poda", + "apiVersion": "v1", + "uid": "1", + }, + map[string]interface{}{ + "kind": "Pod", + "name": "podb", + "apiVersion": "v1", + "uid": "2", + "controller": true, + "blockOwnerDeletion": true, + }, + }, + "finalizers": []interface{}{ + "finalizer.1", + "finalizer.2", + }, + }, + }, + } + + unstruct.SetAPIVersion("test_version") + unstruct.SetKind("test_kind") + unstruct.SetNamespace("test_namespace") + unstruct.SetName("test_name") + unstruct.SetGenerateName("test_generateName") + unstruct.SetUID(types.UID("test_uid")) + unstruct.SetResourceVersion("test_resourceVersion") + unstruct.SetSelfLink("test_selfLink") + unstruct.SetCreationTimestamp(metav1.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)) + date := metav1.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC) + unstruct.SetDeletionTimestamp(&date) + unstruct.SetLabels(map[string]string{"test_label": "test_value"}) + unstruct.SetAnnotations(map[string]string{"test_annotation": "test_value"}) + newOwnerReferences := []metav1.OwnerReference{ + { + Kind: "Pod", + Name: "poda", + APIVersion: "v1", + UID: "1", + }, + { + Kind: "Pod", + Name: "podb", + APIVersion: "v1", + UID: "2", + Controller: &trueVar, + BlockOwnerDeletion: &trueVar, + }, + } + unstruct.SetOwnerReferences(newOwnerReferences) + unstruct.SetFinalizers([]string{"finalizer.1", "finalizer.2"}) + unstruct.SetDeletionGracePeriodSeconds(&ten) + unstruct.SetGeneration(ten) + + if !reflect.DeepEqual(unstruct, want) { + t.Errorf("Wanted: \n%s\n Got:\n%s", want, unstruct) + } +} + +func TestOwnerReferences(t *testing.T) { + t.Parallel() + trueVar := true + falseVar := false + refs := []metav1.OwnerReference{ + { + APIVersion: "v2", + Kind: "K2", + Name: "n2", + UID: types.UID("abc1"), + }, + { + APIVersion: "v1", + Kind: "K1", + Name: "n1", + UID: types.UID("abc2"), + Controller: &trueVar, + BlockOwnerDeletion: &falseVar, + }, + { + APIVersion: "v3", + Kind: "K3", + Name: "n3", + UID: types.UID("abc3"), + Controller: &falseVar, + BlockOwnerDeletion: &trueVar, + }, + } + for i, ref := range refs { + t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() + u1 := unstructured.Unstructured{ + Object: make(map[string]interface{}), + } + refsX := []metav1.OwnerReference{ref} + u1.SetOwnerReferences(refsX) + + have := u1.GetOwnerReferences() + if !reflect.DeepEqual(have, refsX) { + t.Errorf("Object references are not the same: %#v != %#v", have, refsX) + } + }) + } +} + +func TestUnstructuredListGetters(t *testing.T) { + unstruct := unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "test_kind", + "apiVersion": "test_version", + "metadata": map[string]interface{}{ + "resourceVersion": "test_resourceVersion", + "selfLink": "test_selfLink", + }, + }, + } + + if got, want := unstruct.GetAPIVersion(), "test_version"; got != want { + t.Errorf("GetAPIVersions() = %s, want %s", got, want) + } + + if got, want := unstruct.GetKind(), "test_kind"; got != want { + t.Errorf("GetKind() = %s, want %s", got, want) + } + + if got, want := unstruct.GetResourceVersion(), "test_resourceVersion"; got != want { + t.Errorf("GetResourceVersion() = %s, want %s", got, want) + } + + if got, want := unstruct.GetSelfLink(), "test_selfLink"; got != want { + t.Errorf("GetSelfLink() = %s, want %s", got, want) + } +} + +func TestUnstructuredListSetters(t *testing.T) { + unstruct := unstructured.UnstructuredList{} + + want := unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "test_kind", + "apiVersion": "test_version", + "metadata": map[string]interface{}{ + "resourceVersion": "test_resourceVersion", + "selfLink": "test_selfLink", + }, + }, + } + + unstruct.SetAPIVersion("test_version") + unstruct.SetKind("test_kind") + unstruct.SetResourceVersion("test_resourceVersion") + unstruct.SetSelfLink("test_selfLink") + + if !reflect.DeepEqual(unstruct, want) { + t.Errorf("Wanted: \n%s\n Got:\n%s", unstruct, want) + } +} + +func TestDecodeNumbers(t *testing.T) { + + // Start with a valid pod + originalJSON := []byte(`{ + "kind":"Carp", + "apiVersion":"v1", + "metadata":{"name":"pod","namespace":"foo"}, + "spec":{ + "containers":[{"name":"container","image":"container"}], + "activeDeadlineSeconds":1000030003 + } + }`) + + pod := &testapigroup.Carp{} + + _, codecs := TestScheme() + codec := apitesting.TestCodec(codecs, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}) + + err := runtime.DecodeInto(codec, originalJSON, pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Round-trip with unstructured codec + unstructuredObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalJSON) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + roundtripJSON, err := runtime.Encode(unstructured.UnstructuredJSONScheme, unstructuredObj) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Make sure we serialize back out in int form + if !strings.Contains(string(roundtripJSON), `"activeDeadlineSeconds":1000030003`) { + t.Errorf("Expected %s, got %s", `"activeDeadlineSeconds":1000030003`, string(roundtripJSON)) + } + + // Decode with structured codec again + obj2, err := runtime.Decode(codec, roundtripJSON) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // ensure pod is still valid + pod2, ok := obj2.(*testapigroup.Carp) + if !ok { + t.Fatalf("expected an *api.Pod, got %#v", obj2) + } + + // ensure round-trip preserved large integers + if !reflect.DeepEqual(pod, pod2) { + t.Fatalf("Expected\n\t%#v, got \n\t%#v", pod, pod2) + } +} + +// TestAccessorMethods does opaque roundtrip testing against an Unstructured +// instance's Object methods to ensure that what is "Set" matches what you +// subsequently "Get" without any assertions against internal state. +func TestAccessorMethods(t *testing.T) { + int64p := func(i int) *int64 { + v := int64(i) + return &v + } + tests := []struct { + accessor string + val interface{} + nilVal reflect.Value + }{ + {accessor: "Namespace", val: "foo"}, + {accessor: "Name", val: "bar"}, + {accessor: "GenerateName", val: "baz"}, + {accessor: "UID", val: types.UID("uid")}, + {accessor: "ResourceVersion", val: "1"}, + {accessor: "Generation", val: int64(5)}, + {accessor: "SelfLink", val: "/foo"}, + // TODO: Handle timestamps, which are being marshalled as UTC and + // unmarshalled as Local. + // https://github.com/kubernetes/kubernetes/issues/21402 + // {accessor: "CreationTimestamp", val: someTime}, + // {accessor: "DeletionTimestamp", val: someTimeP}, + {accessor: "DeletionTimestamp", nilVal: reflect.ValueOf((*metav1.Time)(nil))}, + {accessor: "DeletionGracePeriodSeconds", val: int64p(10)}, + {accessor: "DeletionGracePeriodSeconds", val: int64p(0)}, + {accessor: "DeletionGracePeriodSeconds", nilVal: reflect.ValueOf((*int64)(nil))}, + {accessor: "Labels", val: map[string]string{"foo": "bar"}}, + {accessor: "Annotations", val: map[string]string{"foo": "bar"}}, + {accessor: "Finalizers", val: []string{"foo"}}, + {accessor: "OwnerReferences", val: []metav1.OwnerReference{{Name: "foo"}}}, + } + for i, test := range tests { + t.Logf("evaluating test %d (%s)", i, test.accessor) + + u := &unstructured.Unstructured{} + setter := reflect.ValueOf(u).MethodByName("Set" + test.accessor) + getter := reflect.ValueOf(u).MethodByName("Get" + test.accessor) + + args := []reflect.Value{} + if test.val != nil { + args = append(args, reflect.ValueOf(test.val)) + } else { + args = append(args, test.nilVal) + } + setter.Call(args) + + ret := getter.Call([]reflect.Value{}) + actual := ret[0].Interface() + + var expected interface{} + if test.val != nil { + expected = test.val + } else { + expected = test.nilVal.Interface() + } + + if e, a := expected, actual; !reflect.DeepEqual(e, a) { + t.Fatalf("%s: expected %v (%T), got %v (%T)", test.accessor, e, e, a, a) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/coverage/coverage.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/coverage/coverage.go new file mode 100644 index 0000000000..8482042a4a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/coverage/coverage.go @@ -0,0 +1,156 @@ +/* +Copyright 2025 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 coverage + +import ( + "fmt" + "regexp" + "sort" + "strings" + "sync" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// Rule is one declared field-validation error: error type plus optional origin. +type Rule struct { + ErrorType string + Origin string +} + +// FieldRules maps field path → declared rules for a single Kind. +type FieldRules map[string][]Rule + +// ruleKey is the flat identity of one declared-or-observed rule across the +// whole test process. +type ruleKey struct { + gvk schema.GroupVersionKind + path string + errorType string + origin string +} + +// indexKeyRe normalizes runtime path subscripts ("[0]", "[my-key]") to "[*]" +// so observed paths line up with the canonical paths from validation-gen. +var indexKeyRe = regexp.MustCompile(`\[[^\]]+\]`) + +var ( + rulesMu sync.Mutex + declared = sets.New[ruleKey]() + observed = sets.New[ruleKey]() + nilPathString = (*field.Path)(nil).String() +) + +// RegisterDeclaredRules records the rules declared for one GVK by +// validation-gen. +func RegisterDeclaredRules(gvk schema.GroupVersionKind, rules FieldRules) { + rulesMu.Lock() + defer rulesMu.Unlock() + for path, rs := range rules { + for _, r := range rs { + declared.Insert(ruleKey{gvk: gvk, path: path, errorType: r.ErrorType, origin: r.Origin}) + } + } +} + +// RecordObservedRules marks every error in errs as observed for the given GVK. +// Idempotent per (GVK, path, errorType, origin). Callers compute the GVK from +// whatever context they have (e.g., a runtime scheme + request info). +func RecordObservedRules(gvk schema.GroupVersionKind, errs field.ErrorList) { + rulesMu.Lock() + defer rulesMu.Unlock() + // Runtime errors carry the actual subscript ("spec.items[3]", + // "metadata.labels[my-key]"); declared paths use "[*]". Replace every + // "[...]" with "[*]" so the lookup in AssertDeclarativeCoverage matches. + for _, e := range errs { + path := e.Field + if path == nilPathString { + path = "" + } + path = indexKeyRe.ReplaceAllString(path, "[*]") + observed.Insert(ruleKey{gvk: gvk, path: path, errorType: string(e.Type), origin: e.Origin}) + } +} + +// AssertDeclarativeCoverage returns nil if every rule registered via +// RegisterDeclaredRules was observed at least once during this test process; +// otherwise an error listing the uncovered rules. +func AssertDeclarativeCoverage() error { + rulesMu.Lock() + uncovered := declared.Difference(observed).UnsortedList() + rulesMu.Unlock() + if len(uncovered) == 0 { + return nil + } + return fmt.Errorf("%d uncovered declarative-validation rules:\n%s", len(uncovered), formatUncovered(uncovered)) +} + +// formatUncovered renders uncovered rules grouped by GVK, one rule per line. +// GVKs are sorted by group/version/kind; rules within a GVK by path/errorType/origin. +// +// Example: +// +// example.com/v1, Kind=Widget: +// spec.name FieldValueRequired +// spec.name FieldValueInvalid origin="format=dns-label" +func formatUncovered(uncovered []ruleKey) string { + gvksToRules := map[schema.GroupVersionKind][]ruleKey{} + for _, k := range uncovered { + gvksToRules[k.gvk] = append(gvksToRules[k.gvk], k) + } + gvks := make([]schema.GroupVersionKind, 0, len(gvksToRules)) + for gvk := range gvksToRules { + gvks = append(gvks, gvk) + } + sort.Slice(gvks, func(i, j int) bool { + if gvks[i].Group != gvks[j].Group { + return gvks[i].Group < gvks[j].Group + } + if gvks[i].Version != gvks[j].Version { + return gvks[i].Version < gvks[j].Version + } + return gvks[i].Kind < gvks[j].Kind + }) + var sb strings.Builder + for _, gvk := range gvks { + gv := gvk.Group + "/" + gvk.Version + if gvk.Group == "" { + gv = gvk.Version + } + fmt.Fprintf(&sb, "%s, Kind=%s:\n", gv, gvk.Kind) + keys := gvksToRules[gvk] + sort.Slice(keys, func(i, j int) bool { + if keys[i].path != keys[j].path { + return keys[i].path < keys[j].path + } + if keys[i].errorType != keys[j].errorType { + return keys[i].errorType < keys[j].errorType + } + return keys[i].origin < keys[j].origin + }) + for _, k := range keys { + if k.origin != "" { + fmt.Fprintf(&sb, " %s %s origin=%q\n", k.path, k.errorType, k.origin) + } else { + fmt.Fprintf(&sb, " %s %s\n", k.path, k.errorType) + } + } + } + return sb.String() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_helper_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_helper_test.go new file mode 100644 index 0000000000..31e6e7af5c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_helper_test.go @@ -0,0 +1,49 @@ +/* +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 test + +import ( + "testing" + + apitesting "k8s.io/apimachinery/pkg/api/apitesting" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/testapigroup" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestDecodeList(t *testing.T) { + pl := List{ + Items: []runtime.Object{ + &testapigroup.Carp{ObjectMeta: metav1.ObjectMeta{Name: "1"}}, + &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Carp", APIVersion: "v1"}, + Raw: []byte(`{"kind":"Carp","apiVersion":"` + "v1" + `","metadata":{"name":"test"}}`), + ContentType: runtime.ContentTypeJSON, + }, + }, + } + + _, codecs := TestScheme() + Codec := apitesting.TestCodec(codecs, testapigroup.SchemeGroupVersion) + + if errs := runtime.DecodeList(pl.Items, Codec); len(errs) != 0 { + t.Fatalf("unexpected error %v", errs) + } + if pod, ok := pl.Items[1].(*testapigroup.Carp); !ok || pod.Name != "test" { + t.Errorf("object not converted: %#v", pl.Items[1]) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_serializer_protobuf_protobuf_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_serializer_protobuf_protobuf_test.go new file mode 100644 index 0000000000..75c26ca8cb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_serializer_protobuf_protobuf_test.go @@ -0,0 +1,362 @@ +/* +Copyright 2015 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 test + +import ( + "bytes" + "encoding/hex" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" +) + +type testObject struct { + gvk schema.GroupVersionKind +} + +func (d *testObject) GetObjectKind() schema.ObjectKind { return d } +func (d *testObject) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } +func (d *testObject) GroupVersionKind() schema.GroupVersionKind { return d.gvk } +func (d *testObject) DeepCopyObject() runtime.Object { + panic("testObject does not support DeepCopy") +} + +type testMarshalable struct { + testObject + data []byte + err error +} + +func (d *testMarshalable) Marshal() ([]byte, error) { + return d.data, d.err +} + +func (d *testMarshalable) DeepCopyObject() runtime.Object { + panic("testMarshalable does not support DeepCopy") +} + +type testBufferedMarshalable struct { + testObject + data []byte + err error +} + +func (d *testBufferedMarshalable) Marshal() ([]byte, error) { + return nil, fmt.Errorf("not invokable") +} + +func (d *testBufferedMarshalable) MarshalTo(data []byte) (int, error) { + copy(data, d.data) + return len(d.data), d.err +} + +func (d *testBufferedMarshalable) Size() int { + return len(d.data) +} + +func (d *testBufferedMarshalable) DeepCopyObject() runtime.Object { + panic("testBufferedMarshalable does not support DeepCopy") +} + +func TestRecognize(t *testing.T) { + s := protobuf.NewSerializer(nil, nil) + ignores := [][]byte{ + nil, + {}, + []byte("k8s"), + {0x6b, 0x38, 0x73, 0x01}, + } + for i, data := range ignores { + if ok, _, err := s.RecognizesData(data); err != nil || ok { + t.Errorf("%d: should not recognize data: %v", i, err) + } + } + recognizes := [][]byte{ + {0x6b, 0x38, 0x73, 0x00}, + {0x6b, 0x38, 0x73, 0x00, 0x01}, + } + for i, data := range recognizes { + if ok, _, err := s.RecognizesData(data); err != nil || !ok { + t.Errorf("%d: should recognize data: %v", i, err) + } + } +} + +func TestEncode(t *testing.T) { + obj1 := &testMarshalable{testObject: testObject{}, data: []byte{}} + wire1 := []byte{ + 0x6b, 0x38, 0x73, 0x00, // prefix + 0x0a, 0x04, + 0x0a, 0x00, // apiversion + 0x12, 0x00, // kind + 0x12, 0x00, // data + 0x1a, 0x00, // content-type + 0x22, 0x00, // content-encoding + } + obj2 := &testMarshalable{ + testObject: testObject{gvk: schema.GroupVersionKind{Kind: "test", Group: "other", Version: "version"}}, + data: []byte{0x01, 0x02, 0x03}, + } + wire2 := []byte{ + 0x6b, 0x38, 0x73, 0x00, // prefix + 0x0a, 0x15, + 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // apiversion + 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, // kind + 0x12, 0x03, 0x01, 0x02, 0x03, // data + 0x1a, 0x00, // content-type + 0x22, 0x00, // content-encoding + } + + err1 := fmt.Errorf("a test error") + + testCases := []struct { + obj runtime.Object + data []byte + errFn func(error) bool + }{ + { + obj: &testObject{}, + errFn: protobuf.IsNotMarshalable, + }, + { + obj: obj1, + data: wire1, + }, + { + obj: &testMarshalable{testObject: obj1.testObject, err: err1}, + errFn: func(err error) bool { return err == err1 }, + }, + { + // if this test fails, writing the "fast path" marshal is not the same as the "slow path" + obj: &testBufferedMarshalable{testObject: obj1.testObject, data: obj1.data}, + data: wire1, + }, + { + obj: obj2, + data: wire2, + }, + { + // if this test fails, writing the "fast path" marshal is not the same as the "slow path" + obj: &testBufferedMarshalable{testObject: obj2.testObject, data: obj2.data}, + data: wire2, + }, + { + obj: &testBufferedMarshalable{testObject: obj1.testObject, err: err1}, + errFn: func(err error) bool { return err == err1 }, + }, + } + + for i, test := range testCases { + s := protobuf.NewSerializer(nil, nil) + data, err := runtime.Encode(s, test.obj) + + switch { + case err == nil && test.errFn != nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil && test.errFn == nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + t.Errorf("%d: failed: %v", i, err) + } + if data != nil { + t.Errorf("%d: should not have returned nil data", i) + } + continue + } + + if test.data != nil && !bytes.Equal(test.data, data) { + t.Errorf("%d: unexpected data:\n%s", i, hex.Dump(data)) + continue + } + + if ok, _, err := s.RecognizesData(data); !ok || err != nil { + t.Errorf("%d: did not recognize data generated by call: %v", i, err) + } + } +} + +func TestProtobufDecode(t *testing.T) { + wire1 := []byte{ + 0x6b, 0x38, 0x73, 0x00, // prefix + 0x0a, 0x04, + 0x0a, 0x00, // apiversion + 0x12, 0x00, // kind + 0x12, 0x00, // data + 0x1a, 0x00, // content-type + 0x22, 0x00, // content-encoding + } + wire2 := []byte{ + 0x6b, 0x38, 0x73, 0x00, // prefix + 0x0a, 0x15, + 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // apiversion + 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, // kind + 0x12, 0x07, 0x6b, 0x38, 0x73, 0x00, 0x01, 0x02, 0x03, // data + 0x1a, 0x00, // content-type + 0x22, 0x00, // content-encoding + } + + testCases := []struct { + obj runtime.Object + data []byte + errFn func(error) bool + }{ + { + obj: &runtime.Unknown{}, + errFn: func(err error) bool { return err.Error() == "empty data" }, + }, + { + data: []byte{0x6b}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "does not appear to be a protobuf message") }, + }, + { + obj: &runtime.Unknown{ + Raw: []byte{}, + }, + data: wire1, + }, + { + obj: &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "other/version", + Kind: "test", + }, + // content type is set because the prefix matches the content + ContentType: runtime.ContentTypeProtobuf, + Raw: []byte{0x6b, 0x38, 0x73, 0x00, 0x01, 0x02, 0x03}, + }, + data: wire2, + }, + } + + for i, test := range testCases { + s := protobuf.NewSerializer(nil, nil) + unk := &runtime.Unknown{} + err := runtime.DecodeInto(s, test.data, unk) + + switch { + case err == nil && test.errFn != nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil && test.errFn == nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + t.Errorf("%d: failed: %v", i, err) + } + continue + } + + if !reflect.DeepEqual(unk, test.obj) { + t.Errorf("%d: unexpected object:\n%#v", i, unk) + continue + } + } +} + +func TestDecodeObjects(t *testing.T) { + obj1 := &v1.Carp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cool", + }, + Spec: v1.CarpSpec{ + Hostname: "coolhost", + }, + } + obj1wire, err := obj1.Marshal() + if err != nil { + t.Fatal(err) + } + + wire1, err := (&runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Carp", APIVersion: "v1"}, + Raw: obj1wire, + }).Marshal() + if err != nil { + t.Fatal(err) + } + + unk2 := &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Carp", APIVersion: "v1"}, + } + wire2 := make([]byte, len(wire1)*2) + n, err := unk2.NestedMarshalTo(wire2, obj1, uint64(obj1.Size())) + if err != nil { + t.Fatal(err) + } + if n != len(wire1) || !bytes.Equal(wire1, wire2[:n]) { + t.Fatalf("unexpected wire:\n%s", hex.Dump(wire2[:n])) + } + + wire1 = append([]byte{0x6b, 0x38, 0x73, 0x00}, wire1...) + + obj1WithKind := obj1.DeepCopyObject() + obj1WithKind.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Carp"}) + testCases := []struct { + obj runtime.Object + data []byte + errFn func(error) bool + }{ + { + obj: obj1WithKind, + data: wire1, + }, + } + scheme := runtime.NewScheme() + for i, test := range testCases { + scheme.AddKnownTypes(schema.GroupVersion{Version: "v1"}, &v1.Carp{}) + require.NoError(t, v1.AddToScheme(scheme)) + s := protobuf.NewSerializer(scheme, scheme) + obj, err := runtime.Decode(s, test.data) + + switch { + case err == nil && test.errFn != nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil && test.errFn == nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + t.Errorf("%d: failed: %v", i, err) + } + if obj != nil { + t.Errorf("%d: should not have returned an object", i) + } + continue + } + + if !apiequality.Semantic.DeepEqual(obj, test.obj) { + t.Errorf("%d: unexpected object:\n%s", i, cmp.Diff(test.obj, obj)) + continue + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_unversioned_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_unversioned_test.go new file mode 100644 index 0000000000..fc8c176480 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/runtime_unversioned_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2015 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 test + +import ( + "encoding/json" + "reflect" + "testing" + + // TODO: Ideally we should create the necessary package structure in e.g., + // pkg/conversion/test/... instead of importing pkg/api here. + apitesting "k8s.io/apimachinery/pkg/api/apitesting" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestV1EncodeDecodeStatus(t *testing.T) { + status := &metav1.Status{ + Status: metav1.StatusFailure, + Code: 200, + Reason: metav1.StatusReasonUnknown, + Message: "", + } + + _, codecs := TestScheme() + codec := apitesting.TestCodec(codecs, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}) + + encoded, err := runtime.Encode(codec, status) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + typeMeta := metav1.TypeMeta{} + if err := json.Unmarshal(encoded, &typeMeta); err != nil { + t.Errorf("unexpected error: %v", err) + } + if typeMeta.Kind != "Status" { + t.Errorf("Kind is not set to \"Status\". Got %v", string(encoded)) + } + if typeMeta.APIVersion != "v1" { + t.Errorf("APIVersion is not set to \"v1\". Got %v", string(encoded)) + } + decoded, err := runtime.Decode(codec, encoded) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !reflect.DeepEqual(status, decoded) { + t.Errorf("expected: %v, got: %v", status, decoded) + } +} + +func TestExperimentalEncodeDecodeStatus(t *testing.T) { + status := &metav1.Status{ + Status: metav1.StatusFailure, + Code: 200, + Reason: metav1.StatusReasonUnknown, + Message: "", + } + _, codecs := TestScheme() + expCodec := apitesting.TestCodec(codecs, schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}) + + encoded, err := runtime.Encode(expCodec, status) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + typeMeta := metav1.TypeMeta{} + if err := json.Unmarshal(encoded, &typeMeta); err != nil { + t.Errorf("unexpected error: %v", err) + } + if typeMeta.Kind != "Status" { + t.Errorf("Kind is not set to \"Status\". Got %s", encoded) + } + if typeMeta.APIVersion != "v1" { + t.Errorf("APIVersion is not set to \"\". Got %s", encoded) + } + decoded, err := runtime.Decode(expCodec, encoded) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !reflect.DeepEqual(status, decoded) { + t.Errorf("expected: %v, got: %v", status, decoded) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/util.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/util.go new file mode 100644 index 0000000000..9b6dac094e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/util.go @@ -0,0 +1,70 @@ +/* +Copyright 2017 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 test + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/testapigroup" + "k8s.io/apimachinery/pkg/apis/testapigroup/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + apiserializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +// List and ListV1 should be kept in sync with k8s.io/kubernetes/pkg/api#List +// and k8s.io/api/core/v1#List. +// +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type List struct { + metav1.TypeMeta + metav1.ListMeta + + Items []runtime.Object +} + +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ListV1 struct { + metav1.TypeMeta `json:""` + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Items []runtime.RawExtension `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +func TestScheme() (*runtime.Scheme, apiserializer.CodecFactory) { + internalGV := schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal} + externalGV := schema.GroupVersion{Group: "", Version: "v1"} + scheme := runtime.NewScheme() + + scheme.AddKnownTypes(internalGV, + &testapigroup.Carp{}, + &testapigroup.CarpList{}, + &List{}, + ) + scheme.AddKnownTypes(externalGV, + &v1.Carp{}, + &v1.CarpList{}, + &List{}, + ) + utilruntime.Must(testapigroup.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) + + codecs := apiserializer.NewCodecFactory(scheme) + return scheme, codecs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/zz_generated.deepcopy.go new file mode 100644 index 0000000000..b86d609211 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/test/zz_generated.deepcopy.go @@ -0,0 +1,94 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package test + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *List) DeepCopyInto(out *List) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.Object, len(*in)) + for i := range *in { + if (*in)[i] != nil { + (*out)[i] = (*in)[i].DeepCopyObject() + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List. +func (in *List) DeepCopy() *List { + if in == nil { + return nil + } + out := new(List) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *List) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ListV1) DeepCopyInto(out *ListV1) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.RawExtension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListV1. +func (in *ListV1) DeepCopy() *ListV1 { + if in == nil { + return nil + } + out := new(ListV1) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ListV1) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/doc.go new file mode 100644 index 0000000000..783cbcdc8d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 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 types implements various generic types used throughout kubernetes. +package types diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/namespacedname.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/namespacedname.go new file mode 100644 index 0000000000..db18ce1ce2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/namespacedname.go @@ -0,0 +1,50 @@ +/* +Copyright 2015 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 types + +// NamespacedName comprises a resource name, with a mandatory namespace, +// rendered as "/". Being a type captures intent and +// helps make sure that UIDs, namespaced names and non-namespaced names +// do not get conflated in code. For most use cases, namespace and name +// will already have been format validated at the API entry point, so we +// don't do that here. Where that's not the case (e.g. in testing), +// consider using NamespacedNameOrDie() in testing.go in this package. + +type NamespacedName struct { + Namespace string + Name string +} + +const ( + Separator = '/' +) + +// String returns the general purpose string representation +func (n NamespacedName) String() string { + return n.Namespace + string(Separator) + n.Name +} + +// MarshalLog emits a struct containing required key/value pair +func (n NamespacedName) MarshalLog() interface{} { + return struct { + Name string `json:"name"` + Namespace string `json:"namespace,omitempty"` + }{ + Name: n.Name, + Namespace: n.Namespace, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/nodename.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/nodename.go new file mode 100644 index 0000000000..cff9ca6717 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/nodename.go @@ -0,0 +1,43 @@ +/* +Copyright 2015 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 types + +// NodeName is a type that holds a api.Node's Name identifier. +// Being a type captures intent and helps make sure that the node name +// is not confused with similar concepts (the hostname, the cloud provider id, +// the cloud provider name etc) +// +// To clarify the various types: +// +// - Node.Name is the Name field of the Node in the API. This should be stored in a NodeName. +// Unfortunately, because Name is part of ObjectMeta, we can't store it as a NodeName at the API level. +// +// - Hostname is the hostname of the local machine (from uname -n). +// However, some components allow the user to pass in a --hostname-override flag, +// which will override this in most places. In the absence of anything more meaningful, +// kubelet will use Hostname as the Node.Name when it creates the Node. +// +// * The cloudproviders have the own names: GCE has InstanceName, AWS has InstanceId. +// +// For GCE, InstanceName is the Name of an Instance object in the GCE API. On GCE, Instance.Name becomes the +// Hostname, and thus it makes sense also to use it as the Node.Name. But that is GCE specific, and it is up +// to the cloudprovider how to do this mapping. +// +// For AWS, the InstanceID is not yet suitable for use as a Node.Name, so we actually use the +// PrivateDnsName for the Node.Name. And this is _not_ always the same as the hostname: if +// we are using a custom DHCP domain it won't be. +type NodeName string diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/patch.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/patch.go new file mode 100644 index 0000000000..d338cf213d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/patch.go @@ -0,0 +1,31 @@ +/* +Copyright 2015 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 types + +// Similarly to above, these are constants to support HTTP PATCH utilized by +// both the client and server that didn't make sense for a whole package to be +// dedicated to. +type PatchType string + +const ( + JSONPatchType PatchType = "application/json-patch+json" + MergePatchType PatchType = "application/merge-patch+json" + StrategicMergePatchType PatchType = "application/strategic-merge-patch+json" + ApplyPatchType PatchType = ApplyYAMLPatchType + ApplyYAMLPatchType PatchType = "application/apply-patch+yaml" + ApplyCBORPatchType PatchType = "application/apply-patch+cbor" +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/uid.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/uid.go new file mode 100644 index 0000000000..869339222e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/types/uid.go @@ -0,0 +1,22 @@ +/* +Copyright 2015 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 types + +// UID is a type that holds unique ID values, including UUIDs. Because we +// don't ONLY use UUIDs, this is an alias to string. Being a type captures +// intent and helps make sure that UIDs and names do not get conflated. +type UID string diff --git a/vendor/k8s.io/client-go/util/retry/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/OWNERS similarity index 64% rename from vendor/k8s.io/client-go/util/retry/OWNERS rename to hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/OWNERS index 75736b5aac..e610094242 100644 --- a/vendor/k8s.io/client-go/util/retry/OWNERS +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/OWNERS @@ -1,4 +1,4 @@ # See the OWNERS docs at https://go.k8s.io/owners -reviewers: - - caesarxuchao +approvers: + - apelisse diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/expiring.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/expiring.go new file mode 100644 index 0000000000..1396274c7b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/expiring.go @@ -0,0 +1,202 @@ +/* +Copyright 2019 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 cache + +import ( + "container/heap" + "sync" + "time" + + "k8s.io/utils/clock" +) + +// NewExpiring returns an initialized expiring cache. +func NewExpiring() *Expiring { + return NewExpiringWithClock(clock.RealClock{}) +} + +// NewExpiringWithClock is like NewExpiring but allows passing in a custom +// clock for testing. +func NewExpiringWithClock(clock clock.Clock) *Expiring { + return &Expiring{ + clock: clock, + cache: make(map[interface{}]entry), + } +} + +// Expiring is a map whose entries expire after a per-entry timeout. +type Expiring struct { + // AllowExpiredGet causes the expiration check to be skipped on Get. + // It should only be used when a key always corresponds to the exact same value. + // Thus when this field is true, expired keys are considered valid + // until the next call to Set (which causes the GC to run). + // It may not be changed concurrently with calls to Get. + AllowExpiredGet bool + + clock clock.Clock + + // mu protects the below fields + mu sync.RWMutex + // cache is the internal map that backs the cache. + cache map[interface{}]entry + // generation is used as a cheap resource version for cache entries. Cleanups + // are scheduled with a key and generation. When the cleanup runs, it first + // compares its generation with the current generation of the entry. It + // deletes the entry iff the generation matches. This prevents cleanups + // scheduled for earlier versions of an entry from deleting later versions of + // an entry when Set() is called multiple times with the same key. + // + // The integer value of the generation of an entry is meaningless. + generation uint64 + + heap expiringHeap +} + +type entry struct { + val interface{} + expiry time.Time + generation uint64 +} + +// Get looks up an entry in the cache. +func (c *Expiring) Get(key interface{}) (val interface{}, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() + e, ok := c.cache[key] + if !ok { + return nil, false + } + if !c.AllowExpiredGet && !c.clock.Now().Before(e.expiry) { + return nil, false + } + return e.val, true +} + +// Set sets a key/value/expiry entry in the map, overwriting any previous entry +// with the same key. The entry expires at the given expiry time, but its TTL +// may be lengthened or shortened by additional calls to Set(). Garbage +// collection of expired entries occurs during calls to Set(), however calls to +// Get() will not return expired entries that have not yet been garbage +// collected. +func (c *Expiring) Set(key interface{}, val interface{}, ttl time.Duration) { + now := c.clock.Now() + expiry := now.Add(ttl) + + c.mu.Lock() + defer c.mu.Unlock() + + c.generation++ + + c.cache[key] = entry{ + val: val, + expiry: expiry, + generation: c.generation, + } + + // Run GC inline before pushing the new entry. + c.gc(now) + + heap.Push(&c.heap, &expiringHeapEntry{ + key: key, + expiry: expiry, + generation: c.generation, + }) +} + +// Delete deletes an entry in the map. +func (c *Expiring) Delete(key interface{}) { + c.mu.Lock() + defer c.mu.Unlock() + c.del(key, 0) +} + +// del deletes the entry for the given key. The generation argument is the +// generation of the entry that should be deleted. If the generation has been +// changed (e.g. if a set has occurred on an existing element but the old +// cleanup still runs), this is a noop. If the generation argument is 0, the +// entry's generation is ignored and the entry is deleted. +// +// del must be called under the write lock. +func (c *Expiring) del(key interface{}, generation uint64) { + e, ok := c.cache[key] + if !ok { + return + } + if generation != 0 && generation != e.generation { + return + } + delete(c.cache, key) +} + +// Len returns the number of items in the cache. +func (c *Expiring) Len() int { + c.mu.RLock() + defer c.mu.RUnlock() + return len(c.cache) +} + +func (c *Expiring) gc(now time.Time) { + for { + // Return from gc if the heap is empty or the next element is not yet + // expired. + // + // heap[0] is a peek at the next element in the heap, which is not obvious + // from looking at the (*expiringHeap).Pop() implementation below. + // heap.Pop() swaps the first entry with the last entry of the heap, then + // calls (*expiringHeap).Pop() which returns the last element. + if len(c.heap) == 0 || now.Before(c.heap[0].expiry) { + return + } + cleanup := heap.Pop(&c.heap).(*expiringHeapEntry) + c.del(cleanup.key, cleanup.generation) + } +} + +type expiringHeapEntry struct { + key interface{} + expiry time.Time + generation uint64 +} + +// expiringHeap is a min-heap ordered by expiration time of its entries. The +// expiring cache uses this as a priority queue to efficiently organize entries +// which will be garbage collected once they expire. +type expiringHeap []*expiringHeapEntry + +var _ heap.Interface = &expiringHeap{} + +func (cq expiringHeap) Len() int { + return len(cq) +} + +func (cq expiringHeap) Less(i, j int) bool { + return cq[i].expiry.Before(cq[j].expiry) +} + +func (cq expiringHeap) Swap(i, j int) { + cq[i], cq[j] = cq[j], cq[i] +} + +func (cq *expiringHeap) Push(c interface{}) { + *cq = append(*cq, c.(*expiringHeapEntry)) +} + +func (cq *expiringHeap) Pop() interface{} { + c := (*cq)[cq.Len()-1] + *cq = (*cq)[:cq.Len()-1] + return c +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/expiring_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/expiring_test.go new file mode 100644 index 0000000000..95e803308b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/expiring_test.go @@ -0,0 +1,321 @@ +/* +Copyright 2019 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 cache + +import ( + "context" + "math/rand" + "sync" + "testing" + "time" + + "github.com/google/uuid" + + testingclock "k8s.io/utils/clock/testing" +) + +func TestExpiringCache(t *testing.T) { + cache := NewExpiring() + + if result, ok := cache.Get("foo"); ok || result != nil { + t.Errorf("Expected null, false, got %#v, %v", result, ok) + } + + record1 := "bob" + record2 := "alice" + + // when empty, record is stored + cache.Set("foo", record1, time.Hour) + if result, ok := cache.Get("foo"); !ok || result != record1 { + t.Errorf("Expected %#v, true, got %#v, %v", record1, result, ok) + } + + // newer record overrides + cache.Set("foo", record2, time.Hour) + if result, ok := cache.Get("foo"); !ok || result != record2 { + t.Errorf("Expected %#v, true, got %#v, %v", record2, result, ok) + } + + // delete the current value + cache.Delete("foo") + if result, ok := cache.Get("foo"); ok || result != nil { + t.Errorf("Expected null, false, got %#v, %v", result, ok) + } +} + +func TestExpiration(t *testing.T) { + fc := &testingclock.FakeClock{} + c := NewExpiringWithClock(fc) + + c.Set("a", "a", time.Second) + + fc.Step(500 * time.Millisecond) + if _, ok := c.Get("a"); !ok { + t.Fatalf("we should have found a key") + } + + fc.Step(time.Second) + if _, ok := c.Get("a"); ok { + t.Fatalf("we should not have found a key") + } + + c.Set("a", "a", time.Second) + + fc.Step(500 * time.Millisecond) + if _, ok := c.Get("a"); !ok { + t.Fatalf("we should have found a key") + } + + // reset should restart the ttl + c.Set("a", "a", time.Second) + + fc.Step(750 * time.Millisecond) + if _, ok := c.Get("a"); !ok { + t.Fatalf("we should have found a key") + } + + // Simulate a race between a reset and cleanup. Assert that del doesn't + // remove the key. + c.Set("a", "a", time.Second) + + e := c.cache["a"] + e.generation++ + e.expiry = e.expiry.Add(1 * time.Second) + c.cache["a"] = e + + fc.Step(1 * time.Second) + if _, ok := c.Get("a"); !ok { + t.Fatalf("we should have found a key") + } + + // Check getting an expired key with and without AllowExpiredGet + c.Set("b", "b", time.Second) + fc.Step(2 * time.Second) + if _, ok := c.Get("b"); ok { + t.Fatalf("we should not have found b key") + } + if count := c.Len(); count != 2 { // b is still in the cache + t.Errorf("expected two items got: %d", count) + } + c.AllowExpiredGet = true + if _, ok := c.Get("b"); !ok { + t.Fatalf("we should have found b key") + } + if count := c.Len(); count != 2 { // b is still in the cache + t.Errorf("expected two items got: %d", count) + } + c.Set("c", "c", time.Second) // set some unrelated key to run gc + if count := c.Len(); count != 2 { // only a and c in the cache now + t.Errorf("expected two items got: %d", count) + } + if _, ok := c.Get("b"); ok { + t.Fatalf("we should not have found b key") + } + if _, ok := c.Get("a"); !ok { + t.Fatalf("we should have found a key") + } + if _, ok := c.Get("c"); !ok { + t.Fatalf("we should have found c key") + } +} + +func TestGarbageCollection(t *testing.T) { + fc := &testingclock.FakeClock{} + + type entry struct { + key, val string + ttl time.Duration + } + + tests := []struct { + name string + now time.Time + set []entry + want map[string]string + }{ + { + name: "two entries just set", + now: fc.Now().Add(0 * time.Second), + set: []entry{ + {"a", "aa", 1 * time.Second}, + {"b", "bb", 2 * time.Second}, + }, + want: map[string]string{ + "a": "aa", + "b": "bb", + }, + }, + { + name: "first entry expired now", + now: fc.Now().Add(1 * time.Second), + set: []entry{ + {"a", "aa", 1 * time.Second}, + {"b", "bb", 2 * time.Second}, + }, + want: map[string]string{ + "b": "bb", + }, + }, + { + name: "first entry expired half a second ago", + now: fc.Now().Add(1500 * time.Millisecond), + set: []entry{ + {"a", "aa", 1 * time.Second}, + {"b", "bb", 2 * time.Second}, + }, + want: map[string]string{ + "b": "bb", + }, + }, + { + name: "three entries weird order", + now: fc.Now().Add(1 * time.Second), + set: []entry{ + {"c", "cc", 3 * time.Second}, + {"a", "aa", 1 * time.Second}, + {"b", "bb", 2 * time.Second}, + }, + want: map[string]string{ + "b": "bb", + "c": "cc", + }, + }, + { + name: "expire multiple entries in one cycle", + now: fc.Now().Add(2500 * time.Millisecond), + set: []entry{ + {"a", "aa", 1 * time.Second}, + {"b", "bb", 2 * time.Second}, + {"c", "cc", 3 * time.Second}, + }, + want: map[string]string{ + "c": "cc", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + c := NewExpiringWithClock(fc) + for _, e := range test.set { + c.Set(e.key, e.val, e.ttl) + } + + c.gc(test.now) + + for k, want := range test.want { + got, ok := c.Get(k) + if !ok { + t.Errorf("expected cache to have entry for key=%q but found none", k) + continue + } + if got != want { + t.Errorf("unexpected value for key=%q: got=%q, want=%q", k, got, want) + } + } + if got, want := c.Len(), len(test.want); got != want { + t.Errorf("unexpected cache size: got=%d, want=%d", got, want) + } + }) + } +} + +func BenchmarkExpiringCacheContention(b *testing.B) { + b.Run("evict_probablility=100%", func(b *testing.B) { + benchmarkExpiringCacheContention(b, 1) + }) + b.Run("evict_probablility=10%", func(b *testing.B) { + benchmarkExpiringCacheContention(b, 0.1) + }) + b.Run("evict_probablility=1%", func(b *testing.B) { + benchmarkExpiringCacheContention(b, 0.01) + }) +} + +func benchmarkExpiringCacheContention(b *testing.B, prob float64) { + const numKeys = 1 << 16 + cache := NewExpiring() + + keys := []string{} + for i := 0; i < numKeys; i++ { + key := uuid.New().String() + keys = append(keys, key) + } + + b.ResetTimer() + + b.SetParallelism(256) + b.RunParallel(func(pb *testing.PB) { + rand := rand.New(rand.NewSource(rand.Int63())) + for pb.Next() { + i := rand.Int31() + key := keys[i%numKeys] + _, ok := cache.Get(key) + if ok { + // compare lower bits of sampled i to decide whether we should evict. + if rand.Float64() < prob { + cache.Delete(key) + } + } else { + cache.Set(key, struct{}{}, 50*time.Millisecond) + } + } + }) +} + +func TestStressExpiringCache(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + const numKeys = 1 << 16 + cache := NewExpiring() + + keys := []string{} + for i := 0; i < numKeys; i++ { + key := uuid.New().String() + keys = append(keys, key) + } + + var wg sync.WaitGroup + for i := 0; i < 256; i++ { + wg.Add(1) + go func() { + defer wg.Done() + rand := rand.New(rand.NewSource(rand.Int63())) + for { + select { + case <-ctx.Done(): + return + default: + } + key := keys[rand.Intn(numKeys)] + if _, ok := cache.Get(key); !ok { + cache.Set(key, struct{}{}, 50*time.Millisecond) + } + } + }() + } + + wg.Wait() + + // trigger a GC with a set and check the cache size. + time.Sleep(60 * time.Millisecond) + cache.Set("trigger", "gc", time.Second) + if cache.Len() != 1 { + t.Errorf("unexpected cache size: got=%d, want=1", cache.Len()) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/lruexpirecache.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/lruexpirecache.go new file mode 100644 index 0000000000..ad486d580f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/lruexpirecache.go @@ -0,0 +1,173 @@ +/* +Copyright 2016 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 cache + +import ( + "container/list" + "sync" + "time" +) + +// Clock defines an interface for obtaining the current time +type Clock interface { + Now() time.Time +} + +// realClock implements the Clock interface by calling time.Now() +type realClock struct{} + +func (realClock) Now() time.Time { return time.Now() } + +// LRUExpireCache is a cache that ensures the mostly recently accessed keys are returned with +// a ttl beyond which keys are forcibly expired. +type LRUExpireCache struct { + // clock is used to obtain the current time + clock Clock + + lock sync.Mutex + + maxSize int + evictionList list.List + entries map[interface{}]*list.Element +} + +// NewLRUExpireCache creates an expiring cache with the given size +func NewLRUExpireCache(maxSize int) *LRUExpireCache { + return NewLRUExpireCacheWithClock(maxSize, realClock{}) +} + +// NewLRUExpireCacheWithClock creates an expiring cache with the given size, using the specified clock to obtain the current time. +func NewLRUExpireCacheWithClock(maxSize int, clock Clock) *LRUExpireCache { + if maxSize <= 0 { + panic("maxSize must be > 0") + } + + return &LRUExpireCache{ + clock: clock, + maxSize: maxSize, + entries: map[interface{}]*list.Element{}, + } +} + +type cacheEntry struct { + key interface{} + value interface{} + expireTime time.Time +} + +// Add adds the value to the cache at key with the specified maximum duration. +func (c *LRUExpireCache) Add(key interface{}, value interface{}, ttl time.Duration) { + c.lock.Lock() + defer c.lock.Unlock() + + // Key already exists + oldElement, ok := c.entries[key] + if ok { + c.evictionList.MoveToFront(oldElement) + oldElement.Value.(*cacheEntry).value = value + oldElement.Value.(*cacheEntry).expireTime = c.clock.Now().Add(ttl) + return + } + + // Make space if necessary + if c.evictionList.Len() >= c.maxSize { + toEvict := c.evictionList.Back() + c.evictionList.Remove(toEvict) + delete(c.entries, toEvict.Value.(*cacheEntry).key) + } + + // Add new entry + entry := &cacheEntry{ + key: key, + value: value, + expireTime: c.clock.Now().Add(ttl), + } + element := c.evictionList.PushFront(entry) + c.entries[key] = element +} + +// Get returns the value at the specified key from the cache if it exists and is not +// expired, or returns false. +func (c *LRUExpireCache) Get(key interface{}) (interface{}, bool) { + c.lock.Lock() + defer c.lock.Unlock() + + element, ok := c.entries[key] + if !ok { + return nil, false + } + + if c.clock.Now().After(element.Value.(*cacheEntry).expireTime) { + c.evictionList.Remove(element) + delete(c.entries, key) + return nil, false + } + + c.evictionList.MoveToFront(element) + + return element.Value.(*cacheEntry).value, true +} + +// Remove removes the specified key from the cache if it exists +func (c *LRUExpireCache) Remove(key interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + + element, ok := c.entries[key] + if !ok { + return + } + + c.evictionList.Remove(element) + delete(c.entries, key) +} + +// RemoveAll removes all keys that match predicate. +func (c *LRUExpireCache) RemoveAll(predicate func(key any) bool) { + c.lock.Lock() + defer c.lock.Unlock() + + for key, element := range c.entries { + if predicate(key) { + c.evictionList.Remove(element) + delete(c.entries, key) + } + } +} + +// Keys returns all unexpired keys in the cache. +// +// Keep in mind that subsequent calls to Get() for any of the returned keys +// might return "not found". +// +// Keys are returned ordered from least recently used to most recently used. +func (c *LRUExpireCache) Keys() []interface{} { + c.lock.Lock() + defer c.lock.Unlock() + + now := c.clock.Now() + + val := make([]interface{}, 0, c.evictionList.Len()) + for element := c.evictionList.Back(); element != nil; element = element.Prev() { + // Only return unexpired keys + if !now.After(element.Value.(*cacheEntry).expireTime) { + val = append(val, element.Value.(*cacheEntry).key) + } + } + + return val +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/lruexpirecache_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/lruexpirecache_test.go new file mode 100644 index 0000000000..d6bd7464f7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/cache/lruexpirecache_test.go @@ -0,0 +1,162 @@ +/* +Copyright 2016 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 cache + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + testingclock "k8s.io/utils/clock/testing" +) + +func expectEntry(t *testing.T, c *LRUExpireCache, key interface{}, value interface{}) { + t.Helper() + result, ok := c.Get(key) + if !ok || result != value { + t.Errorf("Expected cache[%v]: %v, got %v", key, value, result) + } +} + +func expectNotEntry(t *testing.T, c *LRUExpireCache, key interface{}) { + t.Helper() + if result, ok := c.Get(key); ok { + t.Errorf("Expected cache[%v] to be empty, got %v", key, result) + } +} + +// Note: Check keys before checking individual entries, because Get() changes +// the eviction list. +func assertKeys(t *testing.T, gotKeys, wantKeys []interface{}) { + t.Helper() + if diff := cmp.Diff(gotKeys, wantKeys); diff != "" { + t.Errorf("Wrong result for keys: diff (-got +want):\n%s", diff) + } +} + +func TestSimpleGet(t *testing.T) { + c := NewLRUExpireCache(10) + c.Add("long-lived", "12345", 10*time.Hour) + + assertKeys(t, c.Keys(), []interface{}{"long-lived"}) + + expectEntry(t, c, "long-lived", "12345") +} + +func TestSimpleRemove(t *testing.T) { + c := NewLRUExpireCache(10) + c.Add("long-lived", "12345", 10*time.Hour) + c.Remove("long-lived") + + assertKeys(t, c.Keys(), []interface{}{}) + + expectNotEntry(t, c, "long-lived") +} + +func TestSimpleRemoveAll(t *testing.T) { + c := NewLRUExpireCache(10) + c.Add("long-lived", "12345", 10*time.Hour) + c.Add("other-long-lived", "12345", 10*time.Hour) + c.RemoveAll(func(k any) bool { + return k.(string) == "long-lived" + }) + + assertKeys(t, c.Keys(), []any{"other-long-lived"}) + + expectNotEntry(t, c, "long-lived") + expectEntry(t, c, "other-long-lived", "12345") +} + +func TestExpiredGet(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Now()) + c := NewLRUExpireCacheWithClock(10, fakeClock) + c.Add("short-lived", "12345", 1*time.Millisecond) + // ensure the entry expired + fakeClock.Step(2 * time.Millisecond) + + // Keys() should not return expired keys. + assertKeys(t, c.Keys(), []interface{}{}) + + expectNotEntry(t, c, "short-lived") +} + +func TestLRUOverflow(t *testing.T) { + c := NewLRUExpireCache(4) + c.Add("elem1", "1", 10*time.Hour) + c.Add("elem2", "2", 10*time.Hour) + c.Add("elem3", "3", 10*time.Hour) + c.Add("elem4", "4", 10*time.Hour) + c.Add("elem5", "5", 10*time.Hour) + + assertKeys(t, c.Keys(), []interface{}{"elem2", "elem3", "elem4", "elem5"}) + + expectNotEntry(t, c, "elem1") + expectEntry(t, c, "elem2", "2") + expectEntry(t, c, "elem3", "3") + expectEntry(t, c, "elem4", "4") + expectEntry(t, c, "elem5", "5") +} + +func TestAddBringsToFront(t *testing.T) { + c := NewLRUExpireCache(4) + c.Add("elem1", "1", 10*time.Hour) + c.Add("elem2", "2", 10*time.Hour) + c.Add("elem3", "3", 10*time.Hour) + c.Add("elem4", "4", 10*time.Hour) + + c.Add("elem1", "1-new", 10*time.Hour) + + c.Add("elem5", "5", 10*time.Hour) + + assertKeys(t, c.Keys(), []interface{}{"elem3", "elem4", "elem1", "elem5"}) + + expectNotEntry(t, c, "elem2") + expectEntry(t, c, "elem1", "1-new") + expectEntry(t, c, "elem3", "3") + expectEntry(t, c, "elem4", "4") + expectEntry(t, c, "elem5", "5") +} + +func TestGetBringsToFront(t *testing.T) { + c := NewLRUExpireCache(4) + c.Add("elem1", "1", 10*time.Hour) + c.Add("elem2", "2", 10*time.Hour) + c.Add("elem3", "3", 10*time.Hour) + c.Add("elem4", "4", 10*time.Hour) + + c.Get("elem1") + + c.Add("elem5", "5", 10*time.Hour) + + assertKeys(t, c.Keys(), []interface{}{"elem3", "elem4", "elem1", "elem5"}) + + expectNotEntry(t, c, "elem2") + expectEntry(t, c, "elem1", "1") + expectEntry(t, c, "elem3", "3") + expectEntry(t, c, "elem4", "4") + expectEntry(t, c, "elem5", "5") +} + +func TestLRUKeys(t *testing.T) { + c := NewLRUExpireCache(4) + c.Add("elem1", "1", 10*time.Hour) + c.Add("elem2", "2", 10*time.Hour) + c.Add("elem3", "3", 10*time.Hour) + c.Add("elem4", "4", 10*time.Hour) + + assertKeys(t, c.Keys(), []interface{}{"elem1", "elem2", "elem3", "elem4"}) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/complex_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/complex_test.go new file mode 100644 index 0000000000..18ac9336e2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/complex_test.go @@ -0,0 +1,353 @@ +/* +Copyright 2025 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 diff + +import ( + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" +) + +// TestDiffWithRealGoCmp is a comprehensive test that compares our Diff with the actual go-cmp Diff +// across a variety of data structures and types to ensure compatibility. +func TestDiffWithRealGoCmp(t *testing.T) { + // Test with simple types + t.Run("SimpleTypes", func(t *testing.T) { + testCases := []struct { + name string + a interface{} + b interface{} + }{ + {name: "Integers", a: 42, b: 43}, + {name: "Strings", a: "hello", b: "world"}, + {name: "Booleans", a: true, b: false}, + {name: "Floats", a: 3.14, b: 2.71}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ourDiff := Diff(tc.a, tc.b) + goCmpDiff := cmp.Diff(tc.a, tc.b) + + t.Logf("Our diff:\n%s\n\nGo-cmp diff:\n%s", ourDiff, goCmpDiff) + + // Verify both diffs are non-empty + if ourDiff == "" || goCmpDiff == "" { + t.Errorf("Expected non-empty diffs, got ourDiff: %v, goCmpDiff: %v", + ourDiff == "", goCmpDiff == "") + } + }) + } + }) + + // Test with a simple struct + t.Run("SimpleStruct", func(t *testing.T) { + type TestStruct struct { + Name string + Age int + Tags []string + Map map[string]int + } + + a := TestStruct{ + Name: "Alice", + Age: 30, + Tags: []string{"tag1", "tag2"}, + Map: map[string]int{"a": 1, "b": 2}, + } + + b := TestStruct{ + Name: "Bob", + Age: 25, + Tags: []string{"tag1", "tag3"}, + Map: map[string]int{"a": 1, "c": 3}, + } + + ourDiff := Diff(a, b) + goCmpDiff := cmp.Diff(a, b) + + t.Logf("Our diff:\n%s\n\nGo-cmp diff:\n%s", ourDiff, goCmpDiff) + + // Check that our diff contains key differences + keyDifferences := []string{ + "Name", "Alice", "Bob", + "Age", "30", "25", + "Tags", "tag2", "tag3", + "Map", "b", "c", + } + + for _, key := range keyDifferences { + if !strings.Contains(ourDiff, key) { + t.Errorf("Our diff doesn't contain expected key difference: %q", key) + } + } + }) + + // Test with a complex nested struct + t.Run("ComplexNestedStruct", func(t *testing.T) { + type Address struct { + Street string + City string + State string + PostalCode string + Country string + } + + type Contact struct { + Type string + Value string + } + + type Person struct { + ID int + FirstName string + LastName string + Age int + Addresses map[string]Address + Contacts []Contact + Metadata map[string]interface{} + CreatedAt time.Time + } + + now := time.Now() + later := now.Add(24 * time.Hour) + + person1 := Person{ + ID: 1, + FirstName: "John", + LastName: "Doe", + Age: 30, + Addresses: map[string]Address{ + "home": { + Street: "123 Main St", + City: "Anytown", + State: "CA", + PostalCode: "12345", + Country: "USA", + }, + "work": { + Street: "456 Market St", + City: "Worktown", + State: "CA", + PostalCode: "54321", + Country: "USA", + }, + }, + Contacts: []Contact{ + {Type: "email", Value: "john.doe@example.com"}, + {Type: "phone", Value: "555-1234"}, + }, + Metadata: map[string]interface{}{ + "created": "2023-01-01", + "loginCount": 42, + "settings": map[string]bool{ + "notifications": true, + "darkMode": false, + }, + }, + CreatedAt: now, + } + + person2 := Person{ + ID: 1, + FirstName: "John", + LastName: "Smith", // Different + Age: 31, // Different + Addresses: map[string]Address{ + "home": { + Street: "123 Main St", + City: "Anytown", + State: "CA", + PostalCode: "12345", + Country: "USA", + }, + // "work" address is missing + }, + Contacts: []Contact{ + {Type: "email", Value: "john.smith@example.com"}, // Different + {Type: "phone", Value: "555-1234"}, + {Type: "fax", Value: "555-5678"}, // Additional + }, + Metadata: map[string]interface{}{ + "created": "2023-01-01", + "loginCount": 43, // Different + "settings": map[string]bool{ + "notifications": false, // Different + "darkMode": true, // Different + }, + "newField": "new value", // New field + }, + CreatedAt: later, // Different + } + + ourDiff := Diff(person1, person2) + goCmpDiff := cmp.Diff(person1, person2) + + t.Logf("Our diff:\n%s\n\nGo-cmp diff:\n%s", ourDiff, goCmpDiff) + + // Check that our diff contains key differences + keyDifferences := []string{ + "LastName", "Smith", + "Age", + "Addresses", "work", + "Contacts", "john.smith@example.com", "fax", + "Metadata", "loginCount", "settings", "notifications", "darkMode", "newField", + } + + for _, key := range keyDifferences { + if !strings.Contains(ourDiff, key) { + t.Errorf("Our diff doesn't contain expected key difference: %q", key) + } + } + + // Check that both diffs are non-empty + if ourDiff == "" || goCmpDiff == "" { + t.Errorf("Expected non-empty diffs, got ourDiff: %v, goCmpDiff: %v", + ourDiff == "", goCmpDiff == "") + } + }) + + // Test with slices and maps + t.Run("SlicesAndMaps", func(t *testing.T) { + // Test with slices + a1 := []int{1, 2, 3, 4, 5} + b1 := []int{1, 2, 6, 4, 7} + + ourDiff1 := Diff(a1, b1) + goCmpDiff1 := cmp.Diff(a1, b1) + + t.Logf("Our diff (slices):\n%s\n\nGo-cmp diff (slices):\n%s", ourDiff1, goCmpDiff1) + + // Check that our diff contains the differences + if !strings.Contains(ourDiff1, "3") || !strings.Contains(ourDiff1, "6") || + !strings.Contains(ourDiff1, "5") || !strings.Contains(ourDiff1, "7") { + t.Errorf("Our diff doesn't contain all expected differences for slices") + } + + // Test with maps + a2 := map[string]int{"a": 1, "b": 2, "c": 3} + b2 := map[string]int{"a": 1, "b": 5, "d": 4} + + ourDiff2 := Diff(a2, b2) + goCmpDiff2 := cmp.Diff(a2, b2) + + t.Logf("Our diff (maps):\n%s\n\nGo-cmp diff (maps):\n%s", ourDiff2, goCmpDiff2) + + // Check that our diff contains the differences + if !strings.Contains(ourDiff2, "b") || !strings.Contains(ourDiff2, "5") || + !strings.Contains(ourDiff2, "c") || !strings.Contains(ourDiff2, "d") { + t.Errorf("Our diff doesn't contain all expected differences for maps") + } + }) + + // Test with unexported fields + t.Run("UnexportedFields", func(t *testing.T) { + type WithUnexported struct { + Exported int + unexported int + } + + a := WithUnexported{Exported: 1, unexported: 2} + b := WithUnexported{Exported: 3, unexported: 4} + + ourDiff := Diff(a, b) + // Use cmpopts.IgnoreUnexported to ignore unexported fields in go-cmp + goCmpDiff := cmp.Diff(a, b, cmpopts.IgnoreUnexported(WithUnexported{})) + + t.Logf("Our diff (unexported):\n%s\n\nGo-cmp diff (unexported):\n%s", ourDiff, goCmpDiff) + + // Check that our diff contains only the exported field difference + if !strings.Contains(ourDiff, "Exported") || !strings.Contains(ourDiff, "1") || !strings.Contains(ourDiff, "3") { + t.Errorf("Our diff doesn't contain the exported field difference") + } + }) + + // Test with embedded structs + t.Run("EmbeddedStructs", func(t *testing.T) { + type Embedded struct { + Value int + } + + type Container struct { + Embedded + Extra string + } + + a := Container{Embedded: Embedded{Value: 1}, Extra: "a"} + b := Container{Embedded: Embedded{Value: 2}, Extra: "b"} + + ourDiff := Diff(a, b) + goCmpDiff := cmp.Diff(a, b) + + t.Logf("Our diff (embedded):\n%s\n\nGo-cmp diff (embedded):\n%s", ourDiff, goCmpDiff) + + // Check that our diff contains the container field difference + if !strings.Contains(ourDiff, "Extra") || !strings.Contains(ourDiff, "a") || !strings.Contains(ourDiff, "b") { + t.Errorf("Our diff doesn't contain the container field difference") + } + }) + + // Test with interface values of same type + t.Run("InterfaceValues", func(t *testing.T) { + type Container struct { + Value interface{} + } + + // Test with same type in interface + c := Container{Value: 42} + d := Container{Value: 43} + + ourDiff := Diff(c, d) + goCmpDiff := cmp.Diff(c, d) + + t.Logf("Our diff (interface same type):\n%s\n\nGo-cmp diff (interface same type):\n%s", ourDiff, goCmpDiff) + + // Check that our diff contains the value difference + if !strings.Contains(ourDiff, "42") || !strings.Contains(ourDiff, "43") { + t.Errorf("Our diff doesn't contain the value difference for interface values of same type") + } + }) + + // Test with objects that cannot be marshaled to JSON + t.Run("UnmarshalableObjects", func(t *testing.T) { + // Test with a circular reference, which cannot be marshaled to JSON + type Node struct { + Value int + Next *Node + } + + // Create a circular reference + nodeA := &Node{Value: 1} + nodeA.Next = nodeA // Points to itself + + nodeB := &Node{Value: 2} + nodeB.Next = nodeB // Points to itself + + // This should fall back to using dump.Pretty + circularDiff := Diff(nodeA, nodeB) + + t.Logf("Diff for circular references:\n%s", circularDiff) + + // Verify the diff contains the values + if !strings.Contains(circularDiff, "1") || !strings.Contains(circularDiff, "2") { + t.Errorf("Diff doesn't contain expected value differences for circular references") + } + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/diff.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/diff.go new file mode 100644 index 0000000000..7bfa6088bc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/diff.go @@ -0,0 +1,59 @@ +/* +Copyright 2025 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 diff + +import ( + "encoding/json" + "fmt" + + "github.com/pmezard/go-difflib/difflib" + + "k8s.io/utils/dump" +) + +// Diff returns a string representation of the difference between two objects. +// When built without the usegocmp tag, it uses go-difflib/difflib to generate a +// unified diff of the objects. It attempts to use JSON serialization first, +// falling back to an object dump via the dump package if JSON marshaling fails. +func Diff(a, b any) string { + + aStr, aErr := toPrettyJSON(a) + bStr, bErr := toPrettyJSON(b) + if aErr != nil || bErr != nil { + aStr = dump.Pretty(a) + bStr = dump.Pretty(b) + } + + diff := difflib.UnifiedDiff{ + A: difflib.SplitLines(aStr), + B: difflib.SplitLines(bStr), + Context: 3, + } + + diffstr, err := difflib.GetUnifiedDiffString(diff) + if err != nil { + return fmt.Sprintf("error generating diff: %v", err) + } + + return diffstr +} + +// toPrettyJSON converts an object to a pretty-printed JSON string. +func toPrettyJSON(data any) (string, error) { + jsonData, err := json.MarshalIndent(data, "", " ") + return string(jsonData), err +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go new file mode 100644 index 0000000000..9f0dc2f6ad --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/diff/legacy_diff.go @@ -0,0 +1,67 @@ +/* +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 diff + +import ( + "bytes" + "fmt" + "strings" + "text/tabwriter" + + "k8s.io/utils/dump" +) + +// ObjectGoPrintSideBySide prints a and b as textual dumps side by side, +// enabling easy visual scanning for mismatches. +func ObjectGoPrintSideBySide(a, b interface{}) string { + sA := dump.Pretty(a) + sB := dump.Pretty(b) + + linesA := strings.Split(sA, "\n") + linesB := strings.Split(sB, "\n") + width := 0 + for _, s := range linesA { + l := len(s) + if l > width { + width = l + } + } + for _, s := range linesB { + l := len(s) + if l > width { + width = l + } + } + buf := &bytes.Buffer{} + w := tabwriter.NewWriter(buf, width, 0, 1, ' ', 0) + max := len(linesA) + if len(linesB) > max { + max = len(linesB) + } + for i := 0; i < max; i++ { + var a, b string + if i < len(linesA) { + a = linesA[i] + } + if i < len(linesB) { + b = linesB[i] + } + _, _ = fmt.Fprintf(w, "%s\t%s\n", a, b) + } + _ = w.Flush() + return buf.String() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/dump/dump.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/dump/dump.go new file mode 100644 index 0000000000..5cf55473c3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/dump/dump.go @@ -0,0 +1,42 @@ +/* +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 dump + +import ( + "k8s.io/utils/dump" +) + +// Deprecated: Use k8s.io/utils/dump.Pretty instead. +// +//go:fix inline +func Pretty(a interface{}) string { + return dump.Pretty(a) +} + +// Deprecated: Use k8s.io/utils/dump.ForHash instead. +// +//go:fix inline +func ForHash(a interface{}) string { + return dump.ForHash(a) +} + +// Deprecated: Use k8s.io/utils/dump.OneLine instead. +// +//go:fix inline +func OneLine(a interface{}) string { + return dump.OneLine(a) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/duration/duration.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/duration/duration.go new file mode 100644 index 0000000000..a20136a4e5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/duration/duration.go @@ -0,0 +1,93 @@ +/* +Copyright 2018 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 duration + +import ( + "fmt" + "time" +) + +// ShortHumanDuration returns a succinct representation of the provided duration +// with limited precision for consumption by humans. +func ShortHumanDuration(d time.Duration) string { + // Allow deviation no more than 2 seconds(excluded) to tolerate machine time + // inconsistence, it can be considered as almost now. + if seconds := int(d.Seconds()); seconds < -1 { + return "" + } else if seconds < 0 { + return "0s" + } else if seconds < 60 { + return fmt.Sprintf("%ds", seconds) + } else if minutes := int(d.Minutes()); minutes < 60 { + return fmt.Sprintf("%dm", minutes) + } else if hours := int(d.Hours()); hours < 24 { + return fmt.Sprintf("%dh", hours) + } else if hours < 24*365 { + return fmt.Sprintf("%dd", hours/24) + } + return fmt.Sprintf("%dy", int(d.Hours()/24/365)) +} + +// HumanDuration returns a succinct representation of the provided duration +// with limited precision for consumption by humans. It provides ~2-3 significant +// figures of duration. +func HumanDuration(d time.Duration) string { + // Allow deviation no more than 2 seconds(excluded) to tolerate machine time + // inconsistence, it can be considered as almost now. + if seconds := int(d.Seconds()); seconds < -1 { + return "" + } else if seconds < 0 { + return "0s" + } else if seconds < 60*2 { + return fmt.Sprintf("%ds", seconds) + } + minutes := int(d / time.Minute) + if minutes < 10 { + s := int(d/time.Second) % 60 + if s == 0 { + return fmt.Sprintf("%dm", minutes) + } + return fmt.Sprintf("%dm%ds", minutes, s) + } else if minutes < 60*3 { + return fmt.Sprintf("%dm", minutes) + } + hours := int(d / time.Hour) + if hours < 8 { + m := int(d/time.Minute) % 60 + if m == 0 { + return fmt.Sprintf("%dh", hours) + } + return fmt.Sprintf("%dh%dm", hours, m) + } else if hours < 48 { + return fmt.Sprintf("%dh", hours) + } else if hours < 24*8 { + h := hours % 24 + if h == 0 { + return fmt.Sprintf("%dd", hours/24) + } + return fmt.Sprintf("%dd%dh", hours/24, h) + } else if hours < 24*365*2 { + return fmt.Sprintf("%dd", hours/24) + } else if hours < 24*365*8 { + dy := int(hours/24) % 365 + if dy == 0 { + return fmt.Sprintf("%dy", hours/24/365) + } + return fmt.Sprintf("%dy%dd", hours/24/365, dy) + } + return fmt.Sprintf("%dy", int(hours/24/365)) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/duration/duration_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/duration/duration_test.go new file mode 100644 index 0000000000..ae56b08567 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/duration/duration_test.go @@ -0,0 +1,133 @@ +/* +Copyright 2018 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 duration + +import ( + "testing" + "time" +) + +func TestHumanDuration(t *testing.T) { + tests := []struct { + d time.Duration + want string + }{ + {d: time.Second, want: "1s"}, + {d: 70 * time.Second, want: "70s"}, + {d: 190 * time.Second, want: "3m10s"}, + {d: 70 * time.Minute, want: "70m"}, + {d: 47 * time.Hour, want: "47h"}, + {d: 49 * time.Hour, want: "2d1h"}, + {d: (8*24 + 2) * time.Hour, want: "8d"}, + {d: (367 * 24) * time.Hour, want: "367d"}, + {d: (365*2*24 + 25) * time.Hour, want: "2y1d"}, + {d: (365*8*24 + 2) * time.Hour, want: "8y"}, + } + for _, tt := range tests { + t.Run(tt.d.String(), func(t *testing.T) { + if got := HumanDuration(tt.d); got != tt.want { + t.Errorf("HumanDuration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestHumanDurationBoundaries(t *testing.T) { + tests := []struct { + d time.Duration + want string + }{ + {d: -2 * time.Second, want: ""}, + {d: -2*time.Second + 1, want: "0s"}, + {d: 0, want: "0s"}, + {d: time.Second - time.Millisecond, want: "0s"}, + {d: 2*time.Minute - time.Millisecond, want: "119s"}, + {d: 2 * time.Minute, want: "2m"}, + {d: 2*time.Minute + time.Second, want: "2m1s"}, + {d: 10*time.Minute - time.Millisecond, want: "9m59s"}, + {d: 10 * time.Minute, want: "10m"}, + {d: 10*time.Minute + time.Second, want: "10m"}, + {d: 3*time.Hour - time.Millisecond, want: "179m"}, + {d: 3 * time.Hour, want: "3h"}, + {d: 3*time.Hour + time.Minute, want: "3h1m"}, + {d: 8*time.Hour - time.Millisecond, want: "7h59m"}, + {d: 8 * time.Hour, want: "8h"}, + {d: 8*time.Hour + 59*time.Minute, want: "8h"}, + {d: 2*24*time.Hour - time.Millisecond, want: "47h"}, + {d: 2 * 24 * time.Hour, want: "2d"}, + {d: 2*24*time.Hour + time.Hour, want: "2d1h"}, + {d: 8*24*time.Hour - time.Millisecond, want: "7d23h"}, + {d: 8 * 24 * time.Hour, want: "8d"}, + {d: 8*24*time.Hour + 23*time.Hour, want: "8d"}, + {d: 2*365*24*time.Hour - time.Millisecond, want: "729d"}, + {d: 2 * 365 * 24 * time.Hour, want: "2y"}, + {d: 2*365*24*time.Hour + 23*time.Hour, want: "2y"}, + {d: 2*365*24*time.Hour + 23*time.Hour + 59*time.Minute, want: "2y"}, + {d: 2*365*24*time.Hour + 24*time.Hour - time.Millisecond, want: "2y"}, + {d: 2*365*24*time.Hour + 24*time.Hour, want: "2y1d"}, + {d: 3 * 365 * 24 * time.Hour, want: "3y"}, + {d: 4 * 365 * 24 * time.Hour, want: "4y"}, + {d: 5 * 365 * 24 * time.Hour, want: "5y"}, + {d: 6 * 365 * 24 * time.Hour, want: "6y"}, + {d: 7 * 365 * 24 * time.Hour, want: "7y"}, + {d: 8*365*24*time.Hour - time.Millisecond, want: "7y364d"}, + {d: 8 * 365 * 24 * time.Hour, want: "8y"}, + {d: 8*365*24*time.Hour + 364*24*time.Hour, want: "8y"}, + {d: 9 * 365 * 24 * time.Hour, want: "9y"}, + } + for _, tt := range tests { + t.Run(tt.d.String(), func(t *testing.T) { + if got := HumanDuration(tt.d); got != tt.want { + t.Errorf("HumanDuration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestShortHumanDurationBoundaries(t *testing.T) { + tests := []struct { + d time.Duration + want string + }{ + {d: -2 * time.Second, want: ""}, + {d: -2*time.Second + 1, want: "0s"}, + {d: 0, want: "0s"}, + {d: time.Second - time.Millisecond, want: "0s"}, + {d: time.Second, want: "1s"}, + {d: 2*time.Second - time.Millisecond, want: "1s"}, + {d: time.Minute - time.Millisecond, want: "59s"}, + {d: time.Minute, want: "1m"}, + {d: 2*time.Minute - time.Millisecond, want: "1m"}, + {d: time.Hour - time.Millisecond, want: "59m"}, + {d: time.Hour, want: "1h"}, + {d: 2*time.Hour - time.Millisecond, want: "1h"}, + {d: 24*time.Hour - time.Millisecond, want: "23h"}, + {d: 24 * time.Hour, want: "1d"}, + {d: 2*24*time.Hour - time.Millisecond, want: "1d"}, + {d: 365*24*time.Hour - time.Millisecond, want: "364d"}, + {d: 365 * 24 * time.Hour, want: "1y"}, + {d: 2*365*24*time.Hour - time.Millisecond, want: "1y"}, + {d: 2 * 365 * 24 * time.Hour, want: "2y"}, + } + for _, tt := range tests { + t.Run(tt.d.String(), func(t *testing.T) { + if got := ShortHumanDuration(tt.d); got != tt.want { + t.Errorf("ShortHumanDuration() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/doc.go new file mode 100644 index 0000000000..b3b39bc388 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 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 errors implements various utility functions and types around errors. +package errors diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/errors.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/errors.go new file mode 100644 index 0000000000..6f458d13d7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/errors.go @@ -0,0 +1,251 @@ +/* +Copyright 2015 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 errors + +import ( + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/util/sets" +) + +// MessageCountMap contains occurrence for each error message. +// Deprecated: Not used anymore in the k8s.io codebase, use `errors.Join` instead. +type MessageCountMap map[string]int + +// Aggregate represents an object that contains multiple errors, but does not +// necessarily have singular semantic meaning. +// The aggregate can be used with `errors.Is()` to check for the occurrence of +// a specific error type. +// Errors.As() is not supported, because the caller presumably cares about a +// specific error of potentially multiple that match the given type. +type Aggregate interface { + error + Errors() []error + Is(error) bool +} + +// NewAggregate converts a slice of errors into an Aggregate interface, which +// is itself an implementation of the error interface. If the slice is empty, +// this returns nil. +// It will check if any of the element of input error list is nil, to avoid +// nil pointer panic when call Error(). +func NewAggregate(errlist []error) Aggregate { + if len(errlist) == 0 { + return nil + } + // In case of input error list contains nil + var errs []error + for _, e := range errlist { + if e != nil { + errs = append(errs, e) + } + } + if len(errs) == 0 { + return nil + } + return aggregate(errs) +} + +// This helper implements the error and Errors interfaces. Keeping it private +// prevents people from making an aggregate of 0 errors, which is not +// an error, but does satisfy the error interface. +type aggregate []error + +// Error is part of the error interface. +func (agg aggregate) Error() string { + if len(agg) == 0 { + // This should never happen, really. + return "" + } + if len(agg) == 1 { + return agg[0].Error() + } + seenerrs := sets.NewString() + result := "" + agg.visit(func(err error) bool { + msg := err.Error() + if seenerrs.Has(msg) { + return false + } + seenerrs.Insert(msg) + if len(seenerrs) > 1 { + result += ", " + } + result += msg + return false + }) + if len(seenerrs) == 1 { + return result + } + return "[" + result + "]" +} + +func (agg aggregate) Is(target error) bool { + return agg.visit(func(err error) bool { + return errors.Is(err, target) + }) +} + +func (agg aggregate) visit(f func(err error) bool) bool { + for _, err := range agg { + switch err := err.(type) { + case aggregate: + if match := err.visit(f); match { + return match + } + case Aggregate: + for _, nestedErr := range err.Errors() { + if match := f(nestedErr); match { + return match + } + } + default: + if match := f(err); match { + return match + } + } + } + + return false +} + +// Errors is part of the Aggregate interface. +func (agg aggregate) Errors() []error { + return []error(agg) +} + +// Matcher is used to match errors. Returns true if the error matches. +type Matcher func(error) bool + +// FilterOut removes all errors that match any of the matchers from the input +// error. If the input is a singular error, only that error is tested. If the +// input implements the Aggregate interface, the list of errors will be +// processed recursively. +// +// This can be used, for example, to remove known-OK errors (such as io.EOF or +// os.PathNotFound) from a list of errors. +func FilterOut(err error, fns ...Matcher) error { + if err == nil { + return nil + } + if agg, ok := err.(Aggregate); ok { + return NewAggregate(filterErrors(agg.Errors(), fns...)) + } + if !matchesError(err, fns...) { + return err + } + return nil +} + +// matchesError returns true if any Matcher returns true +func matchesError(err error, fns ...Matcher) bool { + for _, fn := range fns { + if fn(err) { + return true + } + } + return false +} + +// filterErrors returns any errors (or nested errors, if the list contains +// nested Errors) for which all fns return false. If no errors +// remain a nil list is returned. The resulting slice will have all +// nested slices flattened as a side effect. +func filterErrors(list []error, fns ...Matcher) []error { + result := []error{} + for _, err := range list { + r := FilterOut(err, fns...) + if r != nil { + result = append(result, r) + } + } + return result +} + +// Flatten takes an Aggregate, which may hold other Aggregates in arbitrary +// nesting, and flattens them all into a single Aggregate, recursively. +func Flatten(agg Aggregate) Aggregate { + result := []error{} + if agg == nil { + return nil + } + for _, err := range agg.Errors() { + if a, ok := err.(Aggregate); ok { + r := Flatten(a) + if r != nil { + result = append(result, r.Errors()...) + } + } else { + if err != nil { + result = append(result, err) + } + } + } + return NewAggregate(result) +} + +// CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate +// Deprecated: Not used anymore in the k8s.io codebase, use `errors.Join` instead. +func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate { + if m == nil { + return nil + } + result := make([]error, 0, len(m)) + for errStr, count := range m { + var countStr string + if count > 1 { + countStr = fmt.Sprintf(" (repeated %v times)", count) + } + result = append(result, fmt.Errorf("%v%v", errStr, countStr)) + } + return NewAggregate(result) +} + +// Reduce will return err or nil, if err is an Aggregate and only has one item, +// the first item in the aggregate. +func Reduce(err error) error { + if agg, ok := err.(Aggregate); ok && err != nil { + switch len(agg.Errors()) { + case 1: + return agg.Errors()[0] + case 0: + return nil + } + } + return err +} + +// AggregateGoroutines runs the provided functions in parallel, stuffing all +// non-nil errors into the returned Aggregate. +// Returns nil if all the functions complete successfully. +func AggregateGoroutines(funcs ...func() error) Aggregate { + errChan := make(chan error, len(funcs)) + for _, f := range funcs { + go func(f func() error) { errChan <- f() }(f) + } + errs := make([]error, 0) + for i := 0; i < cap(errChan); i++ { + if err := <-errChan; err != nil { + errs = append(errs, err) + } + } + return NewAggregate(errs) +} + +// ErrPreconditionViolated is returned when the precondition is violated +var ErrPreconditionViolated = errors.New("precondition is violated") diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/errors_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/errors_test.go new file mode 100644 index 0000000000..02386aa48d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/errors/errors_test.go @@ -0,0 +1,530 @@ +/* +Copyright 2015 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 errors + +import ( + "errors" + "fmt" + "reflect" + "sort" + "testing" +) + +func TestEmptyAggregate(t *testing.T) { + var slice []error + var agg Aggregate + var err error + + agg = NewAggregate(slice) + if agg != nil { + t.Errorf("expected nil, got %#v", agg) + } + err = NewAggregate(slice) + if err != nil { + t.Errorf("expected nil, got %#v", err) + } + + // This is not normally possible, but pedantry demands I test it. + agg = aggregate(slice) // empty aggregate + if s := agg.Error(); s != "" { + t.Errorf("expected empty string, got %q", s) + } + if s := agg.Errors(); len(s) != 0 { + t.Errorf("expected empty slice, got %#v", s) + } + err = agg.(error) + if s := err.Error(); s != "" { + t.Errorf("expected empty string, got %q", s) + } +} + +func TestAggregateWithNil(t *testing.T) { + var slice []error + slice = []error{nil} + var agg Aggregate + var err error + + agg = NewAggregate(slice) + if agg != nil { + t.Errorf("expected nil, got %#v", agg) + } + err = NewAggregate(slice) + if err != nil { + t.Errorf("expected nil, got %#v", err) + } + + // Append a non-nil error + slice = append(slice, fmt.Errorf("err")) + agg = NewAggregate(slice) + if agg == nil { + t.Errorf("expected non-nil") + } + if s := agg.Error(); s != "err" { + t.Errorf("expected 'err', got %q", s) + } + if s := agg.Errors(); len(s) != 1 { + t.Errorf("expected one-element slice, got %#v", s) + } + if s := agg.Errors()[0].Error(); s != "err" { + t.Errorf("expected 'err', got %q", s) + } + + err = agg.(error) + if err == nil { + t.Errorf("expected non-nil") + } + if s := err.Error(); s != "err" { + t.Errorf("expected 'err', got %q", s) + } +} + +func TestSingularAggregate(t *testing.T) { + var slice = []error{fmt.Errorf("err")} + var agg Aggregate + var err error + + agg = NewAggregate(slice) + if agg == nil { + t.Errorf("expected non-nil") + } + if s := agg.Error(); s != "err" { + t.Errorf("expected 'err', got %q", s) + } + if s := agg.Errors(); len(s) != 1 { + t.Errorf("expected one-element slice, got %#v", s) + } + if s := agg.Errors()[0].Error(); s != "err" { + t.Errorf("expected 'err', got %q", s) + } + + err = agg.(error) + if err == nil { + t.Errorf("expected non-nil") + } + if s := err.Error(); s != "err" { + t.Errorf("expected 'err', got %q", s) + } +} + +func TestPluralAggregate(t *testing.T) { + var slice = []error{fmt.Errorf("abc"), fmt.Errorf("123")} + var agg Aggregate + var err error + + agg = NewAggregate(slice) + if agg == nil { + t.Errorf("expected non-nil") + } + if s := agg.Error(); s != "[abc, 123]" { + t.Errorf("expected '[abc, 123]', got %q", s) + } + if s := agg.Errors(); len(s) != 2 { + t.Errorf("expected two-elements slice, got %#v", s) + } + if s := agg.Errors()[0].Error(); s != "abc" { + t.Errorf("expected '[abc, 123]', got %q", s) + } + + err = agg.(error) + if err == nil { + t.Errorf("expected non-nil") + } + if s := err.Error(); s != "[abc, 123]" { + t.Errorf("expected '[abc, 123]', got %q", s) + } +} + +func TestDedupeAggregate(t *testing.T) { + var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc")} + var agg Aggregate + + agg = NewAggregate(slice) + if agg == nil { + t.Errorf("expected non-nil") + } + if s := agg.Error(); s != "abc" { + t.Errorf("expected 'abc', got %q", s) + } + if s := agg.Errors(); len(s) != 2 { + t.Errorf("expected two-elements slice, got %#v", s) + } +} + +func TestDedupePluralAggregate(t *testing.T) { + var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), fmt.Errorf("123")} + var agg Aggregate + + agg = NewAggregate(slice) + if agg == nil { + t.Errorf("expected non-nil") + } + if s := agg.Error(); s != "[abc, 123]" { + t.Errorf("expected '[abc, 123]', got %q", s) + } + if s := agg.Errors(); len(s) != 3 { + t.Errorf("expected three-elements slice, got %#v", s) + } +} + +func TestFlattenAndDedupeAggregate(t *testing.T) { + var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), NewAggregate([]error{fmt.Errorf("abc")})} + var agg Aggregate + + agg = NewAggregate(slice) + if agg == nil { + t.Errorf("expected non-nil") + } + if s := agg.Error(); s != "abc" { + t.Errorf("expected 'abc', got %q", s) + } + if s := agg.Errors(); len(s) != 3 { + t.Errorf("expected three-elements slice, got %#v", s) + } +} + +func TestFlattenAggregate(t *testing.T) { + var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), NewAggregate([]error{fmt.Errorf("abc"), fmt.Errorf("def"), NewAggregate([]error{fmt.Errorf("def"), fmt.Errorf("ghi")})})} + var agg Aggregate + + agg = NewAggregate(slice) + if agg == nil { + t.Errorf("expected non-nil") + } + if s := agg.Error(); s != "[abc, def, ghi]" { + t.Errorf("expected '[abc, def, ghi]', got %q", s) + } + if s := agg.Errors(); len(s) != 3 { + t.Errorf("expected three-elements slice, got %#v", s) + } +} + +func TestFilterOut(t *testing.T) { + testCases := []struct { + err error + filter []Matcher + expected error + }{ + { + nil, + []Matcher{}, + nil, + }, + { + aggregate{}, + []Matcher{}, + nil, + }, + { + aggregate{fmt.Errorf("abc")}, + []Matcher{}, + aggregate{fmt.Errorf("abc")}, + }, + { + aggregate{fmt.Errorf("abc")}, + []Matcher{func(err error) bool { return false }}, + aggregate{fmt.Errorf("abc")}, + }, + { + aggregate{fmt.Errorf("abc")}, + []Matcher{func(err error) bool { return true }}, + nil, + }, + { + aggregate{fmt.Errorf("abc")}, + []Matcher{func(err error) bool { return false }, func(err error) bool { return false }}, + aggregate{fmt.Errorf("abc")}, + }, + { + aggregate{fmt.Errorf("abc")}, + []Matcher{func(err error) bool { return false }, func(err error) bool { return true }}, + nil, + }, + { + aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")}, + []Matcher{func(err error) bool { return err.Error() == "def" }}, + aggregate{fmt.Errorf("abc"), fmt.Errorf("ghi")}, + }, + { + aggregate{aggregate{fmt.Errorf("abc")}}, + []Matcher{}, + aggregate{aggregate{fmt.Errorf("abc")}}, + }, + { + aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}}, + []Matcher{}, + aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}}, + }, + { + aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}}, + []Matcher{func(err error) bool { return err.Error() == "def" }}, + aggregate{aggregate{fmt.Errorf("abc")}}, + }, + } + for i, testCase := range testCases { + err := FilterOut(testCase.err, testCase.filter...) + if !reflect.DeepEqual(testCase.expected, err) { + t.Errorf("%d: expected %v, got %v", i, testCase.expected, err) + } + } +} + +func TestFlatten(t *testing.T) { + testCases := []struct { + agg Aggregate + expected Aggregate + }{ + { + nil, + nil, + }, + { + aggregate{}, + nil, + }, + { + aggregate{fmt.Errorf("abc")}, + aggregate{fmt.Errorf("abc")}, + }, + { + aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")}, + aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")}, + }, + { + aggregate{aggregate{fmt.Errorf("abc")}}, + aggregate{fmt.Errorf("abc")}, + }, + { + aggregate{aggregate{aggregate{fmt.Errorf("abc")}}}, + aggregate{fmt.Errorf("abc")}, + }, + { + aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}}, + aggregate{fmt.Errorf("abc"), fmt.Errorf("def")}, + }, + { + aggregate{aggregate{aggregate{fmt.Errorf("abc")}, fmt.Errorf("def"), aggregate{fmt.Errorf("ghi")}}}, + aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")}, + }, + } + for i, testCase := range testCases { + agg := Flatten(testCase.agg) + if !reflect.DeepEqual(testCase.expected, agg) { + t.Errorf("%d: expected %v, got %v", i, testCase.expected, agg) + } + } +} + +func TestCreateAggregateFromMessageCountMap(t *testing.T) { + testCases := []struct { + name string + mcm MessageCountMap + expected Aggregate + }{ + { + "input has single instance of one message", + MessageCountMap{"abc": 1}, + aggregate{fmt.Errorf("abc")}, + }, + { + "input has multiple messages", + MessageCountMap{"abc": 2, "ghi": 1}, + aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")}, + }, + { + "input has multiple messages", + MessageCountMap{"ghi": 1, "abc": 2}, + aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")}, + }, + } + + var expected, agg []error + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + if testCase.expected != nil { + expected = testCase.expected.Errors() + sort.Slice(expected, func(i, j int) bool { return expected[i].Error() < expected[j].Error() }) + } + if testCase.mcm != nil { + agg = CreateAggregateFromMessageCountMap(testCase.mcm).Errors() + sort.Slice(agg, func(i, j int) bool { return agg[i].Error() < agg[j].Error() }) + } + if !reflect.DeepEqual(expected, agg) { + t.Errorf("expected %v, got %v", expected, agg) + } + }) + } +} + +func TestAggregateGoroutines(t *testing.T) { + testCases := []struct { + errs []error + expected map[string]bool // can't compare directly to Aggregate due to non-deterministic ordering + }{ + { + []error{}, + nil, + }, + { + []error{nil}, + nil, + }, + { + []error{nil, nil}, + nil, + }, + { + []error{fmt.Errorf("1")}, + map[string]bool{"1": true}, + }, + { + []error{fmt.Errorf("1"), nil}, + map[string]bool{"1": true}, + }, + { + []error{fmt.Errorf("1"), fmt.Errorf("267")}, + map[string]bool{"1": true, "267": true}, + }, + { + []error{fmt.Errorf("1"), nil, fmt.Errorf("1234")}, + map[string]bool{"1": true, "1234": true}, + }, + { + []error{nil, fmt.Errorf("1"), nil, fmt.Errorf("1234"), fmt.Errorf("22")}, + map[string]bool{"1": true, "1234": true, "22": true}, + }, + } + for i, testCase := range testCases { + funcs := make([]func() error, len(testCase.errs)) + for i := range testCase.errs { + err := testCase.errs[i] + funcs[i] = func() error { return err } + } + agg := AggregateGoroutines(funcs...) + if agg == nil { + if len(testCase.expected) > 0 { + t.Errorf("%d: expected %v, got nil", i, testCase.expected) + } + continue + } + if len(agg.Errors()) != len(testCase.expected) { + t.Errorf("%d: expected %d errors in aggregate, got %v", i, len(testCase.expected), agg) + continue + } + for _, err := range agg.Errors() { + if !testCase.expected[err.Error()] { + t.Errorf("%d: expected %v, got aggregate containing %v", i, testCase.expected, err) + } + } + } +} + +type alwaysMatchingError struct{} + +func (_ alwaysMatchingError) Error() string { + return "error" +} + +func (_ alwaysMatchingError) Is(_ error) bool { + return true +} + +type someError struct{ msg string } + +func (se someError) Error() string { + if se.msg != "" { + return se.msg + } + return "err" +} + +func TestAggregateWithErrorsIs(t *testing.T) { + testCases := []struct { + name string + err error + matchAgainst error + expectMatch bool + }{ + { + name: "no match", + err: aggregate{errors.New("my-error"), errors.New("my-other-error")}, + matchAgainst: fmt.Errorf("no entry %s", "here"), + }, + { + name: "match via .Is()", + err: aggregate{errors.New("forbidden"), alwaysMatchingError{}}, + matchAgainst: errors.New("unauthorized"), + expectMatch: true, + }, + { + name: "match via equality", + err: aggregate{errors.New("err"), someError{}}, + matchAgainst: someError{}, + expectMatch: true, + }, + { + name: "match via nested aggregate", + err: aggregate{errors.New("closed today"), aggregate{aggregate{someError{}}}}, + matchAgainst: someError{}, + expectMatch: true, + }, + { + name: "match via wrapped aggregate", + err: fmt.Errorf("wrap: %w", aggregate{errors.New("err"), someError{}}), + matchAgainst: someError{}, + expectMatch: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := errors.Is(tc.err, tc.matchAgainst) + if result != tc.expectMatch { + t.Errorf("expected match: %t, got match: %t", tc.expectMatch, result) + } + }) + } +} + +type accessTrackingError struct { + wasAccessed bool +} + +func (accessTrackingError) Error() string { + return "err" +} + +func (ate *accessTrackingError) Is(_ error) bool { + ate.wasAccessed = true + return true +} + +var _ error = &accessTrackingError{} + +func TestErrConfigurationInvalidWithErrorsIsShortCircuitsOnFirstMatch(t *testing.T) { + errC := aggregate{&accessTrackingError{}, &accessTrackingError{}} + _ = errors.Is(errC, &accessTrackingError{}) + + var numAccessed int + for _, err := range errC { + if ate := err.(*accessTrackingError); ate.wasAccessed { + numAccessed++ + } + } + if numAccessed != 1 { + t.Errorf("expected exactly one error to get accessed, got %d", numAccessed) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/framer/framer.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/framer/framer.go new file mode 100644 index 0000000000..f18845a417 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/framer/framer.go @@ -0,0 +1,176 @@ +/* +Copyright 2015 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 framer implements simple frame decoding techniques for an io.ReadCloser +package framer + +import ( + "encoding/binary" + "encoding/json" + "io" +) + +type lengthDelimitedFrameWriter struct { + w io.Writer + h [4]byte +} + +func NewLengthDelimitedFrameWriter(w io.Writer) io.Writer { + return &lengthDelimitedFrameWriter{w: w} +} + +// Write writes a single frame to the nested writer, prepending it with the length +// in bytes of data (as a 4 byte, bigendian uint32). +func (w *lengthDelimitedFrameWriter) Write(data []byte) (int, error) { + binary.BigEndian.PutUint32(w.h[:], uint32(len(data))) + n, err := w.w.Write(w.h[:]) + if err != nil { + return 0, err + } + if n != len(w.h) { + return 0, io.ErrShortWrite + } + return w.w.Write(data) +} + +type lengthDelimitedFrameReader struct { + r io.ReadCloser + remaining int +} + +// NewLengthDelimitedFrameReader returns an io.Reader that will decode length-prefixed +// frames off of a stream. +// +// The protocol is: +// +// stream: message ... +// message: prefix body +// prefix: 4 byte uint32 in BigEndian order, denotes length of body +// body: bytes (0..prefix) +// +// If the buffer passed to Read is not long enough to contain an entire frame, io.ErrShortRead +// will be returned along with the number of bytes read. +func NewLengthDelimitedFrameReader(r io.ReadCloser) io.ReadCloser { + return &lengthDelimitedFrameReader{r: r} +} + +// Read attempts to read an entire frame into data. If that is not possible, io.ErrShortBuffer +// is returned and subsequent calls will attempt to read the last frame. A frame is complete when +// err is nil. +func (r *lengthDelimitedFrameReader) Read(data []byte) (int, error) { + if r.remaining <= 0 { + header := [4]byte{} + n, err := io.ReadAtLeast(r.r, header[:4], 4) + if err != nil { + return 0, err + } + if n != 4 { + return 0, io.ErrUnexpectedEOF + } + frameLength := int(binary.BigEndian.Uint32(header[:])) + r.remaining = frameLength + } + + expect := r.remaining + max := expect + if max > len(data) { + max = len(data) + } + n, err := io.ReadAtLeast(r.r, data[:max], int(max)) + r.remaining -= n + if err != nil { + return n, err + } + if r.remaining > 0 { + return n, io.ErrShortBuffer + } + if n != expect { + return n, io.ErrUnexpectedEOF + } + + return n, nil +} + +func (r *lengthDelimitedFrameReader) Close() error { + return r.r.Close() +} + +type jsonFrameReader struct { + r io.ReadCloser + decoder *json.Decoder + remaining []byte +} + +// NewJSONFramedReader returns an io.Reader that will decode individual JSON objects off +// of a wire. +// +// The boundaries between each frame are valid JSON objects. A JSON parsing error will terminate +// the read. +func NewJSONFramedReader(r io.ReadCloser) io.ReadCloser { + return &jsonFrameReader{ + r: r, + decoder: json.NewDecoder(r), + } +} + +// ReadFrame decodes the next JSON object in the stream, or returns an error. The returned +// byte slice will be modified the next time ReadFrame is invoked and should not be altered. +func (r *jsonFrameReader) Read(data []byte) (int, error) { + // Return whatever remaining data exists from an in progress frame + if n := len(r.remaining); n > 0 { + if n <= len(data) { + //nolint:staticcheck // SA4006,SA4010 underlying array of data is modified here. + data = append(data[0:0], r.remaining...) + r.remaining = nil + return n, nil + } + + n = len(data) + //nolint:staticcheck // SA4006,SA4010 underlying array of data is modified here. + data = append(data[0:0], r.remaining[:n]...) + r.remaining = r.remaining[n:] + return n, io.ErrShortBuffer + } + + // RawMessage#Unmarshal appends to data - we reset the slice down to 0 and will either see + // data written to data, or be larger than data and a different array. + m := json.RawMessage(data[:0]) + if err := r.decoder.Decode(&m); err != nil { + return 0, err + } + + // If capacity of data is less than length of the message, decoder will allocate a new slice + // and set m to it, which means we need to copy the partial result back into data and preserve + // the remaining result for subsequent reads. + if len(m) > cap(data) { + copy(data, m) + r.remaining = m[len(data):] + return len(data), io.ErrShortBuffer + } + + if len(m) > len(data) { + // The bytes beyond len(data) were stored in data's underlying array, which we do + // not own after this function returns. + r.remaining = append([]byte(nil), m[len(data):]...) + return len(data), io.ErrShortBuffer + } + + return len(m), nil +} + +func (r *jsonFrameReader) Close() error { + return r.r.Close() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/framer/framer_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/framer/framer_test.go new file mode 100644 index 0000000000..7a04fc1b7a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/framer/framer_test.go @@ -0,0 +1,236 @@ +/* +Copyright 2016 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 framer + +import ( + "bytes" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + netutil "k8s.io/apimachinery/pkg/util/net" +) + +func TestRead(t *testing.T) { + data := []byte{ + 0x00, 0x00, 0x00, 0x04, + 0x01, 0x02, 0x03, 0x04, + 0x00, 0x00, 0x00, 0x03, + 0x05, 0x06, 0x07, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x08, + } + b := bytes.NewBuffer(data) + r := NewLengthDelimitedFrameReader(io.NopCloser(b)) + buf := make([]byte, 1) + if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x01}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x02}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + // read the remaining frame + buf = make([]byte, 2) + if n, err := r.Read(buf); err != nil && n != 2 && bytes.Equal(buf, []byte{0x03, 0x04}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + // read with buffer equal to frame + buf = make([]byte, 3) + if n, err := r.Read(buf); err != nil && n != 3 && bytes.Equal(buf, []byte{0x05, 0x06, 0x07}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + // read empty frame + buf = make([]byte, 3) + if n, err := r.Read(buf); err != nil && n != 0 && bytes.Equal(buf, []byte{}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + // read with larger buffer than frame + buf = make([]byte, 3) + if n, err := r.Read(buf); err != nil && n != 1 && bytes.Equal(buf, []byte{0x08}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + // read EOF + if n, err := r.Read(buf); err != io.EOF && n != 0 { + t.Fatalf("unexpected: %v %d", err, n) + } +} + +func TestReadLarge(t *testing.T) { + data := []byte{ + 0x00, 0x00, 0x00, 0x04, + 0x01, 0x02, 0x03, 0x04, + 0x00, 0x00, 0x00, 0x03, + 0x05, 0x06, 0x07, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x08, + } + b := bytes.NewBuffer(data) + r := NewLengthDelimitedFrameReader(io.NopCloser(b)) + buf := make([]byte, 40) + if n, err := r.Read(buf); err != nil && n != 4 && bytes.Equal(buf, []byte{0x01, 0x02, 0x03, 0x04}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + if n, err := r.Read(buf); err != nil && n != 3 && bytes.Equal(buf, []byte{0x05, 0x06, 0x7}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + if n, err := r.Read(buf); err != nil && n != 0 && bytes.Equal(buf, []byte{}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + if n, err := r.Read(buf); err != nil && n != 1 && bytes.Equal(buf, []byte{0x08}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + // read EOF + if n, err := r.Read(buf); err != io.EOF && n != 0 { + t.Fatalf("unexpected: %v %d", err, n) + } +} + +func TestReadInvalidFrame(t *testing.T) { + data := []byte{ + 0x00, 0x00, 0x00, 0x04, + 0x01, 0x02, + } + b := bytes.NewBuffer(data) + r := NewLengthDelimitedFrameReader(io.NopCloser(b)) + buf := make([]byte, 1) + if n, err := r.Read(buf); err != io.ErrShortBuffer && n != 1 && bytes.Equal(buf, []byte{0x01}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + // read the remaining frame + buf = make([]byte, 3) + if n, err := r.Read(buf); err != io.ErrUnexpectedEOF && n != 1 && bytes.Equal(buf, []byte{0x02}) { + t.Fatalf("unexpected: %v %d %v", err, n, buf) + } + // read EOF + if n, err := r.Read(buf); err != io.EOF && n != 0 { + t.Fatalf("unexpected: %v %d", err, n) + } +} + +func TestReadClientTimeout(t *testing.T) { + header := []byte{ + 0x00, 0x00, 0x00, 0x04, + } + data := []byte{ + 0x01, 0x02, 0x03, 0x04, + 0x00, 0x00, 0x00, 0x03, + 0x05, 0x06, 0x07, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x08, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(header) + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + time.Sleep(1 * time.Second) + _, _ = w.Write(data) + })) + defer server.Close() + + client := &http.Client{ + Timeout: 500 * time.Millisecond, + } + + resp, err := client.Get(server.URL) + if err != nil { + t.Fatalf("unexpected: %v", err) + } + + r := NewLengthDelimitedFrameReader(resp.Body) + buf := make([]byte, 1) + if n, err := r.Read(buf); err == nil || !netutil.IsTimeout(err) { + t.Fatalf("unexpected: %v %d", err, n) + } +} + +func TestJSONFrameReader(t *testing.T) { + b := bytes.NewBufferString("{\"test\":true}\n1\n[\"a\"]") + r := NewJSONFramedReader(io.NopCloser(b)) + buf := make([]byte, 20) + if n, err := r.Read(buf); err != nil || n != 13 || string(buf[:n]) != `{"test":true}` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `1` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + if n, err := r.Read(buf); err != nil || n != 5 || string(buf[:n]) != `["a"]` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + if n, err := r.Read(buf); err != io.EOF || n != 0 { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } +} + +func TestJSONFrameReaderShortBuffer(t *testing.T) { + b := bytes.NewBufferString("{\"test\":true}\n1\n[\"a\"]") + r := NewJSONFramedReader(io.NopCloser(b)) + buf := make([]byte, 3) + + if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `{"t` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `est` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `":t` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `rue` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `}` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + + if n, err := r.Read(buf); err != nil || n != 1 || string(buf[:n]) != `1` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + + if n, err := r.Read(buf); err != io.ErrShortBuffer || n != 3 || string(buf[:n]) != `["a` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + if n, err := r.Read(buf); err != nil || n != 2 || string(buf[:n]) != `"]` { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + + if n, err := r.Read(buf); err != io.EOF || n != 0 { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } +} + +func TestJSONFrameReaderShortBufferNoUnderlyingArrayReuse(t *testing.T) { + b := bytes.NewBufferString("{}") + r := NewJSONFramedReader(io.NopCloser(b)) + buf := make([]byte, 1, 2) // cap(buf) > len(buf) && cap(buf) <= len("{}") + + if n, err := r.Read(buf); !errors.Is(err, io.ErrShortBuffer) || n != 1 || string(buf[:n]) != "{" { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } + buf = append(buf, make([]byte, 1)...) // stomps the second byte of the backing array + if n, err := r.Read(buf[1:]); err != nil || n != 1 || string(buf[1:1+n]) != "}" { + t.Fatalf("unexpected: %v %d %q", err, n, buf) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/doc.go new file mode 100644 index 0000000000..5fdc7955fa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2015 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 httpstream contains compatibility wrappers for streaming transport APIs. +// +// Deprecated: use k8s.io/streaming/pkg/httpstream directly. +package httpstream diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go new file mode 100644 index 0000000000..a7c8d897dc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go @@ -0,0 +1,201 @@ +/* +Copyright 2015 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 httpstream + +import ( + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +const ( + HeaderConnection = "Connection" + HeaderUpgrade = "Upgrade" + HeaderProtocolVersion = "X-Stream-Protocol-Version" + HeaderAcceptedProtocolVersions = "X-Accepted-Stream-Protocol-Versions" +) + +// NewStreamHandler defines a function that is called when a new Stream is +// received. If no error is returned, the Stream is accepted; otherwise, +// the stream is rejected. After the reply frame has been sent, replySent is closed. +type NewStreamHandler func(stream Stream, replySent <-chan struct{}) error + +// NoOpNewStreamHandler is a stream handler that accepts a new stream and +// performs no other logic. +func NoOpNewStreamHandler(stream Stream, replySent <-chan struct{}) error { return nil } + +// Dialer knows how to open a streaming connection to a server. +type Dialer interface { + + // Dial opens a streaming connection to a server using one of the protocols + // specified (in order of most preferred to least preferred). + Dial(protocols ...string) (Connection, string, error) +} + +// UpgradeRoundTripper is a type of http.RoundTripper that is able to upgrade +// HTTP requests to support multiplexed bidirectional streams. After RoundTrip() +// is invoked, if the upgrade is successful, clients may retrieve the upgraded +// connection by calling UpgradeRoundTripper.Connection(). +type UpgradeRoundTripper interface { + http.RoundTripper + // NewConnection validates the response and creates a new Connection. + NewConnection(resp *http.Response) (Connection, error) +} + +// ResponseUpgrader knows how to upgrade HTTP requests and responses to +// add streaming support to them. +type ResponseUpgrader interface { + // UpgradeResponse upgrades an HTTP response to one that supports multiplexed + // streams. newStreamHandler will be called asynchronously whenever the + // other end of the upgraded connection creates a new stream. + UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler NewStreamHandler) Connection +} + +// Connection represents an upgraded HTTP connection. +type Connection interface { + // CreateStream creates a new Stream with the supplied headers. + CreateStream(headers http.Header) (Stream, error) + // Close resets all streams and closes the connection. + Close() error + // CloseChan returns a channel that is closed when the underlying connection is closed. + CloseChan() <-chan bool + // SetIdleTimeout sets the amount of time the connection may remain idle before + // it is automatically closed. + SetIdleTimeout(timeout time.Duration) + // RemoveStreams can be used to remove a set of streams from the Connection. + RemoveStreams(streams ...Stream) +} + +// Stream represents a bidirectional communications channel that is part of an +// upgraded connection. +type Stream interface { + io.ReadWriteCloser + // Reset closes both directions of the stream, indicating that neither client + // or server can use it any more. + Reset() error + // Headers returns the headers used to create the stream. + Headers() http.Header + // Identifier returns the stream's ID. + Identifier() uint32 +} + +// UpgradeFailureError encapsulates the cause for why the streaming +// upgrade request failed. Implements error interface. +type UpgradeFailureError struct { + Cause error +} + +func (u *UpgradeFailureError) Error() string { + return fmt.Sprintf("unable to upgrade streaming request: %s", u.Cause) +} + +// IsUpgradeFailure returns true if the passed error is (or wrapped error contains) +// the UpgradeFailureError. +func IsUpgradeFailure(err error) bool { + if err == nil { + return false + } + var upgradeErr *UpgradeFailureError + return errors.As(err, &upgradeErr) +} + +// isHTTPSProxyError returns true if error is Gorilla/Websockets HTTPS Proxy dial error; +// false otherwise (see https://github.com/kubernetes/kubernetes/issues/126134). +func IsHTTPSProxyError(err error) bool { + if err == nil { + return false + } + return strings.Contains(err.Error(), "proxy: unknown scheme: https") +} + +// IsUpgradeRequest returns true if the given request is a connection upgrade request +func IsUpgradeRequest(req *http.Request) bool { + for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] { + if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) { + return true + } + } + return false +} + +func negotiateProtocol(clientProtocols, serverProtocols []string) string { + for i := range clientProtocols { + for j := range serverProtocols { + if clientProtocols[i] == serverProtocols[j] { + return clientProtocols[i] + } + } + } + return "" +} + +func commaSeparatedHeaderValues(header []string) []string { + var parsedClientProtocols []string + for i := range header { + for _, clientProtocol := range strings.Split(header[i], ",") { + if proto := strings.Trim(clientProtocol, " "); len(proto) > 0 { + parsedClientProtocols = append(parsedClientProtocols, proto) + } + } + } + return parsedClientProtocols +} + +// Handshake performs a subprotocol negotiation. If the client did request a +// subprotocol, Handshake will select the first common value found in +// serverProtocols, otherwise it will return an error and write an HTTP BadRequest to the response. +// If a match is found, Handshake adds a response header indicating the chosen subprotocol. +// If no match is found, HTTP forbidden is returned, along with a response header containing +// the list of protocols the server can accept. +func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string) (string, error) { + if len(serverProtocols) == 0 { + panic(fmt.Errorf("unable to upgrade: serverProtocols is required")) + } + values, ok := req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)] + if !ok { + err := fmt.Errorf("unable to upgrade: header %s does not exist in request with %d headers", HeaderProtocolVersion, len(req.Header)) + http.Error(w, err.Error(), http.StatusBadRequest) + return "", err + } + if len(values) == 0 { + err := fmt.Errorf("unable to upgrade: header %s is empty", HeaderProtocolVersion) + http.Error(w, err.Error(), http.StatusBadRequest) + return "", err + } + clientProtocols := commaSeparatedHeaderValues(values) + if len(clientProtocols) == 0 { + err := fmt.Errorf("unable to upgrade: header %s contains %s, but no valid protocols", HeaderProtocolVersion, values) + http.Error(w, err.Error(), http.StatusBadRequest) + return "", err + } + + negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols) + if len(negotiatedProtocol) == 0 { + for i := range serverProtocols { + w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i]) + } + err := fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols) + http.Error(w, err.Error(), http.StatusForbidden) + return "", err + } + + w.Header().Add(HeaderProtocolVersion, negotiatedProtocol) + return negotiatedProtocol, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go new file mode 100644 index 0000000000..d03acb0eed --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2015 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 spdy contains compatibility wrappers for the SPDY transport stack. +// +// Deprecated: use k8s.io/streaming/pkg/httpstream/spdy directly. +package spdy diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go new file mode 100644 index 0000000000..37dfe81894 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go @@ -0,0 +1,236 @@ +/* +Copyright 2015 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 spdy + +import ( + "crypto/tls" + "net" + "net/http" + "net/url" + "time" + + apihttpstream "k8s.io/apimachinery/pkg/util/httpstream" + streamhttp "k8s.io/streaming/pkg/httpstream" + streamspdy "k8s.io/streaming/pkg/httpstream/spdy" +) + +const HeaderSpdy31 = streamspdy.HeaderSpdy31 + +// SpdyRoundTripper is a compatibility wrapper around the streaming module's +// SPDY round tripper. +type SpdyRoundTripper struct { + delegate *streamspdy.SpdyRoundTripper +} + +func NewRoundTripper(tlsConfig *tls.Config) (*SpdyRoundTripper, error) { + delegate, err := streamspdy.NewRoundTripper(tlsConfig) + if err != nil { + return nil, err + } + return &SpdyRoundTripper{delegate: delegate}, nil +} + +func NewRoundTripperWithProxy(tlsConfig *tls.Config, proxier func(*http.Request) (*url.URL, error)) (*SpdyRoundTripper, error) { + delegate, err := streamspdy.NewRoundTripperWithProxy(tlsConfig, proxier) + if err != nil { + return nil, err + } + return &SpdyRoundTripper{delegate: delegate}, nil +} + +// RoundTripperConfig is a set of options for an SpdyRoundTripper. +type RoundTripperConfig struct { + // TLS configuration used by the round tripper if UpgradeTransport not present. + TLS *tls.Config + // Proxier is a proxy function invoked on each request. Optional. + Proxier func(*http.Request) (*url.URL, error) + // PingPeriod is a period for sending SPDY Pings on the connection. + // Optional. + PingPeriod time.Duration + // UpgradeTransport is a subtitute transport used for dialing. If set, + // this field will be used instead of "TLS" and "Proxier" for connection creation. + // Optional. + UpgradeTransport http.RoundTripper +} + +func NewRoundTripperWithConfig(cfg RoundTripperConfig) (*SpdyRoundTripper, error) { + delegate, err := streamspdy.NewRoundTripperWithConfig(streamspdy.RoundTripperConfig{ + TLS: cfg.TLS, + Proxier: cfg.Proxier, + PingPeriod: cfg.PingPeriod, + UpgradeTransport: cfg.UpgradeTransport, + }) + if err != nil { + return nil, err + } + return &SpdyRoundTripper{delegate: delegate}, nil +} + +// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during +// proxying with a spdy roundtripper. +func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config { + return s.delegate.TLSClientConfig() +} + +// Dial opens a network connection for an upgrade request. +func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) { + return s.delegate.Dial(req) +} + +// RoundTrip executes a request and upgrades the connection. +func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return s.delegate.RoundTrip(req) +} + +// NewConnection validates a server upgrade response and prepares the transport. +func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (apihttpstream.Connection, error) { + conn, err := s.delegate.NewConnection(resp) + if err != nil { + return nil, err + } + return wrapConnection(conn), nil +} + +type responseUpgraderAdapter struct { + delegate streamhttp.ResponseUpgrader +} + +func (r *responseUpgraderAdapter) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler apihttpstream.NewStreamHandler) apihttpstream.Connection { + conn := r.delegate.UpgradeResponse(w, req, wrapNewStreamHandler(newStreamHandler)) + return wrapConnection(conn) +} + +func NewResponseUpgrader() apihttpstream.ResponseUpgrader { + return &responseUpgraderAdapter{delegate: streamspdy.NewResponseUpgrader()} +} + +func NewResponseUpgraderWithPings(pingPeriod time.Duration) apihttpstream.ResponseUpgrader { + return &responseUpgraderAdapter{delegate: streamspdy.NewResponseUpgraderWithPings(pingPeriod)} +} + +func NewClientConnection(conn net.Conn) (apihttpstream.Connection, error) { + c, err := streamspdy.NewClientConnection(conn) + if err != nil { + return nil, err + } + return wrapConnection(c), nil +} + +func NewClientConnectionWithPings(conn net.Conn, pingPeriod time.Duration) (apihttpstream.Connection, error) { + c, err := streamspdy.NewClientConnectionWithPings(conn, pingPeriod) + if err != nil { + return nil, err + } + return wrapConnection(c), nil +} + +func NewServerConnection(conn net.Conn, newStreamHandler apihttpstream.NewStreamHandler) (apihttpstream.Connection, error) { + c, err := streamspdy.NewServerConnection(conn, wrapNewStreamHandler(newStreamHandler)) + if err != nil { + return nil, err + } + return wrapConnection(c), nil +} + +func NewServerConnectionWithPings(conn net.Conn, newStreamHandler apihttpstream.NewStreamHandler, pingPeriod time.Duration) (apihttpstream.Connection, error) { + c, err := streamspdy.NewServerConnectionWithPings(conn, wrapNewStreamHandler(newStreamHandler), pingPeriod) + if err != nil { + return nil, err + } + return wrapConnection(c), nil +} + +type streamAdapter struct { + delegate streamhttp.Stream +} + +func (s *streamAdapter) Read(p []byte) (int, error) { + return s.delegate.Read(p) +} + +func (s *streamAdapter) Write(p []byte) (int, error) { + return s.delegate.Write(p) +} + +func (s *streamAdapter) Close() error { + return s.delegate.Close() +} + +func (s *streamAdapter) Reset() error { + return s.delegate.Reset() +} + +func (s *streamAdapter) Headers() http.Header { + return s.delegate.Headers() +} + +func (s *streamAdapter) Identifier() uint32 { + return s.delegate.Identifier() +} + +type connectionAdapter struct { + delegate streamhttp.Connection +} + +func (c *connectionAdapter) CreateStream(headers http.Header) (apihttpstream.Stream, error) { + stream, err := c.delegate.CreateStream(headers) + if err != nil { + return nil, err + } + return &streamAdapter{delegate: stream}, nil +} + +func (c *connectionAdapter) Close() error { + return c.delegate.Close() +} + +func (c *connectionAdapter) CloseChan() <-chan bool { + return c.delegate.CloseChan() +} + +func (c *connectionAdapter) SetIdleTimeout(timeout time.Duration) { + c.delegate.SetIdleTimeout(timeout) +} + +func (c *connectionAdapter) RemoveStreams(streams ...apihttpstream.Stream) { + streamingStreams := make([]streamhttp.Stream, 0, len(streams)) + for _, stream := range streams { + if stream == nil { + continue + } + if s, ok := stream.(streamhttp.Stream); ok { + streamingStreams = append(streamingStreams, s) + } + } + c.delegate.RemoveStreams(streamingStreams...) +} + +func wrapConnection(conn streamhttp.Connection) apihttpstream.Connection { + if conn == nil { + return nil + } + return &connectionAdapter{delegate: conn} +} + +func wrapNewStreamHandler(newStreamHandler apihttpstream.NewStreamHandler) streamhttp.NewStreamHandler { + if newStreamHandler == nil { + return nil + } + return func(stream streamhttp.Stream, replySent <-chan struct{}) error { + return newStreamHandler(&streamAdapter{delegate: stream}, replySent) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go new file mode 100644 index 0000000000..9b07bfbb13 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2015 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 wsstream contains compatibility wrappers for websocket streaming. +// +// Deprecated: use k8s.io/streaming/pkg/httpstream/wsstream directly. +package wsstream diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go new file mode 100644 index 0000000000..73abcdc6ea --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go @@ -0,0 +1,92 @@ +/* +Copyright 2015 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 wsstream + +import ( + "io" + "net/http" + "time" + + "golang.org/x/net/websocket" + + "k8s.io/klog/v2" + streamws "k8s.io/streaming/pkg/httpstream/wsstream" +) + +const ( + WebSocketProtocolHeader = streamws.WebSocketProtocolHeader + ChannelWebSocketProtocol = streamws.ChannelWebSocketProtocol + Base64ChannelWebSocketProtocol = streamws.Base64ChannelWebSocketProtocol +) + +type ChannelType = streamws.ChannelType + +const ( + IgnoreChannel = streamws.IgnoreChannel + ReadChannel = streamws.ReadChannel + WriteChannel = streamws.WriteChannel + ReadWriteChannel = streamws.ReadWriteChannel +) + +func IsWebSocketRequest(req *http.Request) bool { + return streamws.IsWebSocketRequest(req) +} + +func IsWebSocketRequestWithStreamCloseProtocol(req *http.Request) bool { + return streamws.IsWebSocketRequestWithStreamCloseProtocol(req) +} + +func IsWebSocketRequestWithTunnelingProtocol(req *http.Request) bool { + return streamws.IsWebSocketRequestWithTunnelingProtocol(req) +} + +func IgnoreReceives(ws *websocket.Conn, timeout time.Duration) { + streamws.IgnoreReceives(ws, timeout) +} + +func IgnoreReceivesWithLogger(logger klog.Logger, ws *websocket.Conn, timeout time.Duration) { + streamws.IgnoreReceivesWithLogger(logger, ws, timeout) +} + +type ChannelProtocolConfig = streamws.ChannelProtocolConfig + +func NewDefaultChannelProtocols(channels []ChannelType) map[string]ChannelProtocolConfig { + return streamws.NewDefaultChannelProtocols(channels) +} + +type Conn = streamws.Conn + +func NewConn(protocols map[string]ChannelProtocolConfig) *Conn { + return streamws.NewConn(protocols) +} + +type ReaderProtocolConfig = streamws.ReaderProtocolConfig + +func NewDefaultReaderProtocols() map[string]ReaderProtocolConfig { + return streamws.NewDefaultReaderProtocols() +} + +type Reader = streamws.Reader + +func NewReader(r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader { + //nolint:logcheck // Intentionally using the non-contextual variant here. + return streamws.NewReader(r, ping, protocols) +} + +func NewReaderWithLogger(logger klog.Logger, r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader { + return streamws.NewReaderWithLogger(logger, r, ping, protocols) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/generated.pb.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/generated.pb.go new file mode 100644 index 0000000000..5be552e1eb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/generated.pb.go @@ -0,0 +1,298 @@ +/* +Copyright 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. +*/ + +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: k8s.io/apimachinery/pkg/util/intstr/generated.proto + +package intstr + +import ( + fmt "fmt" + + io "io" + math_bits "math/bits" +) + +func (m *IntOrString) Reset() { *m = IntOrString{} } + +func (m *IntOrString) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntOrString) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntOrString) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.StrVal) + copy(dAtA[i:], m.StrVal) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.StrVal))) + i-- + dAtA[i] = 0x1a + i = encodeVarintGenerated(dAtA, i, uint64(m.IntVal)) + i-- + dAtA[i] = 0x10 + i = encodeVarintGenerated(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *IntOrString) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Type)) + n += 1 + sovGenerated(uint64(m.IntVal)) + l = len(m.StrVal) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *IntOrString) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntOrString: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntOrString: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= Type(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IntVal", wireType) + } + m.IntVal = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.IntVal |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StrVal", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.StrVal = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenerated + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenerated + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/generated.proto b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/generated.proto new file mode 100644 index 0000000000..e3d26a59a5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/generated.proto @@ -0,0 +1,44 @@ +/* +Copyright 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = "proto2"; + +package k8s.io.apimachinery.pkg.util.intstr; + +// Package-wide variables from generator "generated". +option go_package = "k8s.io/apimachinery/pkg/util/intstr"; + +// IntOrString is a type that can hold an int32 or a string. When used in +// JSON or YAML marshalling and unmarshalling, it produces or consumes the +// inner type. This allows you to have, for example, a JSON field that can +// accept a name or number. +// TODO: Rename to Int32OrString +// +// +protobuf=true +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.util.intstr +message IntOrString { + optional int64 type = 1; + + optional int32 intVal = 2; + + optional string strVal = 3; +} + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/instr_fuzz.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/instr_fuzz.go new file mode 100644 index 0000000000..494325c1f3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/instr_fuzz.go @@ -0,0 +1,42 @@ +//go:build !notest + +/* +Copyright 2020 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 intstr + +import ( + "sigs.k8s.io/randfill" +) + +// RandFill satisfies randfill.NativeSelfFiller +func (intstr *IntOrString) RandFill(c randfill.Continue) { + if intstr == nil { + return + } + if c.Bool() { + intstr.Type = Int + c.Fill(&intstr.IntVal) + intstr.StrVal = "" + } else { + intstr.Type = String + intstr.IntVal = 0 + c.Fill(&intstr.StrVal) + } +} + +// ensure IntOrString implements fuzz.Interface +var _ randfill.NativeSelfFiller = &IntOrString{} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/intstr.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/intstr.go new file mode 100644 index 0000000000..b0ca3803e4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/intstr.go @@ -0,0 +1,260 @@ +/* +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 intstr + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "runtime/debug" + "strconv" + "strings" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "k8s.io/klog/v2" +) + +// IntOrString is a type that can hold an int32 or a string. When used in +// JSON or YAML marshalling and unmarshalling, it produces or consumes the +// inner type. This allows you to have, for example, a JSON field that can +// accept a name or number. +// TODO: Rename to Int32OrString +// +// +protobuf=true +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.util.intstr +type IntOrString struct { + Type Type `protobuf:"varint,1,opt,name=type,casttype=Type"` + IntVal int32 `protobuf:"varint,2,opt,name=intVal"` + StrVal string `protobuf:"bytes,3,opt,name=strVal"` +} + +// Type represents the stored type of IntOrString. +type Type int64 + +const ( + Int Type = iota // The IntOrString holds an int. + String // The IntOrString holds a string. +) + +// FromInt creates an IntOrString object with an int32 value. It is +// your responsibility not to call this method with a value greater +// than int32. +// Deprecated: use FromInt32 instead. +func FromInt(val int) IntOrString { + if val > math.MaxInt32 || val < math.MinInt32 { + //nolint:logcheck // Should not be reached. + klog.Errorf("value: %d overflows int32\n%s\n", val, debug.Stack()) + } + return IntOrString{Type: Int, IntVal: int32(val)} +} + +// FromInt32 creates an IntOrString object with an int32 value. +func FromInt32(val int32) IntOrString { + return IntOrString{Type: Int, IntVal: val} +} + +// FromString creates an IntOrString object with a string value. +func FromString(val string) IntOrString { + return IntOrString{Type: String, StrVal: val} +} + +// Parse the given string and try to convert it to an int32 integer before +// setting it as a string value. +func Parse(val string) IntOrString { + i, err := strconv.ParseInt(val, 10, 32) + if err != nil { + return FromString(val) + } + return FromInt32(int32(i)) +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (intstr *IntOrString) UnmarshalJSON(value []byte) error { + if value[0] == '"' { + intstr.Type = String + return json.Unmarshal(value, &intstr.StrVal) + } + intstr.Type = Int + return json.Unmarshal(value, &intstr.IntVal) +} + +func (intstr *IntOrString) UnmarshalCBOR(value []byte) error { + if err := cbor.Unmarshal(value, &intstr.StrVal); err == nil { + intstr.Type = String + return nil + } + + if err := cbor.Unmarshal(value, &intstr.IntVal); err != nil { + return err + } + + intstr.Type = Int + return nil +} + +// String returns the string value, or the Itoa of the int value. +func (intstr *IntOrString) String() string { + if intstr == nil { + return "" + } + if intstr.Type == String { + return intstr.StrVal + } + return strconv.Itoa(intstr.IntValue()) +} + +// IntValue returns the IntVal if type Int, or if +// it is a String, will attempt a conversion to int, +// returning 0 if a parsing error occurs. +func (intstr *IntOrString) IntValue() int { + if intstr.Type == String { + i, _ := strconv.Atoi(intstr.StrVal) + return i + } + return int(intstr.IntVal) +} + +// MarshalJSON implements the json.Marshaller interface. +func (intstr IntOrString) MarshalJSON() ([]byte, error) { + switch intstr.Type { + case Int: + return json.Marshal(intstr.IntVal) + case String: + return json.Marshal(intstr.StrVal) + default: + return []byte{}, fmt.Errorf("impossible IntOrString.Type") + } +} + +func (intstr IntOrString) MarshalCBOR() ([]byte, error) { + switch intstr.Type { + case Int: + return cbor.Marshal(intstr.IntVal) + case String: + return cbor.Marshal(intstr.StrVal) + default: + return nil, fmt.Errorf("impossible IntOrString.Type") + } +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (IntOrString) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (IntOrString) OpenAPISchemaFormat() string { return "int-or-string" } + +// OpenAPIV3OneOfTypes is used by the kube-openapi generator when constructing +// the OpenAPI v3 spec of this type. +func (IntOrString) OpenAPIV3OneOfTypes() []string { return []string{"integer", "string"} } + +func ValueOrDefault(intOrPercent *IntOrString, defaultValue IntOrString) *IntOrString { + if intOrPercent == nil { + return &defaultValue + } + return intOrPercent +} + +// GetScaledValueFromIntOrPercent is meant to replace GetValueFromIntOrPercent. +// This method returns a scaled value from an IntOrString type. If the IntOrString +// is a percentage string value it's treated as a percentage and scaled appropriately +// in accordance to the total, if it's an int value it's treated as a simple value and +// if it is a string value which is either non-numeric or numeric but lacking a trailing '%' it returns an error. +func GetScaledValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) { + if intOrPercent == nil { + return 0, errors.New("nil value for IntOrString") + } + value, isPercent, err := getIntOrPercentValueSafely(intOrPercent) + if err != nil { + return 0, fmt.Errorf("invalid value for IntOrString: %v", err) + } + if isPercent { + if roundUp { + value = int(math.Ceil(float64(value) * (float64(total)) / 100)) + } else { + value = int(math.Floor(float64(value) * (float64(total)) / 100)) + } + } + return value, nil +} + +// GetValueFromIntOrPercent was deprecated in favor of +// GetScaledValueFromIntOrPercent. This method was treating all int as a numeric value and all +// strings with or without a percent symbol as a percentage value. +// Deprecated +func GetValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) { + if intOrPercent == nil { + return 0, errors.New("nil value for IntOrString") + } + value, isPercent, err := getIntOrPercentValue(intOrPercent) + if err != nil { + return 0, fmt.Errorf("invalid value for IntOrString: %v", err) + } + if isPercent { + if roundUp { + value = int(math.Ceil(float64(value) * (float64(total)) / 100)) + } else { + value = int(math.Floor(float64(value) * (float64(total)) / 100)) + } + } + return value, nil +} + +// getIntOrPercentValue is a legacy function and only meant to be called by GetValueFromIntOrPercent +// For a more correct implementation call getIntOrPercentSafely +func getIntOrPercentValue(intOrStr *IntOrString) (int, bool, error) { + switch intOrStr.Type { + case Int: + return intOrStr.IntValue(), false, nil + case String: + s := strings.Replace(intOrStr.StrVal, "%", "", -1) + v, err := strconv.Atoi(s) + if err != nil { + return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err) + } + return int(v), true, nil + } + return 0, false, fmt.Errorf("invalid type: neither int nor percentage") +} + +func getIntOrPercentValueSafely(intOrStr *IntOrString) (int, bool, error) { + switch intOrStr.Type { + case Int: + return intOrStr.IntValue(), false, nil + case String: + isPercent := false + s := intOrStr.StrVal + if strings.HasSuffix(s, "%") { + isPercent = true + s = strings.TrimSuffix(intOrStr.StrVal, "%") + } else { + return 0, false, fmt.Errorf("invalid type: string is not a percentage") + } + v, err := strconv.Atoi(s) + if err != nil { + return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err) + } + return int(v), isPercent, nil + } + return 0, false, fmt.Errorf("invalid type: neither int nor percentage") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/intstr_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/intstr_test.go new file mode 100644 index 0000000000..eb63854bbc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/intstr_test.go @@ -0,0 +1,568 @@ +/* +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 intstr + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "testing" + + cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct" + "sigs.k8s.io/yaml" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/randfill" +) + +func TestFromInt(t *testing.T) { + i := FromInt(93) + if i.Type != Int || i.IntVal != 93 { + t.Errorf("Expected IntVal=93, got %+v", i) + } +} + +func TestFromInt32(t *testing.T) { + i := FromInt32(93) + if i.Type != Int || i.IntVal != 93 { + t.Errorf("Expected IntVal=93, got %+v", i) + } +} + +func TestFromString(t *testing.T) { + i := FromString("76") + if i.Type != String || i.StrVal != "76" { + t.Errorf("Expected StrVal=\"76\", got %+v", i) + } +} + +type IntOrStringHolder struct { + IOrS IntOrString `json:"val"` +} + +func TestIntOrStringUnmarshalJSON(t *testing.T) { + cases := []struct { + input string + result IntOrString + }{ + {"{\"val\": 123}", FromInt32(123)}, + {"{\"val\": \"123\"}", FromString("123")}, + } + + for _, c := range cases { + var result IntOrStringHolder + if err := json.Unmarshal([]byte(c.input), &result); err != nil { + t.Errorf("Failed to unmarshal input '%v': %v", c.input, err) + } + if result.IOrS != c.result { + t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result) + } + } +} + +func TestIntOrStringMarshalJSON(t *testing.T) { + cases := []struct { + input IntOrString + result string + }{ + {FromInt32(123), "{\"val\":123}"}, + {FromString("123"), "{\"val\":\"123\"}"}, + } + + for _, c := range cases { + input := IntOrStringHolder{c.input} + result, err := json.Marshal(&input) + if err != nil { + t.Errorf("Failed to marshal input '%v': %v", input, err) + } + if string(result) != c.result { + t.Errorf("Failed to marshal input '%v': expected: %+v, got %q", input, c.result, string(result)) + } + } +} + +func TestIntOrStringMarshalJSONUnmarshalYAML(t *testing.T) { + cases := []struct { + input IntOrString + }{ + {FromInt32(123)}, + {FromString("123")}, + } + + for _, c := range cases { + input := IntOrStringHolder{c.input} + jsonMarshalled, err := json.Marshal(&input) + if err != nil { + t.Errorf("1: Failed to marshal input: '%v': %v", input, err) + } + + var result IntOrStringHolder + err = yaml.Unmarshal(jsonMarshalled, &result) + if err != nil { + t.Errorf("2: Failed to unmarshal '%+v': %v", string(jsonMarshalled), err) + } + + if !reflect.DeepEqual(input, result) { + t.Errorf("3: Failed to marshal input '%+v': got %+v", input, result) + } + } +} + +func TestGetIntFromIntOrString(t *testing.T) { + tests := []struct { + input IntOrString + expectErr bool + expectVal int + expectPerc bool + }{ + { + input: FromInt32(200), + expectErr: false, + expectVal: 200, + expectPerc: false, + }, + { + input: FromString("200"), + expectErr: true, + expectPerc: false, + }, + { + input: FromString("30%0"), + expectErr: true, + expectPerc: false, + }, + { + input: FromString("40%"), + expectErr: false, + expectVal: 40, + expectPerc: true, + }, + { + input: FromString("%"), + expectErr: true, + expectPerc: false, + }, + { + input: FromString("a%"), + expectErr: true, + expectPerc: false, + }, + { + input: FromString("a"), + expectErr: true, + expectPerc: false, + }, + { + input: FromString("40#"), + expectErr: true, + expectPerc: false, + }, + { + input: FromString("40%%"), + expectErr: true, + expectPerc: false, + }, + } + for _, test := range tests { + t.Run("", func(t *testing.T) { + value, isPercent, err := getIntOrPercentValueSafely(&test.input) + if test.expectVal != value { + t.Fatalf("expected value does not match, expected: %d, got: %d", test.expectVal, value) + } + if test.expectPerc != isPercent { + t.Fatalf("expected percent does not match, expected: %t, got: %t", test.expectPerc, isPercent) + } + if test.expectErr != (err != nil) { + t.Fatalf("expected error does not match, expected error: %v, got: %v", test.expectErr, err) + } + }) + } + +} + +func TestGetIntFromIntOrPercent(t *testing.T) { + tests := []struct { + input IntOrString + total int + roundUp bool + expectErr bool + expectVal int + }{ + { + input: FromInt32(123), + expectErr: false, + expectVal: 123, + }, + { + input: FromString("90%"), + total: 100, + roundUp: true, + expectErr: false, + expectVal: 90, + }, + { + input: FromString("90%"), + total: 95, + roundUp: true, + expectErr: false, + expectVal: 86, + }, + { + input: FromString("90%"), + total: 95, + roundUp: false, + expectErr: false, + expectVal: 85, + }, + { + input: FromString("%"), + expectErr: true, + }, + { + input: FromString("90#"), + expectErr: true, + }, + { + input: FromString("#%"), + expectErr: true, + }, + { + input: FromString("90"), + expectErr: true, + }, + } + + for i, test := range tests { + t.Logf("test case %d", i) + value, err := GetScaledValueFromIntOrPercent(&test.input, test.total, test.roundUp) + if test.expectErr && err == nil { + t.Errorf("expected error, but got none") + continue + } + if !test.expectErr && err != nil { + t.Errorf("unexpected err: %v", err) + continue + } + if test.expectVal != value { + t.Errorf("expected %v, but got %v", test.expectVal, value) + } + } +} + +func TestGetValueFromIntOrPercentNil(t *testing.T) { + _, err := GetScaledValueFromIntOrPercent(nil, 0, false) + if err == nil { + t.Errorf("expected error got none") + } +} + +func TestParse(t *testing.T) { + tests := []struct { + input string + output IntOrString + }{ + { + input: "0", + output: IntOrString{Type: Int, IntVal: 0}, + }, + { + input: "2147483647", // math.MaxInt32 + output: IntOrString{Type: Int, IntVal: 2147483647}, + }, + { + input: "-2147483648", // math.MinInt32 + output: IntOrString{Type: Int, IntVal: -2147483648}, + }, + { + input: "2147483648", // math.MaxInt32+1 + output: IntOrString{Type: String, StrVal: "2147483648"}, + }, + { + input: "-2147483649", // math.MinInt32-1 + output: IntOrString{Type: String, StrVal: "-2147483649"}, + }, + { + input: "9223372036854775807", // math.MaxInt64 + output: IntOrString{Type: String, StrVal: "9223372036854775807"}, + }, + { + input: "-9223372036854775808", // math.MinInt64 + output: IntOrString{Type: String, StrVal: "-9223372036854775808"}, + }, + { + input: "9223372036854775808", // math.MaxInt64+1 + output: IntOrString{Type: String, StrVal: "9223372036854775808"}, + }, + { + input: "-9223372036854775809", // math.MinInt64-1 + output: IntOrString{Type: String, StrVal: "-9223372036854775809"}, + }, + } + + for i, test := range tests { + t.Logf("test case %d", i) + value := Parse(test.input) + if test.output.Type != value.Type { + t.Errorf("expected type %d (%v), but got %d (%v)", test.output.Type, test.output, value.Type, value) + continue + } + if value.Type == Int && test.output.IntVal != value.IntVal { + t.Errorf("expected int value %d (%v), but got %d (%v)", test.output.IntVal, test.output, value.IntVal, value) + continue + } + if value.Type == String && test.output.StrVal != value.StrVal { + t.Errorf("expected string value %q (%v), but got %q (%v)", test.output.StrVal, test.output, value.StrVal, value) + } + } +} + +func TestMarshalCBOR(t *testing.T) { + for _, tc := range []struct { + in IntOrString + want []byte + assertOnError func(*testing.T, error) + }{ + { + in: IntOrString{Type: 42}, + assertOnError: func(t *testing.T, err error) { + if err == nil { + t.Fatal("expected non-nil error") + } + const want = "impossible IntOrString.Type" + if got := err.Error(); got != want { + t.Fatalf("want error message %q, got %q", want, got) + } + }, + }, + { + in: FromString(""), + want: []byte{0x40}, + }, + { + in: FromString("abc"), + want: []byte{0x43, 'a', 'b', 'c'}, + }, + { + in: FromInt32(0), // min positive integer representable in one byte + want: []byte{0x00}, + }, + { + in: FromInt32(23), // max positive integer representable in one byte + want: []byte{0x17}, + }, + { + in: FromInt32(24), // min positive integer representable in two bytes + want: []byte{0x18, 0x18}, + }, + { + in: FromInt32(math.MaxUint8), // max positive integer representable in two bytes + want: []byte{0x18, 0xff}, + }, + { + in: FromInt32(math.MaxUint8 + 1), // min positive integer representable in three bytes + want: []byte{0x19, 0x01, 0x00}, + }, + { + in: FromInt32(math.MaxUint16), // max positive integer representable in three bytes + want: []byte{0x19, 0xff, 0xff}, + }, + { + in: FromInt32(math.MaxUint16 + 1), // min positive integer representable in five bytes + want: []byte{0x1a, 0x00, 0x01, 0x00, 0x00}, + }, + { + in: FromInt32(math.MaxInt32), // max positive integer representable by Go int32 + want: []byte{0x1a, 0x7f, 0xff, 0xff, 0xff}, + }, + { + in: FromInt32(-1), // max negative integer representable in one byte + want: []byte{0x20}, + }, + { + in: FromInt32(-24), // min negative integer representable in one byte + want: []byte{0x37}, + }, + { + in: FromInt32(-1 - 24), // max negative integer representable in two bytes + want: []byte{0x38, 0x18}, + }, + { + in: FromInt32(-1 - math.MaxUint8), // min negative integer representable in two bytes + want: []byte{0x38, 0xff}, + }, + { + in: FromInt32(-2 - math.MaxUint8), // max negative integer representable in three bytes + want: []byte{0x39, 0x01, 0x00}, + }, + { + in: FromInt32(-1 - math.MaxUint16), // min negative integer representable in three bytes + want: []byte{0x39, 0xff, 0xff}, + }, + { + in: FromInt32(-2 - math.MaxUint16), // max negative integer representable in five bytes + want: []byte{0x3a, 0x00, 0x01, 0x00, 0x00}, + }, + { + in: FromInt32(math.MinInt32), // min negative integer representable by Go int32 + want: []byte{0x3a, 0x7f, 0xff, 0xff, 0xff}, + }, + } { + t.Run(fmt.Sprintf("{Type:%d,IntVal:%d,StrVal:%q}", tc.in.Type, tc.in.IntVal, tc.in.StrVal), func(t *testing.T) { + got, err := tc.in.MarshalCBOR() + if tc.assertOnError != nil { + tc.assertOnError(t, err) + } else if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Errorf("unexpected difference between expected and actual output:\n%s", diff) + } + }) + } +} + +func TestUnmarshalCBOR(t *testing.T) { + for _, tc := range []struct { + in []byte + want IntOrString + assertOnError func(*testing.T, error) + }{ + { + in: []byte{0xa0}, // {} + assertOnError: func(t *testing.T, err error) { + if err == nil { + t.Fatal("expected non-nil error") + } + const want = "cbor: cannot unmarshal map into Go value of type int32" + if got := err.Error(); got != want { + t.Fatalf("want error message %q, got %q", want, got) + } + + }, + }, + { + in: []byte{0x40}, + want: FromString(""), + }, + { + in: []byte{0x43, 'a', 'b', 'c'}, + want: FromString("abc"), + }, + { + in: []byte{0x00}, + want: FromInt32(0), // min positive integer representable in one byte + }, + { + in: []byte{0x17}, + want: FromInt32(23), // max positive integer representable in one byte + }, + { + in: []byte{0x18, 0x18}, + want: FromInt32(24), // min positive integer representable in two bytes + }, + { + in: []byte{0x18, 0xff}, + want: FromInt32(math.MaxUint8), // max positive integer representable in two bytes + }, + { + in: []byte{0x19, 0x01, 0x00}, + want: FromInt32(math.MaxUint8 + 1), // min positive integer representable in three bytes + }, + { + in: []byte{0x19, 0xff, 0xff}, + want: FromInt32(math.MaxUint16), // max positive integer representable in three bytes + }, + { + in: []byte{0x1a, 0x00, 0x01, 0x00, 0x00}, + want: FromInt32(math.MaxUint16 + 1), // min positive integer representable in five bytes + }, + { + in: []byte{0x1a, 0x7f, 0xff, 0xff, 0xff}, + want: FromInt32(math.MaxInt32), // max positive integer representable by Go int32 + }, + { + in: []byte{0x20}, + want: FromInt32(-1), // max negative integer representable in one byte + }, + { + in: []byte{0x37}, + want: FromInt32(-24), // min negative integer representable in one byte + }, + { + in: []byte{0x38, 0x18}, + want: FromInt32(-1 - 24), // max negative integer representable in two bytes + }, + { + in: []byte{0x38, 0xff}, + want: FromInt32(-1 - math.MaxUint8), // min negative integer representable in two bytes + }, + { + in: []byte{0x39, 0x01, 0x00}, + want: FromInt32(-2 - math.MaxUint8), // max negative integer representable in three bytes + }, + { + in: []byte{0x39, 0xff, 0xff}, + want: FromInt32(-1 - math.MaxUint16), // min negative integer representable in three bytes + }, + { + in: []byte{0x3a, 0x00, 0x01, 0x00, 0x00}, + want: FromInt32(-2 - math.MaxUint16), // max negative integer representable in five bytes + }, + { + in: []byte{0x3a, 0x7f, 0xff, 0xff, 0xff}, + want: FromInt32(math.MinInt32), // min negative integer representable by Go int32 + }, + } { + t.Run(fmt.Sprintf("{Type:%d,IntVal:%d,StrVal:%q}", tc.want.Type, tc.want.IntVal, tc.want.StrVal), func(t *testing.T) { + var got IntOrString + err := got.UnmarshalCBOR(tc.in) + if tc.assertOnError != nil { + tc.assertOnError(t, err) + } else if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if diff := cmp.Diff(got, tc.want); diff != "" { + t.Errorf("unexpected difference between expected and actual output:\n%s", diff) + } + }) + } +} + +func TestIntOrStringRoundtripCBOR(t *testing.T) { + fuzzer := randfill.New() + for i := 0; i < 500; i++ { + var initial, final IntOrString + fuzzer.Fill(&initial) + b, err := cbor.Marshal(initial) + if err != nil { + t.Errorf("error encoding %v: %v", initial, err) + continue + } + err = cbor.Unmarshal(b, &final) + if err != nil { + t.Errorf("%v: error decoding %v: %v", initial, string(b), err) + } + if diff := cmp.Diff(initial, final); diff != "" { + diag, err := cbor.Diagnose(b) + if err != nil { + t.Logf("failed to produce diagnostic encoding of 0x%x: %v", b, err) + } + t.Errorf("unexpected diff:\n%s\ncbor: %s", diff, diag) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/zz_generated.model_name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/zz_generated.model_name.go new file mode 100644 index 0000000000..b2d6e0ae3c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/intstr/zz_generated.model_name.go @@ -0,0 +1,27 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package intstr + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in IntOrString) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.util.intstr.IntOrString" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/json/json.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/json/json.go new file mode 100644 index 0000000000..55dba361c3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/json/json.go @@ -0,0 +1,121 @@ +/* +Copyright 2015 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 json + +import ( + "encoding/json" + "fmt" + "io" + + kjson "sigs.k8s.io/json" +) + +// NewEncoder delegates to json.NewEncoder +// It is only here so this package can be a drop-in for common encoding/json uses +func NewEncoder(w io.Writer) *json.Encoder { + return json.NewEncoder(w) +} + +// Marshal delegates to json.Marshal +// It is only here so this package can be a drop-in for common encoding/json uses +func Marshal(v interface{}) ([]byte, error) { + return json.Marshal(v) +} + +// limit recursive depth to prevent stack overflow errors +const maxDepth = 10000 + +// Unmarshal unmarshals the given data. +// Object keys are case-sensitive. +// Numbers decoded into interface{} fields are converted to int64 or float64. +func Unmarshal(data []byte, v interface{}) error { + return kjson.UnmarshalCaseSensitivePreserveInts(data, v) +} + +// ConvertInterfaceNumbers converts any json.Number values to int64 or float64. +// Values which are map[string]interface{} or []interface{} are recursively visited +func ConvertInterfaceNumbers(v *interface{}, depth int) error { + var err error + switch v2 := (*v).(type) { + case json.Number: + *v, err = convertNumber(v2) + case map[string]interface{}: + err = ConvertMapNumbers(v2, depth+1) + case []interface{}: + err = ConvertSliceNumbers(v2, depth+1) + } + return err +} + +// ConvertMapNumbers traverses the map, converting any json.Number values to int64 or float64. +// values which are map[string]interface{} or []interface{} are recursively visited +func ConvertMapNumbers(m map[string]interface{}, depth int) error { + if depth > maxDepth { + return fmt.Errorf("exceeded max depth of %d", maxDepth) + } + + var err error + for k, v := range m { + switch v := v.(type) { + case json.Number: + m[k], err = convertNumber(v) + case map[string]interface{}: + err = ConvertMapNumbers(v, depth+1) + case []interface{}: + err = ConvertSliceNumbers(v, depth+1) + } + if err != nil { + return err + } + } + return nil +} + +// ConvertSliceNumbers traverses the slice, converting any json.Number values to int64 or float64. +// values which are map[string]interface{} or []interface{} are recursively visited +func ConvertSliceNumbers(s []interface{}, depth int) error { + if depth > maxDepth { + return fmt.Errorf("exceeded max depth of %d", maxDepth) + } + + var err error + for i, v := range s { + switch v := v.(type) { + case json.Number: + s[i], err = convertNumber(v) + case map[string]interface{}: + err = ConvertMapNumbers(v, depth+1) + case []interface{}: + err = ConvertSliceNumbers(v, depth+1) + } + if err != nil { + return err + } + } + return nil +} + +// convertNumber converts a json.Number to an int64 or float64, or returns an error +func convertNumber(n json.Number) (interface{}, error) { + // Attempt to convert to an int64 first + if i, err := n.Int64(); err == nil { + return i, nil + } + // Return a float64 (default json.Decode() behavior) + // An overflow will return an error + return n.Float64() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/json/json_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/json/json_test.go new file mode 100644 index 0000000000..8caaa274a7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/json/json_test.go @@ -0,0 +1,418 @@ +//go:build go1.8 + +/* +Copyright 2015 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 json + +import ( + gojson "encoding/json" + + "fmt" + "math" + "reflect" + "strconv" + "strings" + "testing" +) + +func TestEvaluateTypes(t *testing.T) { + testCases := []struct { + In string + Data interface{} + Out string + Err bool + }{ + // Invalid syntaxes + { + In: `x`, + Err: true, + }, + { + In: ``, + Err: true, + }, + + // Null + { + In: `null`, + Data: nil, + Out: `null`, + }, + // Booleans + { + In: `true`, + Data: true, + Out: `true`, + }, + { + In: `false`, + Data: false, + Out: `false`, + }, + + // Integers + { + In: `0`, + Data: int64(0), + Out: `0`, + }, + { + In: `-0`, + Data: int64(-0), + Out: `0`, + }, + { + In: `1`, + Data: int64(1), + Out: `1`, + }, + { + In: `2147483647`, + Data: int64(math.MaxInt32), + Out: `2147483647`, + }, + { + In: `-2147483648`, + Data: int64(math.MinInt32), + Out: `-2147483648`, + }, + { + In: `9223372036854775807`, + Data: int64(math.MaxInt64), + Out: `9223372036854775807`, + }, + { + In: `-9223372036854775808`, + Data: int64(math.MinInt64), + Out: `-9223372036854775808`, + }, + + // Int overflow + { + In: `9223372036854775808`, // MaxInt64 + 1 + Data: float64(9223372036854775808), + Out: `9223372036854776000`, + }, + { + In: `-9223372036854775809`, // MinInt64 - 1 + Data: float64(math.MinInt64), + Out: `-9223372036854776000`, + }, + + // Floats + { + In: `0.0`, + Data: float64(0), + Out: `0`, + }, + { + In: `-0.0`, + Data: float64(-0.0), //nolint:staticcheck // SA4026: in Go, the floating-point literal '-0.0' is the same as '0.0' + Out: `-0`, + }, + { + In: `0.5`, + Data: float64(0.5), + Out: `0.5`, + }, + { + In: `1e3`, + Data: float64(1e3), + Out: `1000`, + }, + { + In: `1.5`, + Data: float64(1.5), + Out: `1.5`, + }, + { + In: `-0.3`, + Data: float64(-.3), + Out: `-0.3`, + }, + { + // Largest representable float32 + In: `3.40282346638528859811704183484516925440e+38`, + Data: float64(math.MaxFloat32), + Out: strconv.FormatFloat(math.MaxFloat32, 'g', -1, 64), + }, + { + // Smallest float32 without losing precision + In: `1.175494351e-38`, + Data: float64(1.175494351e-38), + Out: `1.175494351e-38`, + }, + { + // float32 closest to zero + In: `1.401298464324817070923729583289916131280e-45`, + Data: float64(math.SmallestNonzeroFloat32), + Out: strconv.FormatFloat(math.SmallestNonzeroFloat32, 'g', -1, 64), + }, + { + // Largest representable float64 + In: `1.797693134862315708145274237317043567981e+308`, + Data: float64(math.MaxFloat64), + Out: strconv.FormatFloat(math.MaxFloat64, 'g', -1, 64), + }, + { + // Closest to zero without losing precision + In: `2.2250738585072014e-308`, + Data: float64(2.2250738585072014e-308), + Out: `2.2250738585072014e-308`, + }, + + { + // float64 closest to zero + In: `4.940656458412465441765687928682213723651e-324`, + Data: float64(math.SmallestNonzeroFloat64), + Out: strconv.FormatFloat(math.SmallestNonzeroFloat64, 'g', -1, 64), + }, + + { + // math.MaxFloat64 + 2 overflow + In: `1.7976931348623159e+308`, + Err: true, + }, + + // Strings + { + In: `""`, + Data: string(""), + Out: `""`, + }, + { + In: `"0"`, + Data: string("0"), + Out: `"0"`, + }, + { + In: `"A"`, + Data: string("A"), + Out: `"A"`, + }, + { + In: `"Iñtërnâtiônàlizætiøn"`, + Data: string("Iñtërnâtiônàlizætiøn"), + Out: `"Iñtërnâtiônàlizætiøn"`, + }, + + // Arrays + { + In: `[]`, + Data: []interface{}{}, + Out: `[]`, + }, + { + In: `[` + strings.Join([]string{ + `null`, + `true`, + `false`, + `0`, + `9223372036854775807`, + `0.0`, + `0.5`, + `1.0`, + `1.797693134862315708145274237317043567981e+308`, + `"0"`, + `"A"`, + `"Iñtërnâtiônàlizætiøn"`, + `[null,true,1,1.0,1.5]`, + `{"boolkey":true,"floatkey":1.0,"intkey":1,"nullkey":null}`, + }, ",") + `]`, + Data: []interface{}{ + nil, + true, + false, + int64(0), + int64(math.MaxInt64), + float64(0.0), + float64(0.5), + float64(1.0), + float64(math.MaxFloat64), + string("0"), + string("A"), + string("Iñtërnâtiônàlizætiøn"), + []interface{}{nil, true, int64(1), float64(1.0), float64(1.5)}, + map[string]interface{}{"nullkey": nil, "boolkey": true, "intkey": int64(1), "floatkey": float64(1.0)}, + }, + Out: `[` + strings.Join([]string{ + `null`, + `true`, + `false`, + `0`, + `9223372036854775807`, + `0`, + `0.5`, + `1`, + strconv.FormatFloat(math.MaxFloat64, 'g', -1, 64), + `"0"`, + `"A"`, + `"Iñtërnâtiônàlizætiøn"`, + `[null,true,1,1,1.5]`, + `{"boolkey":true,"floatkey":1,"intkey":1,"nullkey":null}`, // gets alphabetized by Marshal + }, ",") + `]`, + }, + + // Maps + { + In: `{}`, + Data: map[string]interface{}{}, + Out: `{}`, + }, + { + In: `{"boolkey":true,"floatkey":1.0,"intkey":1,"nullkey":null}`, + Data: map[string]interface{}{"nullkey": nil, "boolkey": true, "intkey": int64(1), "floatkey": float64(1.0)}, + Out: `{"boolkey":true,"floatkey":1,"intkey":1,"nullkey":null}`, // gets alphabetized by Marshal + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d_map", i), func(t *testing.T) { + // decode the input as a map item + inputJSON := fmt.Sprintf(`{"data":%s}`, tc.In) + expectedJSON := fmt.Sprintf(`{"data":%s}`, tc.Out) + m := map[string]interface{}{} + err := Unmarshal([]byte(inputJSON), &m) + if tc.Err && err != nil { + // Expected error + return + } + if err != nil { + t.Fatalf("%s: error decoding: %v", tc.In, err) + } + if tc.Err { + t.Fatalf("%s: expected error, got none", tc.In) + } + data, ok := m["data"] + if !ok { + t.Fatalf("%s: decoded object missing data key: %#v", tc.In, m) + } + if !reflect.DeepEqual(tc.Data, data) { + t.Fatalf("%s: expected\n\t%#v (%v), got\n\t%#v (%v)", tc.In, tc.Data, reflect.TypeOf(tc.Data), data, reflect.TypeOf(data)) + } + + outputJSON, err := Marshal(m) + if err != nil { + t.Fatalf("%s: error encoding: %v", tc.In, err) + } + + if expectedJSON != string(outputJSON) { + t.Fatalf("%s: expected\n\t%s, got\n\t%s", tc.In, expectedJSON, string(outputJSON)) + } + }) + + t.Run(fmt.Sprintf("%d_slice", i), func(t *testing.T) { + // decode the input as an array item + inputJSON := fmt.Sprintf(`[0,%s]`, tc.In) + expectedJSON := fmt.Sprintf(`[0,%s]`, tc.Out) + m := []interface{}{} + err := Unmarshal([]byte(inputJSON), &m) + if tc.Err && err != nil { + // Expected error + return + } + if err != nil { + t.Fatalf("%s: error decoding: %v", tc.In, err) + } + if tc.Err { + t.Fatalf("%s: expected error, got none", tc.In) + } + if len(m) != 2 { + t.Fatalf("%s: decoded object wasn't the right length: %#v", tc.In, m) + } + data := m[1] + if !reflect.DeepEqual(tc.Data, data) { + t.Fatalf("%s: expected\n\t%#v (%v), got\n\t%#v (%v)", tc.In, tc.Data, reflect.TypeOf(tc.Data), data, reflect.TypeOf(data)) + } + + outputJSON, err := Marshal(m) + if err != nil { + t.Fatalf("%s: error encoding: %v", tc.In, err) + } + + if expectedJSON != string(outputJSON) { + t.Fatalf("%s: expected\n\t%s, got\n\t%s", tc.In, expectedJSON, string(outputJSON)) + } + }) + + t.Run(fmt.Sprintf("%d_raw", i), func(t *testing.T) { + // decode the input as a standalone object + inputJSON := tc.In + expectedJSON := tc.Out + var m interface{} + err := Unmarshal([]byte(inputJSON), &m) + if tc.Err && err != nil { + // Expected error + return + } + if err != nil { + t.Fatalf("%s: error decoding: %v", tc.In, err) + } + if tc.Err { + t.Fatalf("%s: expected error, got none", tc.In) + } + data := m + if !reflect.DeepEqual(tc.Data, data) { + t.Fatalf("%s: expected\n\t%#v (%v), got\n\t%#v (%v)", tc.In, tc.Data, reflect.TypeOf(tc.Data), data, reflect.TypeOf(data)) + } + + outputJSON, err := Marshal(m) + if err != nil { + t.Fatalf("%s: error encoding: %v", tc.In, err) + } + + if expectedJSON != string(outputJSON) { + t.Fatalf("%s: expected\n\t%s, got\n\t%s", tc.In, expectedJSON, string(outputJSON)) + } + }) + } +} + +func TestUnmarshalNil(t *testing.T) { + { + var v *interface{} + err := Unmarshal([]byte(`0`), v) + goerr := gojson.Unmarshal([]byte(`0`), v) + if err == nil || goerr == nil || err.Error() != goerr.Error() { + t.Fatalf("expected error matching stdlib, got %v, %v", err, goerr) + } else { + t.Log(err) + } + } + + { + var v *[]interface{} + err := Unmarshal([]byte(`[]`), v) + goerr := gojson.Unmarshal([]byte(`[]`), v) + if err == nil || goerr == nil || err.Error() != goerr.Error() { + t.Fatalf("expected error matching stdlib, got %v, %v", err, goerr) + } else { + t.Log(err) + } + } + + { + var v *map[string]interface{} + err := Unmarshal([]byte(`{}`), v) + goerr := gojson.Unmarshal([]byte(`{}`), v) + if err == nil || goerr == nil || err.Error() != goerr.Error() { + t.Fatalf("expected error matching stdlib, got %v, %v", err, goerr) + } else { + t.Log(err) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go new file mode 100644 index 0000000000..786c16e27a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go @@ -0,0 +1,160 @@ +/* +Copyright 2017 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 jsonmergepatch + +import ( + "fmt" + "reflect" + + "gopkg.in/evanphx/json-patch.v4" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/apimachinery/pkg/util/mergepatch" +) + +// Create a 3-way merge patch based-on JSON merge patch. +// Calculate addition-and-change patch between current and modified. +// Calculate deletion patch between original and modified. +func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...mergepatch.PreconditionFunc) ([]byte, error) { + if len(original) == 0 { + original = []byte(`{}`) + } + if len(modified) == 0 { + modified = []byte(`{}`) + } + if len(current) == 0 { + current = []byte(`{}`) + } + + addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified) + if err != nil { + return nil, err + } + // Only keep addition and changes + addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false) + if err != nil { + return nil, err + } + + deletePatch, err := jsonpatch.CreateMergePatch(original, modified) + if err != nil { + return nil, err + } + // Only keep deletion + deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true) + if err != nil { + return nil, err + } + + hasConflicts, err := mergepatch.HasConflicts(addAndChangePatchObj, deletePatchObj) + if err != nil { + return nil, err + } + if hasConflicts { + return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(addAndChangePatchObj), mergepatch.ToYAMLOrError(deletePatchObj)) + } + patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch) + if err != nil { + return nil, err + } + + var patchMap map[string]interface{} + err = json.Unmarshal(patch, &patchMap) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal patch for precondition check: %s", patch) + } + meetPreconditions, err := meetPreconditions(patchMap, fns...) + if err != nil { + return nil, err + } + if !meetPreconditions { + return nil, mergepatch.NewErrPreconditionFailed(patchMap) + } + + return patch, nil +} + +// keepOrDeleteNullInJsonPatch takes a json-encoded byte array and a boolean. +// It returns a filtered object and its corresponding json-encoded byte array. +// It is a wrapper of func keepOrDeleteNullInObj +func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) { + var patchMap map[string]interface{} + err := json.Unmarshal(patch, &patchMap) + if err != nil { + return nil, nil, err + } + filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull) + if err != nil { + return nil, nil, err + } + o, err := json.Marshal(filteredMap) + return o, filteredMap, err +} + +// keepOrDeleteNullInObj will keep only the null value and delete all the others, +// if keepNull is true. Otherwise, it will delete all the null value and keep the others. +func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) { + filteredMap := make(map[string]interface{}) + var err error + for key, val := range m { + switch { + case keepNull && val == nil: + filteredMap[key] = nil + case val != nil: + switch typedVal := val.(type) { + case map[string]interface{}: + // Explicitly-set empty maps are treated as values instead of empty patches + if len(typedVal) == 0 { + if !keepNull { + filteredMap[key] = typedVal + } + continue + } + + var filteredSubMap map[string]interface{} + filteredSubMap, err = keepOrDeleteNullInObj(typedVal, keepNull) + if err != nil { + return nil, err + } + + // If the returned filtered submap was empty, this is an empty patch for the entire subdict, so the key + // should not be set + if len(filteredSubMap) != 0 { + filteredMap[key] = filteredSubMap + } + + case []interface{}, string, float64, bool, int64, nil: + // Lists are always replaced in Json, no need to check each entry in the list. + if !keepNull { + filteredMap[key] = val + } + default: + return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal)) + } + } + } + return filteredMap, nil +} + +func meetPreconditions(patchObj map[string]interface{}, fns ...mergepatch.PreconditionFunc) (bool, error) { + // Apply the preconditions to the patch, and return an error if any of them fail. + for _, fn := range fns { + if !fn(patchObj) { + return false, fmt.Errorf("precondition failed for: %v", patchObj) + } + } + return true, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go new file mode 100644 index 0000000000..1a9eb0f874 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch_test.go @@ -0,0 +1,747 @@ +/* +Copyright 2017 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 jsonmergepatch + +import ( + "fmt" + "reflect" + "testing" + + jsonpatch "gopkg.in/evanphx/json-patch.v4" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/utils/dump" + "sigs.k8s.io/yaml" +) + +type FilterNullTestCases struct { + TestCases []FilterNullTestCase +} + +type FilterNullTestCase struct { + Description string + OriginalObj map[string]interface{} + ExpectedWithNull map[string]interface{} + ExpectedWithoutNull map[string]interface{} +} + +var filterNullTestCaseData = []byte(` +testCases: + - description: nil original + originalObj: {} + expectedWithNull: {} + expectedWithoutNull: {} + - description: simple map + originalObj: + nilKey: null + nonNilKey: foo + expectedWithNull: + nilKey: null + expectedWithoutNull: + nonNilKey: foo + - description: simple map with all nil values + originalObj: + nilKey1: null + nilKey2: null + expectedWithNull: + nilKey1: null + nilKey2: null + expectedWithoutNull: {} + - description: simple map with all non-nil values + originalObj: + nonNilKey1: foo + nonNilKey2: bar + expectedWithNull: {} + expectedWithoutNull: + nonNilKey1: foo + nonNilKey2: bar + - description: nested map + originalObj: + mapKey: + nilKey: null + nonNilKey: foo + expectedWithNull: + mapKey: + nilKey: null + expectedWithoutNull: + mapKey: + nonNilKey: foo + - description: nested map that all subkeys are nil + originalObj: + mapKey: + nilKey1: null + nilKey2: null + expectedWithNull: + mapKey: + nilKey1: null + nilKey2: null + expectedWithoutNull: {} + - description: nested map that all subkeys are non-nil + originalObj: + mapKey: + nonNilKey1: foo + nonNilKey2: bar + expectedWithNull: {} + expectedWithoutNull: + mapKey: + nonNilKey1: foo + nonNilKey2: bar + - description: explicitly empty map as value + originalObj: + mapKey: {} + expectedWithNull: {} + expectedWithoutNull: + mapKey: {} + - description: explicitly empty nested map + originalObj: + mapKey: + nonNilKey: {} + expectedWithNull: {} + expectedWithoutNull: + mapKey: + nonNilKey: {} + - description: multiple expliclty empty nested maps + originalObj: + mapKey: + nonNilKey1: {} + nonNilKey2: {} + expectedWithNull: {} + expectedWithoutNull: + mapKey: + nonNilKey1: {} + nonNilKey2: {} + - description: nested map with non-null value as empty map + originalObj: + mapKey: + nonNilKey: {} + nilKey: null + expectedWithNull: + mapKey: + nilKey: null + expectedWithoutNull: + mapKey: + nonNilKey: {} + - description: empty list + originalObj: + listKey: [] + expectedWithNull: {} + expectedWithoutNull: + listKey: [] + - description: list of primitives + originalObj: + listKey: + - 1 + - 2 + expectedWithNull: {} + expectedWithoutNull: + listKey: + - 1 + - 2 + - description: list of maps + originalObj: + listKey: + - k1: v1 + - k2: null + - k3: v3 + k4: null + expectedWithNull: {} + expectedWithoutNull: + listKey: + - k1: v1 + - k2: null + - k3: v3 + k4: null + - description: list of different types + originalObj: + listKey: + - k1: v1 + - k2: null + - v3 + expectedWithNull: {} + expectedWithoutNull: + listKey: + - k1: v1 + - k2: null + - v3 +`) + +func TestKeepOrDeleteNullInObj(t *testing.T) { + tc := FilterNullTestCases{} + err := yaml.Unmarshal(filterNullTestCaseData, &tc) + if err != nil { + t.Fatalf("can't unmarshal test cases: %s\n", err) + } + + for _, test := range tc.TestCases { + resultWithNull, err := keepOrDeleteNullInObj(test.OriginalObj, true) + if err != nil { + t.Errorf("Failed in test case %q when trying to keep null values: %s", test.Description, err) + } + if !reflect.DeepEqual(test.ExpectedWithNull, resultWithNull) { + t.Errorf("Failed in test case %q when trying to keep null values:\nexpected expectedWithNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithNull, resultWithNull) + } + + resultWithoutNull, err := keepOrDeleteNullInObj(test.OriginalObj, false) + if err != nil { + t.Errorf("Failed in test case %q when trying to keep non-null values: %s", test.Description, err) + } + if !reflect.DeepEqual(test.ExpectedWithoutNull, resultWithoutNull) { + t.Errorf("Failed in test case %q when trying to keep non-null values:\n expected expectedWithoutNull:\n%+v\nbut got:\n%+v\n", test.Description, test.ExpectedWithoutNull, resultWithoutNull) + } + } +} + +type JSONMergePatchTestCases struct { + TestCases []JSONMergePatchTestCase +} + +type JSONMergePatchTestCase struct { + Description string + JSONMergePatchTestCaseData +} + +type JSONMergePatchTestCaseData struct { + // Original is the original object (last-applied config in annotation) + Original map[string]interface{} + // Modified is the modified object (new config we want) + Modified map[string]interface{} + // Current is the current object (live config in the server) + Current map[string]interface{} + // ThreeWay is the expected three-way merge patch + ThreeWay map[string]interface{} + // Result is the expected object after applying the three-way patch on current object. + Result map[string]interface{} +} + +var createJSONMergePatchTestCaseData = []byte(` +testCases: + - description: nil original + modified: + name: 1 + value: 1 + current: + name: 1 + other: a + threeWay: + value: 1 + result: + name: 1 + value: 1 + other: a + - description: nil patch + original: + name: 1 + modified: + name: 1 + current: + name: 1 + threeWay: + {} + result: + name: 1 + - description: add field to map + original: + name: 1 + modified: + name: 1 + value: 1 + current: + name: 1 + other: a + threeWay: + value: 1 + result: + name: 1 + value: 1 + other: a + - description: add field to map with conflict + original: + name: 1 + modified: + name: 1 + value: 1 + current: + name: a + other: a + threeWay: + name: 1 + value: 1 + result: + name: 1 + value: 1 + other: a + - description: add field and delete field from map + original: + name: 1 + modified: + value: 1 + current: + name: 1 + other: a + threeWay: + name: null + value: 1 + result: + value: 1 + other: a + - description: add field and delete field from map with conflict + original: + name: 1 + modified: + value: 1 + current: + name: a + other: a + threeWay: + name: null + value: 1 + result: + value: 1 + other: a + - description: delete field from nested map + original: + simpleMap: + key1: 1 + key2: 1 + modified: + simpleMap: + key1: 1 + current: + simpleMap: + key1: 1 + key2: 1 + other: a + threeWay: + simpleMap: + key2: null + result: + simpleMap: + key1: 1 + other: a + - description: delete field from nested map with conflict + original: + simpleMap: + key1: 1 + key2: 1 + modified: + simpleMap: + key1: 1 + current: + simpleMap: + key1: a + key2: 1 + other: a + threeWay: + simpleMap: + key1: 1 + key2: null + result: + simpleMap: + key1: 1 + other: a + - description: delete all fields from map + original: + name: 1 + value: 1 + modified: {} + current: + name: 1 + value: 1 + other: a + threeWay: + name: null + value: null + result: + other: a + - description: delete all fields from map with conflict + original: + name: 1 + value: 1 + modified: {} + current: + name: 1 + value: a + other: a + threeWay: + name: null + value: null + result: + other: a + - description: add field and delete all fields from map + original: + name: 1 + value: 1 + modified: + other: a + current: + name: 1 + value: 1 + other: a + threeWay: + name: null + value: null + result: + other: a + - description: add field and delete all fields from map with conflict + original: + name: 1 + value: 1 + modified: + other: a + current: + name: 1 + value: 1 + other: b + threeWay: + name: null + value: null + other: a + result: + other: a + - description: replace list of scalars + original: + intList: + - 1 + - 2 + modified: + intList: + - 2 + - 3 + current: + intList: + - 1 + - 2 + threeWay: + intList: + - 2 + - 3 + result: + intList: + - 2 + - 3 + - description: replace list of scalars with conflict + original: + intList: + - 1 + - 2 + modified: + intList: + - 2 + - 3 + current: + intList: + - 1 + - 4 + threeWay: + intList: + - 2 + - 3 + result: + intList: + - 2 + - 3 + - description: patch with different scalar type + original: + foo: 1 + modified: + foo: true + current: + foo: 1 + bar: 2 + threeWay: + foo: true + result: + foo: true + bar: 2 + - description: patch from scalar to list + original: + foo: 0 + modified: + foo: + - 1 + - 2 + current: + foo: 0 + bar: 2 + threeWay: + foo: + - 1 + - 2 + result: + foo: + - 1 + - 2 + bar: 2 + - description: patch from list to scalar + original: + foo: + - 1 + - 2 + modified: + foo: 0 + current: + foo: + - 1 + - 2 + bar: 2 + threeWay: + foo: 0 + result: + foo: 0 + bar: 2 + - description: patch from scalar to map + original: + foo: 0 + modified: + foo: + baz: 1 + current: + foo: 0 + bar: 2 + threeWay: + foo: + baz: 1 + result: + foo: + baz: 1 + bar: 2 + - description: patch from map to scalar + original: + foo: + baz: 1 + modified: + foo: 0 + current: + foo: + baz: 1 + bar: 2 + threeWay: + foo: 0 + result: + foo: 0 + bar: 2 + - description: patch from map to list + original: + foo: + baz: 1 + modified: + foo: + - 1 + - 2 + current: + foo: + baz: 1 + bar: 2 + threeWay: + foo: + - 1 + - 2 + result: + foo: + - 1 + - 2 + bar: 2 + - description: patch from list to map + original: + foo: + - 1 + - 2 + modified: + foo: + baz: 0 + current: + foo: + - 1 + - 2 + bar: 2 + threeWay: + foo: + baz: 0 + result: + foo: + baz: 0 + bar: 2 + - description: patch with different nested types + original: + foo: + - a: true + - 2 + - false + modified: + foo: + - 1 + - false + - b: 1 + current: + foo: + - a: true + - 2 + - false + bar: 0 + threeWay: + foo: + - 1 + - false + - b: 1 + result: + foo: + - 1 + - false + - b: 1 + bar: 0 + - description: patch array with nil + original: + foo: + - a: true + - null + - false + bar: [] + drop: + - 1 + modified: + foo: + - 1 + - false + - b: 1 + bar: + - c + - null + - null + - a + drop: + - null + current: + foo: + - a: true + - 2 + - false + bar: + - c + - null + - null + - a + drop: + threeWay: + foo: + - 1 + - false + - b: 1 + drop: + - null + result: + foo: + - 1 + - false + - b: 1 + drop: + - null + bar: + - c + - null + - null + - a +`) + +func TestCreateThreeWayJSONMergePatch(t *testing.T) { + tc := JSONMergePatchTestCases{} + err := yaml.Unmarshal(createJSONMergePatchTestCaseData, &tc) + if err != nil { + t.Errorf("can't unmarshal test cases: %s\n", err) + return + } + + for _, c := range tc.TestCases { + testThreeWayPatch(t, c) + } +} + +func testThreeWayPatch(t *testing.T, c JSONMergePatchTestCase) { + original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c) + actual, err := CreateThreeWayJSONMergePatch(original, modified, current) + if err != nil { + t.Fatalf("error: %s", err) + } + testPatchCreation(t, expected, actual, c.Description) + testPatchApplication(t, current, actual, result, c.Description) +} + +func testPatchCreation(t *testing.T, expected, actual []byte, description string) { + if !reflect.DeepEqual(actual, expected) { + t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n", + description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual)) + return + } +} + +func testPatchApplication(t *testing.T, original, patch, expected []byte, description string) { + result, err := jsonpatch.MergePatch(original, patch) + if err != nil { + t.Errorf("error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", + err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) + return + } + + if !reflect.DeepEqual(result, expected) { + format := "error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" + t.Errorf(format, description, + jsonToYAMLOrError(original), jsonToYAMLOrError(patch), + jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) + return + } +} + +func threeWayTestCaseToJSONOrFail(t *testing.T, c JSONMergePatchTestCase) ([]byte, []byte, []byte, []byte, []byte) { + return testObjectToJSONOrFail(t, c.Original), + testObjectToJSONOrFail(t, c.Modified), + testObjectToJSONOrFail(t, c.Current), + testObjectToJSONOrFail(t, c.ThreeWay), + testObjectToJSONOrFail(t, c.Result) +} + +func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte { + if o == nil { + return nil + } + j, err := toJSON(o) + if err != nil { + t.Error(err) + } + return j +} + +func jsonToYAMLOrError(j []byte) string { + y, err := jsonToYAML(j) + if err != nil { + return err.Error() + } + return string(y) +} + +func toJSON(v interface{}) ([]byte, error) { + j, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("json marshal failed: %v\n%v\n", err, dump.Pretty(v)) + } + return j, nil +} + +func jsonToYAML(j []byte) ([]byte, error) { + y, err := yaml.JSONToYAML(j) + if err != nil { + return nil, fmt.Errorf("json to yaml failed: %v\n%v\n", err, j) + } + return y, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/endpoints.yaml b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/endpoints.yaml new file mode 100644 index 0000000000..a667e98342 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/endpoints.yaml @@ -0,0 +1,7018 @@ +apiVersion: v1 +kind: Endpoints +metadata: + creationTimestamp: '2016-10-04T17:45:58Z' + labels: + app: my-app + name: app-server + namespace: default + resourceVersion: '184597135' + selfLink: /self/link + uid: 6826f086-8a5a-11e6-8d09-42010a800005 +subsets: +- addresses: + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0000 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0001 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0002 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0003 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0004 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0005 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0006 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0007 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0008 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0009 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0010 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0011 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0012 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0013 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0014 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0015 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0016 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0017 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0018 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0019 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0020 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0021 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0022 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0023 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0024 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0025 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0026 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0027 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0028 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0029 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0030 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0031 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0032 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0033 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0034 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0035 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0036 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0037 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0038 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0039 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0040 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0041 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0042 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0043 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0044 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0045 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0046 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0047 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0048 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0049 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0050 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0051 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0052 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0053 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0054 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0055 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0056 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0057 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0058 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0059 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0060 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0061 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0062 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0063 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0064 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0065 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0066 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0067 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0068 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0069 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0070 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0071 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0072 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0073 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0074 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0075 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0076 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0077 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0078 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0079 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0080 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0081 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0082 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0083 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0084 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0085 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0086 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0087 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0088 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0089 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0090 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0091 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0092 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0093 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0094 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0095 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0096 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0097 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0098 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0099 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0100 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0101 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0102 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0103 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0104 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0105 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0106 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0107 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0108 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0109 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0110 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0111 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0112 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0113 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0114 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0115 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0116 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0117 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0118 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0119 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0120 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0121 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0122 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0123 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0124 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0125 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0126 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0127 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0128 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0129 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0130 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0131 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0132 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0133 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0134 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0135 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0136 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0137 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0138 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0139 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0140 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0141 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0142 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0143 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0144 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0145 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0146 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0147 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0148 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0149 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0150 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0151 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0152 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0153 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0154 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0155 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0156 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0157 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0158 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0159 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0160 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0161 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0162 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0163 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0164 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0165 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0166 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0167 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0168 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0169 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0170 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0171 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0172 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0173 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0174 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0175 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0176 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0177 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0178 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0179 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0180 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0181 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0182 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0183 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0184 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0185 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0186 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0187 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0188 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0189 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0190 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0191 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0192 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0193 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0194 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0195 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0196 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0197 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0198 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0199 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0200 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0201 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0202 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0203 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0204 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0205 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0206 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0207 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0208 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0209 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0210 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0211 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0212 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0213 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0214 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0215 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0216 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0217 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0218 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0219 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0220 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0221 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0222 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0223 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0224 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0225 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0226 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0227 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0228 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0229 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0230 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0231 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0232 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0233 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0234 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0235 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0236 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0237 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0238 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0239 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0240 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0241 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0242 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0243 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0244 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0245 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0246 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0247 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0248 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0249 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0250 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0251 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0252 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0253 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0254 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0255 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0256 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0257 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0258 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0259 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0260 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0261 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0262 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0263 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0264 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0265 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0266 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0267 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0268 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0269 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0270 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0271 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0272 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0273 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0274 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0275 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0276 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0277 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0278 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0279 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0280 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0281 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0282 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0283 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0284 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0285 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0286 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0287 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0288 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0289 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0290 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0291 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0292 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0293 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0294 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0295 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0296 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0297 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0298 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0299 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0300 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0301 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0302 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0303 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0304 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0305 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0306 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0307 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0308 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0309 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0310 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0311 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0312 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0313 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0314 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0315 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0316 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0317 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0318 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0319 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0320 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0321 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0322 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0323 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0324 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0325 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0326 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0327 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0328 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0329 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0330 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0331 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0332 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0333 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0334 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0335 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0336 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0337 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0338 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0339 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0340 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0341 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0342 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0343 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0344 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0345 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0346 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0347 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0348 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0349 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0350 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0351 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0352 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0353 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0354 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0355 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0356 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0357 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0358 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0359 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0360 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0361 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0362 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0363 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0364 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0365 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0366 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0367 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0368 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0369 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0370 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0371 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0372 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0373 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0374 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0375 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0376 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0377 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0378 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0379 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0380 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0381 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0382 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0383 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0384 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0385 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0386 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0387 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0388 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0389 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0390 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0391 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0392 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0393 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0394 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0395 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0396 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0397 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0398 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0399 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0400 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0401 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0402 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0403 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0404 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0405 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0406 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0407 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0408 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0409 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0410 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0411 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0412 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0413 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0414 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0415 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0416 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0417 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0418 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0419 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0420 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0421 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0422 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0423 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0424 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0425 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0426 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0427 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0428 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0429 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0430 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0431 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0432 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0433 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0434 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0435 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0436 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0437 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0438 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0439 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0440 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0441 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0442 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0443 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0444 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0445 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0446 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0447 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0448 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0449 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0450 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0451 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0452 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0453 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0454 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0455 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0456 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0457 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0458 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0459 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0460 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0461 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0462 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0463 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0464 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0465 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0466 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0467 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0468 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0469 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0470 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0471 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0472 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0473 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0474 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0475 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0476 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0477 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0478 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0479 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0480 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0481 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0482 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0483 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0484 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0485 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0486 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0487 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0488 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0489 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0490 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0491 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0492 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0493 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0494 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0495 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0496 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0497 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0498 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0499 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0500 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0501 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0502 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0503 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0504 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0505 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0506 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0507 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0508 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0509 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0510 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0511 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0512 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0513 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0514 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0515 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0516 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0517 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0518 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0519 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0520 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0521 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0522 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0523 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0524 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0525 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0526 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0527 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0528 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0529 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0530 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0531 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0532 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0533 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0534 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0535 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0536 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0537 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0538 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0539 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0540 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0541 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0542 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0543 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0544 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0545 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0546 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0547 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0548 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0549 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0550 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0551 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0552 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0553 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0554 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0555 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0556 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0557 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0558 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0559 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0560 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0561 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0562 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0563 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0564 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0565 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0566 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0567 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0568 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0569 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0570 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0571 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0572 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0573 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0574 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0575 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0576 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0577 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0578 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0579 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0580 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0581 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0582 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0583 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0584 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0585 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0586 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0587 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0588 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0589 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0590 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0591 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0592 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0593 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0594 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0595 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0596 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0597 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0598 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0599 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0600 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0601 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0602 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0603 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0604 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0605 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0606 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0607 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0608 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0609 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0610 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0611 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0612 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0613 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0614 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0615 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0616 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0617 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0618 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0619 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0620 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0621 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0622 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0623 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0624 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0625 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0626 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0627 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0628 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0629 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0630 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0631 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0632 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0633 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0634 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0635 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0636 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0637 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0638 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0639 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0640 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0641 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0642 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0643 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0644 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0645 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0646 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0647 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0648 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0649 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0650 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0651 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0652 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0653 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0654 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0655 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0656 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0657 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0658 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0659 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0660 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0661 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0662 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0663 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0664 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0665 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0666 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0667 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0668 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0669 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0670 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0671 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0672 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0673 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0674 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0675 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0676 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0677 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0678 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0679 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0680 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0681 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0682 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0683 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0684 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0685 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0686 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0687 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0688 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0689 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0690 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0691 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0692 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0693 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0694 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0695 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0696 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0697 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0698 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0699 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0700 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0701 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0702 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0703 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0704 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0705 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0706 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0707 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0708 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0709 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0710 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0711 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0712 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0713 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0714 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0715 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0716 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0717 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0718 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0719 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0720 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0721 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0722 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0723 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0724 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0725 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0726 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0727 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0728 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0729 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0730 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0731 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0732 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0733 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0734 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0735 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0736 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0737 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0738 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0739 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0740 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0741 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0742 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0743 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0744 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0745 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0746 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0747 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0748 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0749 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0750 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0751 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0752 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0753 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0754 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0755 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0756 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0757 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0758 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0759 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0760 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0761 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0762 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0763 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0764 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0765 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0766 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0767 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0768 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0769 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0770 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0771 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0772 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0773 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0774 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0775 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0776 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0777 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0778 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0779 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0780 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0781 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0782 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0783 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0784 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0785 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0786 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0787 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0788 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0789 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0790 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0791 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0792 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0793 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0794 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0795 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0796 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0797 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0798 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0799 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0800 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0801 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0802 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0803 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0804 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0805 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0806 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0807 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0808 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0809 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0810 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0811 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0812 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0813 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0814 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0815 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0816 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0817 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0818 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0819 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0820 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0821 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0822 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0823 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0824 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0825 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0826 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0827 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0828 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0829 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0830 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0831 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0832 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0833 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0834 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0835 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0836 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0837 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0838 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0839 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0840 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0841 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0842 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0843 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0844 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0845 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0846 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0847 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0848 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0849 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0850 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0851 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0852 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0853 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0854 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0855 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0856 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0857 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0858 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0859 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0860 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0861 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0862 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0863 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0864 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0865 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0866 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0867 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0868 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0869 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0870 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0871 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0872 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0873 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0874 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0875 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0876 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0877 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0878 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0879 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0880 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0881 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0882 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0883 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0884 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0885 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0886 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0887 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0888 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0889 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0890 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0891 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0892 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0893 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0894 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0895 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0896 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0897 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0898 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0899 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0900 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0901 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0902 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0903 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0904 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0905 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0906 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0907 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0908 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0909 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0910 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0911 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0912 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0913 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0914 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0915 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0916 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0917 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0918 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0919 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0920 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0921 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0922 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0923 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0924 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0925 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0926 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0927 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0928 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0929 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0930 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0931 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0932 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0933 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0934 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0935 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0936 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0937 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0938 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0939 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0940 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0941 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0942 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0943 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0944 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0945 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0946 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0947 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0948 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0949 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0950 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0951 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0952 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0953 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0954 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0955 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0956 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0957 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0958 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0959 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0960 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0961 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0962 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0963 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0964 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0965 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0966 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0967 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0968 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0969 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0970 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0971 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0972 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0973 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0974 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0975 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0976 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0977 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0978 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0979 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0980 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0981 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0982 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0983 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0984 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0985 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0986 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0987 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0988 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0989 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0990 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0991 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0992 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0993 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0994 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0995 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0996 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0997 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0998 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + - ip: 10.0.0.1 + targetRef: + kind: Pod + name: pod-name-1234-0999 + namespace: default + resourceVersion: '1234567890' + uid: 11111111-2222-3333-4444-555555555555 + ports: + - name: port-name + port: 8080 + protocol: TCP + diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/extract.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/extract.go new file mode 100644 index 0000000000..ff7dda1a50 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/extract.go @@ -0,0 +1,107 @@ +/* +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 managedfields + +import ( + "fmt" + + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/typed" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +// ExtractInto extracts the applied configuration state from object for fieldManager +// into applyConfiguration. If no managed fields are found for the given fieldManager, +// no error is returned, but applyConfiguration is left unpopulated. It is possible +// that no managed fields were found for the fieldManager because other field managers +// have taken ownership of all the fields previously owned by the fieldManager. It is +// also possible the fieldManager never owned fields. +// +// The provided object MUST bo a root resource object since subresource objects +// do not contain their own managed fields. For example, an autoscaling.Scale +// object read from a "scale" subresource does not have any managed fields and so +// cannot be used as the object. +// +// If the fields of a subresource are a subset of the fields of the root object, +// and their field paths and types are exactly the same, then ExtractInto can be +// called with the root resource as the object and the subresource as the +// applyConfiguration. This works for "status", obviously, because status is +// represented by the exact same object as the root resource. This does NOT +// work, for example, with the "scale" subresources of Deployment, ReplicaSet and +// StatefulSet. While the spec.replicas, status.replicas fields are in the same +// exact field path locations as they are in autoscaling.Scale, the selector +// fields are in different locations, and are a different type. +func ExtractInto(object runtime.Object, objectType typed.ParseableType, fieldManager string, applyConfiguration interface{}, subresource string) error { + typedObj, err := toTyped(object, objectType) + if err != nil { + return fmt.Errorf("error converting obj to typed: %w", err) + } + + accessor, err := meta.Accessor(object) + if err != nil { + return fmt.Errorf("error accessing metadata: %w", err) + } + fieldsEntry, ok := findManagedFields(accessor, fieldManager, subresource) + if !ok { + return nil + } + fieldset := &fieldpath.Set{} + err = fieldset.FromJSON(fieldsEntry.FieldsV1.GetRawReader()) + if err != nil { + return fmt.Errorf("error marshalling FieldsV1 to JSON: %w", err) + } + + u := typedObj.ExtractItems(fieldset.Leaves()).AsValue().Unstructured() + m, ok := u.(map[string]interface{}) + if !ok { + return fmt.Errorf("unable to convert managed fields for %s to unstructured, expected map, got %T", fieldManager, u) + } + + // set the type meta manually if it doesn't exist to avoid missing kind errors + // when decoding from unstructured JSON + if _, ok := m["kind"]; !ok && object.GetObjectKind().GroupVersionKind().Kind != "" { + m["kind"] = object.GetObjectKind().GroupVersionKind().Kind + m["apiVersion"] = object.GetObjectKind().GroupVersionKind().GroupVersion().String() + } + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(m, applyConfiguration); err != nil { + return fmt.Errorf("error extracting into obj from unstructured: %w", err) + } + return nil +} + +func findManagedFields(accessor metav1.Object, fieldManager string, subresource string) (metav1.ManagedFieldsEntry, bool) { + objManagedFields := accessor.GetManagedFields() + for _, mf := range objManagedFields { + if mf.Manager == fieldManager && mf.Operation == metav1.ManagedFieldsOperationApply && mf.Subresource == subresource { + return mf, true + } + } + return metav1.ManagedFieldsEntry{}, false +} + +func toTyped(obj runtime.Object, objectType typed.ParseableType) (*typed.TypedValue, error) { + switch o := obj.(type) { + case *unstructured.Unstructured: + return objectType.FromUnstructured(o.Object) + default: + return objectType.FromStructured(o) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go new file mode 100644 index 0000000000..50d6273de0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/extract_test.go @@ -0,0 +1,272 @@ +/* +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 managedfields + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/structured-merge-diff/v6/typed" + + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + runtimeschema "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestExtractInto(t *testing.T) { + one := int32(1) + parser, err := typed.NewParser(schemaYAML) + if err != nil { + t.Fatalf("Failed to parse schema: %v", err) + } + cases := []struct { + name string + obj runtime.Object + objType typed.ParseableType + managedFields []metav1.ManagedFieldsEntry // written to object before test is run + fieldManager string + expectedOut interface{} + subresource string + }{ + { + name: "unstructured, no matching manager", + obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr999", `{ "f:spec": { "f:replicas": {}}}`, ""), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{}, + }, + { + name: "unstructured, one manager", + obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{"spec": map[string]interface{}{"replicas": 1}}, + }, + { + name: "unstructured, multiple manager", + obj: &unstructured.Unstructured{Object: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""), + applyFieldsEntry("mgr2", `{ "f:spec": { "f:paused": {}}}`, ""), + }, + fieldManager: "mgr2", + expectedOut: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}, + }, + { + name: "structured, no matching manager", + obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr999", `{ "f:spec": { "f:replicas": {}}}`, ""), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{}, + }, + { + name: "structured, one manager", + obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{"spec": map[string]interface{}{"replicas": int64(1)}}, + }, + { + name: "structured, multiple manager", + obj: &fakeDeployment{Spec: fakeDeploymentSpec{Replicas: &one, Paused: true}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:spec": { "f:replicas": {}}}`, ""), + applyFieldsEntry("mgr2", `{ "f:spec": { "f:paused": {}}}`, ""), + }, + fieldManager: "mgr2", + expectedOut: map[string]interface{}{"spec": map[string]interface{}{"paused": true}}, + }, + { + name: "subresource", + obj: &fakeDeployment{Status: fakeDeploymentStatus{Replicas: &one}}, + objType: parser.Type("io.k8s.api.apps.v1.Deployment"), + managedFields: []metav1.ManagedFieldsEntry{ + applyFieldsEntry("mgr1", `{ "f:status": { "f:replicas": {}}}`, "status"), + }, + fieldManager: "mgr1", + expectedOut: map[string]interface{}{"status": map[string]interface{}{"replicas": int64(1)}}, + subresource: "status", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + out := map[string]interface{}{} + accessor, err := meta.Accessor(tc.obj) + if err != nil { + t.Fatalf("Error accessing object: %v", err) + } + accessor.SetManagedFields(tc.managedFields) + err = ExtractInto(tc.obj, tc.objType, tc.fieldManager, &out, tc.subresource) + if err != nil { + t.Fatalf("Unexpected extract error: %v", err) + } + if !equality.Semantic.DeepEqual(out, tc.expectedOut) { + t.Fatalf("Expected output did not match actual output: %s", cmp.Diff(out, tc.expectedOut)) + } + }) + } +} + +func applyFieldsEntry(fieldManager string, fieldsJSON string, subresource string) metav1.ManagedFieldsEntry { + return metav1.ManagedFieldsEntry{ + Manager: fieldManager, + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(fieldsJSON), + Subresource: subresource, + } +} + +type fakeDeployment struct { + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec fakeDeploymentSpec `json:"spec"` + Status fakeDeploymentStatus `json:"status"` +} + +type fakeDeploymentSpec struct { + Replicas *int32 `json:"replicas"` + Paused bool `json:"paused,omitempty"` +} + +type fakeDeploymentStatus struct { + Replicas *int32 `json:"replicas"` +} + +func (o *fakeDeployment) GetObjectMeta() metav1.ObjectMeta { + return o.ObjectMeta +} +func (o *fakeDeployment) GetObjectKind() runtimeschema.ObjectKind { + return runtimeschema.EmptyObjectKind +} +func (o *fakeDeployment) DeepCopyObject() runtime.Object { + return o +} + +// trimmed up schema for test purposes +const schemaYAML = typed.YAMLObject(`types: +- name: io.k8s.api.apps.v1.Deployment + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + - name: spec + type: + namedType: io.k8s.api.apps.v1.DeploymentSpec + - name: status + type: + namedType: io.k8s.api.apps.v1.DeploymentStatus +- name: io.k8s.api.apps.v1.DeploymentSpec + map: + fields: + - name: paused + type: + scalar: boolean + - name: replicas + type: + scalar: numeric +- name: io.k8s.api.apps.v1.DeploymentStatus + map: + fields: + - name: replicas + type: + scalar: numeric +- name: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + map: + fields: + - name: creationTimestamp + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: managedFields + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry + map: + fields: + - name: apiVersion + type: + scalar: string + - name: fieldsType + type: + scalar: string + - name: fieldsV1 + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 + - name: manager + type: + scalar: string + - name: operation + type: + scalar: string + - name: time + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: subresource + type: + scalar: string +- name: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 + map: + elementType: + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.Time + scalar: untyped +- name: __untyped_atomic_ + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic +`) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager.go new file mode 100644 index 0000000000..b1e621f390 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager.go @@ -0,0 +1,58 @@ +/* +Copyright 2018 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 managedfields + +import ( + "fmt" + + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields/internal" +) + +// FieldManager updates the managed fields and merges applied +// configurations. +type FieldManager = internal.FieldManager + +// NewDefaultFieldManager creates a new FieldManager that merges apply requests +// and update managed fields for other types of requests. +func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]fieldpath.Filter) (*FieldManager, error) { + f, err := internal.NewStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields) + if err != nil { + return nil, fmt.Errorf("failed to create field manager: %v", err) + } + return internal.NewDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil +} + +// NewDefaultCRDFieldManager creates a new FieldManager specifically for +// CRDs. This allows for the possibility of fields which are not defined +// in models, as well as having no models defined at all. +func NewDefaultCRDFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, subresource string, resetFields map[fieldpath.APIVersion]fieldpath.Filter) (_ *FieldManager, err error) { + f, err := internal.NewCRDStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields) + if err != nil { + return nil, fmt.Errorf("failed to create field manager: %v", err) + } + return internal.NewDefaultFieldManager(f, typeConverter, objectConverter, objectCreater, kind, subresource), nil +} + +func ValidateManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) error { + _, err := internal.DecodeManagedFields(encodedManagedFields) + return err +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager_test.go new file mode 100644 index 0000000000..6ba8dc5679 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/fieldmanager_test.go @@ -0,0 +1,1139 @@ +/* +Copyright 2019 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 managedfields_test + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + "k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest" + yamlutil "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/kube-openapi/pkg/validation/spec" + "sigs.k8s.io/yaml" +) + +var fakeTypeConverter = func() managedfields.TypeConverter { + data, err := os.ReadFile(filepath.Join(strings.Repeat(".."+string(filepath.Separator), 7), + "api", "openapi-spec", "swagger.json")) + if err != nil { + panic(err) + } + swag := spec.Swagger{} + if err := json.Unmarshal(data, &swag); err != nil { + panic(err) + } + convertedDefs := map[string]*spec.Schema{} + for k, v := range swag.Definitions { + vCopy := v + convertedDefs[k] = &vCopy + } + typeConverter, err := managedfields.NewTypeConverter(convertedDefs, false) + if err != nil { + panic(err) + } + return typeConverter +}() + +// TestUpdateApplyConflict tests that applying to an object, which +// wasn't created by apply, will give conflicts +func TestUpdateApplyConflict(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + patch := []byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment", + "labels": {"app": "nginx"} + }, + "spec": { + "replicas": 3, + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "nginx:latest" + }] + } + } + } + }`) + newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal(patch, &newObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + if err := f.Update(newObj, "fieldmanager_test"); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment", + }, + "spec": { + "replicas": 101, + } + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + err := f.Apply(appliedObj, "fieldmanager_conflict", false) + if err == nil || !apierrors.IsConflict(err) { + t.Fatalf("Expecting to get conflicts but got %v", err) + } +} + +func TestApplyStripsFields(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + newObj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }, + } + + newObj.SetName("b") + newObj.SetNamespace("b") + newObj.SetUID("b") + newObj.SetGeneration(0) + newObj.SetResourceVersion("b") + newObj.SetCreationTimestamp(metav1.NewTime(time.Now())) + newObj.SetManagedFields([]metav1.ManagedFieldsEntry{ + { + Manager: "update", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + }, + }) + if err := f.Update(newObj, "fieldmanager_test"); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + if m := f.ManagedFields(); len(m) != 0 { + t.Fatalf("fields did not get stripped: %v", m) + } +} + +func TestVersionCheck(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + // patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors + err := f.Apply(appliedObj, "fieldmanager_test", false) + if err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + appliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1beta1", + "kind": "Deployment", + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + // patch has 'apiVersion: apps/v1beta1' but live version is apps/v1 -> error + err = f.Apply(appliedObj, "fieldmanager_test", false) + if err == nil { + t.Fatalf("expected an error from mismatched patch and live versions") + } + switch typ := err.(type) { + default: + t.Fatalf("expected error to be of type %T was %T (%v)", apierrors.StatusError{}, typ, err) + case apierrors.APIStatus: + if typ.Status().Code != http.StatusBadRequest { + t.Fatalf("expected status code to be %d but was %d", + http.StatusBadRequest, typ.Status().Code) + } + } +} + +func TestVersionCheckDoesNotPanic(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + // patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors + err := f.Apply(appliedObj, "fieldmanager_test", false) + if err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + appliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + // patch has 'apiVersion: apps/v2' but live version is apps/v1 -> error + err = f.Apply(appliedObj, "fieldmanager_test", false) + if err == nil { + t.Fatalf("expected an error from mismatched patch and live versions") + } + switch typ := err.(type) { + default: + t.Fatalf("expected error to be of type %T was %T (%v)", apierrors.StatusError{}, typ, err) + case apierrors.APIStatus: + if typ.Status().Code != http.StatusBadRequest { + t.Fatalf("expected status code to be %d but was %d", + http.StatusBadRequest, typ.Status().Code) + } + } +} + +func TestApplyDoesNotStripLabels(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "a": "b" + }, + } + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + err := f.Apply(appliedObj, "fieldmanager_test", false) + if err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + if m := f.ManagedFields(); len(m) != 1 { + t.Fatalf("labels shouldn't get stripped on apply: %v", m) + } +} + +func getObjectBytes(file string) []byte { + s, err := os.ReadFile(file) + if err != nil { + panic(err) + } + return s +} + +func TestApplyNewObject(t *testing.T) { + tests := []struct { + gvk schema.GroupVersionKind + obj []byte + }{ + { + gvk: schema.FromAPIVersionAndKind("v1", "Pod"), + obj: getObjectBytes("pod.yaml"), + }, + { + gvk: schema.FromAPIVersionAndKind("v1", "Node"), + obj: getObjectBytes("node.yaml"), + }, + { + gvk: schema.FromAPIVersionAndKind("v1", "Endpoints"), + obj: getObjectBytes("endpoints.yaml"), + }, + } + + for _, test := range tests { + t.Run(test.gvk.String(), func(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, test.gvk) + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal(test.obj, &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + if err := f.Apply(appliedObj, "fieldmanager_test", false); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestApplyFailsWithManagedFields(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "managedFields": [ + { + "manager": "test", + } + ] + } + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + err := f.Apply(appliedObj, "fieldmanager_test", false) + + if err == nil { + t.Fatalf("successfully applied with set managed fields") + } +} + +func TestApplySuccessWithNoManagedFields(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "a": "b" + }, + } + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + err := f.Apply(appliedObj, "fieldmanager_test", false) + + if err != nil { + t.Fatalf("failed to apply object: %v", err) + } +} + +// Run an update and apply, and make sure that nothing has changed. +func TestNoOpChanges(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) + + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "a": "b" + }, + "creationTimestamp": null, + } + }`), &obj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply", false); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + before := f.Live() + // Wait to make sure the timestamp is different + time.Sleep(time.Second) + // Applying with a different fieldmanager will create an entry.. + if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply_other", false); err != nil { + t.Fatalf("failed to update object: %v", err) + } + if reflect.DeepEqual(before, f.Live()) { + t.Fatalf("Applying no-op apply with new manager didn't change object: \n%v", f.Live()) + } + before = f.Live() + // Wait to make sure the timestamp is different + time.Sleep(time.Second) + if err := f.Update(obj.DeepCopyObject(), "fieldmanager_test_update"); err != nil { + t.Fatalf("failed to update object: %v", err) + } + if !reflect.DeepEqual(before, f.Live()) { + t.Fatalf("No-op update has changed the object:\n%v\n---\n%v", before, f.Live()) + } + before = f.Live() + // Wait to make sure the timestamp is different + time.Sleep(time.Second) + if err := f.Apply(obj.DeepCopyObject(), "fieldmanager_test_apply", true); err != nil { + t.Fatalf("failed to re-apply object: %v", err) + } + if !reflect.DeepEqual(before, f.Live()) { + t.Fatalf("No-op apply has changed the object:\n%v\n---\n%v", before, f.Live()) + } +} + +// Tests that one can reset the managedFields by sending either an empty +// list +func TestResetManagedFieldsEmptyList(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) + + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "a": "b" + }, + } + }`), &obj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + if err := f.Apply(obj, "fieldmanager_test_apply", false); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "managedFields": [], + "labels": { + "a": "b" + }, + } + }`), &obj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + if err := f.Update(obj, "update_manager"); err != nil { + t.Fatalf("failed to update with empty manager: %v", err) + } + + if len(f.ManagedFields()) != 0 { + t.Fatalf("failed to reset managedFields: %v", f.ManagedFields()) + } +} + +// Tests that one can reset the managedFields by sending either a list with one empty item. +func TestResetManagedFieldsEmptyItem(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) + + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "a": "b" + }, + } + }`), &obj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + if err := f.Apply(obj, "fieldmanager_test_apply", false); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "managedFields": [{}], + "labels": { + "a": "b" + }, + } + }`), &obj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + if err := f.Update(obj, "update_manager"); err != nil { + t.Fatalf("failed to update with empty manager: %v", err) + } + + if len(f.ManagedFields()) != 0 { + t.Fatalf("failed to reset managedFields: %v", f.ManagedFields()) + } +} + +func TestServerSideApplyWithInvalidLastApplied(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + // create object with client-side apply + newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment := []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app-v1 +spec: + replicas: 1 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + + invalidLastApplied := "invalid-object" + if err := internal.SetLastApplied(newObj, invalidLastApplied); err != nil { + t.Errorf("failed to set last applied: %v", err) + } + + if err := f.Update(newObj, "kubectl-client-side-apply-test"); err != nil { + t.Errorf("failed to update object: %v", err) + } + + lastApplied, err := getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if lastApplied != invalidLastApplied { + t.Errorf("expected last applied annotation to be set to %q, but got: %q", invalidLastApplied, lastApplied) + } + + // upgrade management of the object from client-side apply to server-side apply + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + appliedDeployment := []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app-v2 +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(appliedDeployment, &appliedObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + + if err := f.Apply(appliedObj, "kubectl", false); err == nil || !apierrors.IsConflict(err) { + t.Errorf("expected conflict when applying with invalid last-applied annotation, but got no error for object: \n%+v", appliedObj) + } + + lastApplied, err = getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if lastApplied != invalidLastApplied { + t.Errorf("expected last applied annotation to be NOT be updated, but got: %q", lastApplied) + } + + // force server-side apply should work and fix the annotation + if err := f.Apply(appliedObj, "kubectl", true); err != nil { + t.Errorf("failed to force server-side apply with: %v", err) + } + + lastApplied, err = getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if lastApplied == invalidLastApplied || + !strings.Contains(lastApplied, "my-app-v2") { + t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) + } +} + +func TestInteropForClientSideApplyAndServerSideApply(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + // create object with client-side apply + newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment := []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image-v1 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := setLastAppliedFromEncoded(newObj, deployment); err != nil { + t.Errorf("failed to set last applied: %v", err) + } + + if err := f.Update(newObj, "kubectl-client-side-apply-test"); err != nil { + t.Errorf("failed to update object: %v", err) + } + lastApplied, err := getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if !strings.Contains(lastApplied, "my-image-v1") { + t.Errorf("expected last applied annotation to be set properly, but got: %q", lastApplied) + } + + // upgrade management of the object from client-side apply to server-side apply + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + appliedDeployment := []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app-v2 # change +spec: + replicas: 8 # change + selector: + matchLabels: + app: my-app-v2 # change + template: + metadata: + labels: + app: my-app-v2 # change + spec: + containers: + - name: my-c + image: my-image-v2 # change +`) + if err := yaml.Unmarshal(appliedDeployment, &appliedObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + + if err := f.Apply(appliedObj, "kubectl", false); err != nil { + t.Errorf("error applying object: %v", err) + } + + lastApplied, err = getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if !strings.Contains(lastApplied, "my-image-v2") { + t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) + } +} + +func TestNoTrackManagedFieldsForClientSideApply(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + // create object + newObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment := []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := f.Update(newObj, "test_kubectl_create"); err != nil { + t.Errorf("failed to update object: %v", err) + } + if m := f.ManagedFields(); len(m) == 0 { + t.Errorf("expected to have managed fields, but got: %v", m) + } + + // stop tracking managed fields + newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment = []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + managedFields: [] # stop tracking managed fields + labels: + app: my-app +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + newObj.SetUID("nonempty") + if err := f.Update(newObj, "test_kubectl_replace"); err != nil { + t.Errorf("failed to update object: %v", err) + } + if m := f.ManagedFields(); len(m) != 0 { + t.Errorf("expected to have stop tracking managed fields, but got: %v", m) + } + + // check that we still don't track managed fields + newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment = []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := setLastAppliedFromEncoded(newObj, deployment); err != nil { + t.Errorf("failed to set last applied: %v", err) + } + if err := f.Update(newObj, "test_k_client_side_apply"); err != nil { + t.Errorf("failed to update object: %v", err) + } + if m := f.ManagedFields(); len(m) != 0 { + t.Errorf("expected to continue to not track managed fields, but got: %v", m) + } + lastApplied, err := getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if !strings.Contains(lastApplied, "my-app") { + t.Errorf("expected last applied annotation to be set properly, but got: %q", lastApplied) + } + + // start tracking managed fields + newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment = []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := f.Apply(newObj, "test_server_side_apply_without_upgrade", false); err != nil { + t.Errorf("error applying object: %v", err) + } + if m := f.ManagedFields(); len(m) < 2 { + t.Errorf("expected to start tracking managed fields with at least 2 field managers, but got: %v", m) + } + if e, a := "test_server_side_apply_without_upgrade", f.ManagedFields()[0].Manager; e != a { + t.Fatalf("exected first manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } + if e, a := "before-first-apply", f.ManagedFields()[1].Manager; e != a { + t.Fatalf("exected second manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } + + // upgrade management of the object from client-side apply to server-side apply + newObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + deployment = []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app-v2 # change +spec: + replicas: 8 # change +`) + if err := yaml.Unmarshal(deployment, &newObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + if err := f.Apply(newObj, "kubectl", false); err != nil { + t.Errorf("error applying object: %v", err) + } + if m := f.ManagedFields(); len(m) == 0 { + t.Errorf("expected to track managed fields, but got: %v", m) + } + lastApplied, err = getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + if !strings.Contains(lastApplied, "my-app-v2") { + t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) + } +} + +func yamlToJSON(y []byte) (string, error) { + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal(y, &obj.Object); err != nil { + return "", fmt.Errorf("error decoding YAML: %v", err) + } + serialization, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return "", fmt.Errorf("error encoding object: %v", err) + } + json, err := yamlutil.ToJSON(serialization) + if err != nil { + return "", fmt.Errorf("error converting to json: %v", err) + } + return string(json), nil +} + +func setLastAppliedFromEncoded(obj runtime.Object, lastApplied []byte) error { + lastAppliedJSON, err := yamlToJSON(lastApplied) + if err != nil { + return err + } + return internal.SetLastApplied(obj, lastAppliedJSON) +} + +func getLastApplied(obj runtime.Object) (string, error) { + accessor := meta.NewAccessor() + annotations, err := accessor.Annotations(obj) + if err != nil { + return "", fmt.Errorf("failed to access annotations: %v", err) + } + if annotations == nil { + return "", fmt.Errorf("no annotations on obj: %v", obj) + } + + lastApplied, ok := annotations[internal.LastAppliedConfigAnnotation] + if !ok { + return "", fmt.Errorf("expected last applied annotation, but got none for object: %v", obj) + } + return lastApplied, nil +} + +func TestUpdateViaSubresources(t *testing.T) { + f := managedfieldstest.NewTestFieldManagerSubresource(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "scale") + + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "a":"b" + }, + } + }`), &obj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + obj.SetManagedFields([]metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1( + `{"f:metadata":{"f:labels":{"f:another_field":{}}}}`, + ), + }, + }) + + // Check that managed fields cannot be changed explicitly via subresources + expectedManager := "fieldmanager_test_subresource" + if err := f.Update(obj, expectedManager); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + managedFields := f.ManagedFields() + if len(managedFields) != 1 { + t.Fatalf("Expected new managed fields to have one entry. Got:\n%#v", managedFields) + } + if managedFields[0].Manager != expectedManager { + t.Fatalf("Expected first item to have manager set to: %s. Got: %s", expectedManager, managedFields[0].Manager) + } + + // Check that managed fields cannot be reset via subresources + newObj := obj.DeepCopy() + newObj.SetManagedFields([]metav1.ManagedFieldsEntry{}) + if err := f.Update(newObj, expectedManager); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + newManagedFields := f.ManagedFields() + if len(newManagedFields) != 1 { + t.Fatalf("Expected new managed fields to have one entry. Got:\n%#v", newManagedFields) + } +} + +// Ensures that a no-op Apply does not mutate managed fields +func TestApplyDoesNotChangeManagedFields(t *testing.T) { + originalManagedFields := []metav1.ManagedFieldsEntry{} + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, + schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + newObj := &unstructured.Unstructured{ + Object: map[string]interface{}{}, + } + appliedObj := &unstructured.Unstructured{ + Object: map[string]interface{}{}, + } + + // Convert YAML string inputs to unstructured instances + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment", + "labels": {"app": "nginx"} + }, + "spec": { + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "nginx:latest" + }] + } + } + } + }`), &newObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment", + }, + "spec": { + "replicas": 101, + } + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + // Agent A applies initial configuration + if err := f.Apply(newObj.DeepCopyObject(), "fieldmanager_z", false); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + // Agent B applies additive configuration + if err := f.Apply(appliedObj, "fieldmanager_b", false); err != nil { + t.Fatalf("failed to apply object %v", err) + } + + // Next, agent A applies the initial configuration again, but we expect + // a no-op to managed fields. + // + // The following update is expected not to change the liveObj, save off + // the fields + for _, field := range f.ManagedFields() { + originalManagedFields = append(originalManagedFields, *field.DeepCopy()) + } + + // Make sure timestamp change would be caught + time.Sleep(2 * time.Second) + + if err := f.Apply(newObj, "fieldmanager_z", false); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + // ensure that the live object is unchanged + if !reflect.DeepEqual(originalManagedFields, f.ManagedFields()) { + originalYAML, _ := yaml.Marshal(originalManagedFields) + current, _ := yaml.Marshal(f.ManagedFields()) + + // should have been a no-op w.r.t. managed fields + t.Fatalf("managed fields changed: ORIGINAL\n%v\nCURRENT\n%v", + string(originalYAML), string(current)) + } +} + +// Ensures that a no-op Update does not mutate managed fields +func TestUpdateDoesNotChangeManagedFields(t *testing.T) { + originalManagedFields := []metav1.ManagedFieldsEntry{} + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, + schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + newObj := &unstructured.Unstructured{ + Object: map[string]interface{}{}, + } + + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment", + "labels": {"app": "nginx"} + }, + "spec": { + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "nginx:latest" + }] + } + } + } + }`), &newObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + // Agent A updates with initial configuration + if err := f.Update(newObj.DeepCopyObject(), "fieldmanager_z"); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + for _, field := range f.ManagedFields() { + originalManagedFields = append(originalManagedFields, *field.DeepCopy()) + } + + // Make sure timestamp change would be caught + time.Sleep(2 * time.Second) + + // If the same exact configuration is updated once again, the + // managed fields are not expected to change + // + // However, a change in field ownership WOULD be a semantic change which + // should cause managed fields to change. + if err := f.Update(newObj, "fieldmanager_z"); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + // ensure that the live object is unchanged + if !reflect.DeepEqual(originalManagedFields, f.ManagedFields()) { + originalYAML, _ := yaml.Marshal(originalManagedFields) + current, _ := yaml.Marshal(f.ManagedFields()) + + // should have been a no-op w.r.t. managed fields + t.Fatalf("managed fields changed: ORIGINAL\n%v\nCURRENT\n%v", + string(originalYAML), string(current)) + } +} + +// This test makes sure that the liveObject during a patch does not mutate +// its managed fields. +func TestLiveObjectManagedFieldsNotRemoved(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, + schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + newObj := &unstructured.Unstructured{ + Object: map[string]interface{}{}, + } + appliedObj := &unstructured.Unstructured{ + Object: map[string]interface{}{}, + } + // Convert YAML string inputs to unstructured instances + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment", + "labels": {"app": "nginx"} + }, + "spec": { + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "labels": { + "app": "nginx" + } + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "nginx:latest" + }] + } + } + } + }`), &newObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "deployment", + }, + "spec": { + "replicas": 101, + } + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + // Agent A applies initial configuration + if err := f.Apply(newObj.DeepCopyObject(), "fieldmanager_z", false); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + originalLiveObj := f.Live() + + accessor, err := meta.Accessor(originalLiveObj) + if err != nil { + panic(fmt.Errorf("couldn't get accessor: %v", err)) + } + + // Managed fields should not be stripped + if len(accessor.GetManagedFields()) == 0 { + t.Fatalf("empty managed fields of object which expected nonzero fields") + } + + // Agent A applies the exact same configuration + if err := f.Apply(appliedObj.DeepCopyObject(), "fieldmanager_z", false); err != nil { + t.Fatalf("failed to apply object: %v", err) + } + + accessor, err = meta.Accessor(originalLiveObj) + if err != nil { + panic(fmt.Errorf("couldn't get accessor: %v", err)) + } + + // Managed fields should not be stripped + if len(accessor.GetManagedFields()) == 0 { + t.Fatalf("empty managed fields of object which expected nonzero fields") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/gvkparser.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/gvkparser.go new file mode 100644 index 0000000000..89e4470548 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/gvkparser.go @@ -0,0 +1,128 @@ +/* +Copyright 2018 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 managedfields + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/schemaconv" + "k8s.io/kube-openapi/pkg/util/proto" + smdschema "sigs.k8s.io/structured-merge-diff/v6/schema" + "sigs.k8s.io/structured-merge-diff/v6/typed" +) + +// groupVersionKindExtensionKey is the key used to lookup the +// GroupVersionKind value for an object definition from the +// definition's "extensions" map. +const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind" + +// GvkParser contains a Parser that allows introspecting the schema. +type GvkParser struct { + gvks map[schema.GroupVersionKind]string + parser typed.Parser +} + +// Type returns a helper which can produce objects of the given type. Any +// errors are deferred until a further function is called. +func (p *GvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType { + typeName, ok := p.gvks[gvk] + if !ok { + return nil + } + t := p.parser.Type(typeName) + return &t +} + +// NewGVKParser builds a GVKParser from a proto.Models. This +// will automatically find the proper version of the object, and the +// corresponding schema information. +func NewGVKParser(models proto.Models, preserveUnknownFields bool) (*GvkParser, error) { + typeSchema, err := schemaconv.ToSchemaWithPreserveUnknownFields(models, preserveUnknownFields) + if err != nil { + return nil, fmt.Errorf("failed to convert models to schema: %v", err) + } + parser := GvkParser{ + gvks: map[schema.GroupVersionKind]string{}, + } + parser.parser = typed.Parser{Schema: smdschema.Schema{Types: typeSchema.Types}} + for _, modelName := range models.ListModels() { + model := models.LookupModel(modelName) + if model == nil { + panic(fmt.Sprintf("ListModels returns a model that can't be looked-up for: %v", modelName)) + } + gvkList := parseGroupVersionKind(model) + for _, gvk := range gvkList { + if len(gvk.Kind) > 0 { + _, ok := parser.gvks[gvk] + if ok { + return nil, fmt.Errorf("duplicate entry for %v", gvk) + } + parser.gvks[gvk] = modelName + } + } + } + return &parser, nil +} + +// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. +func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind { + extensions := s.GetExtensions() + + gvkListResult := []schema.GroupVersionKind{} + + // Get the extensions + gvkExtension, ok := extensions[groupVersionKindExtensionKey] + if !ok { + return []schema.GroupVersionKind{} + } + + // gvk extension must be a list of at least 1 element. + gvkList, ok := gvkExtension.([]interface{}) + if !ok { + return []schema.GroupVersionKind{} + } + + for _, gvk := range gvkList { + // gvk extension list must be a map with group, version, and + // kind fields + gvkMap, ok := gvk.(map[interface{}]interface{}) + if !ok { + continue + } + group, ok := gvkMap["group"].(string) + if !ok { + continue + } + version, ok := gvkMap["version"].(string) + if !ok { + continue + } + kind, ok := gvkMap["kind"].(string) + if !ok { + continue + } + + gvkListResult = append(gvkListResult, schema.GroupVersionKind{ + Group: group, + Version: version, + Kind: kind, + }) + } + + return gvkListResult +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/atmostevery.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/atmostevery.go new file mode 100644 index 0000000000..b75ef7416e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/atmostevery.go @@ -0,0 +1,60 @@ +/* +Copyright 2020 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 internal + +import ( + "sync" + "time" +) + +// AtMostEvery will never run the method more than once every specified +// duration. +type AtMostEvery struct { + delay time.Duration + lastCall time.Time + mutex sync.Mutex +} + +// NewAtMostEvery creates a new AtMostEvery, that will run the method at +// most every given duration. +func NewAtMostEvery(delay time.Duration) *AtMostEvery { + return &AtMostEvery{ + delay: delay, + } +} + +// updateLastCall returns true if the lastCall time has been updated, +// false if it was too early. +func (s *AtMostEvery) updateLastCall() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + if time.Since(s.lastCall) < s.delay { + return false + } + s.lastCall = time.Now() + return true +} + +// Do will run the method if enough time has passed, and return true. +// Otherwise, it does nothing and returns false. +func (s *AtMostEvery) Do(fn func()) bool { + if !s.updateLastCall() { + return false + } + fn() + return true +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/atmostevery_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/atmostevery_test.go new file mode 100644 index 0000000000..8dea4add71 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/atmostevery_test.go @@ -0,0 +1,51 @@ +/* +Copyright 2020 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 internal_test + +import ( + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/managedfields/internal" +) + +func TestAtMostEvery(t *testing.T) { + duration := time.Second + delay := 179 * time.Millisecond + atMostEvery := internal.NewAtMostEvery(delay) + count := 0 + exit := time.NewTicker(duration) + tick := time.NewTicker(2 * time.Millisecond) + defer exit.Stop() + defer tick.Stop() + + done := false + for !done { + select { + case <-exit.C: + done = true + case <-tick.C: + atMostEvery.Do(func() { + count++ + }) + } + } + + if expected := int(duration/delay) + 1; count > expected { + t.Fatalf("Function called %d times, should have been called less than or equal to %d times", count, expected) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/buildmanagerinfo.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/buildmanagerinfo.go new file mode 100644 index 0000000000..fa342ca135 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/buildmanagerinfo.go @@ -0,0 +1,74 @@ +/* +Copyright 2019 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 internal + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type buildManagerInfoManager struct { + fieldManager Manager + groupVersion schema.GroupVersion + subresource string +} + +var _ Manager = &buildManagerInfoManager{} + +// NewBuildManagerInfoManager creates a new Manager that converts the manager name into a unique identifier +// combining operation and version for update requests, and just operation for apply requests. +func NewBuildManagerInfoManager(f Manager, gv schema.GroupVersion, subresource string) Manager { + return &buildManagerInfoManager{ + fieldManager: f, + groupVersion: gv, + subresource: subresource, + } +} + +// Update implements Manager. +func (f *buildManagerInfoManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate) + if err != nil { + return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err) + } + return f.fieldManager.Update(liveObj, newObj, managed, manager) +} + +// Apply implements Manager. +func (f *buildManagerInfoManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { + manager, err := f.buildManagerInfo(manager, metav1.ManagedFieldsOperationApply) + if err != nil { + return nil, nil, fmt.Errorf("failed to build manager identifier: %v", err) + } + return f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force) +} + +func (f *buildManagerInfoManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) { + managerInfo := metav1.ManagedFieldsEntry{ + Manager: prefix, + Operation: operation, + APIVersion: f.groupVersion.String(), + Subresource: f.subresource, + } + if managerInfo.Manager == "" { + managerInfo.Manager = "unknown" + } + return BuildManagerIdentifier(&managerInfo) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers.go new file mode 100644 index 0000000000..a9530ff2b4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers.go @@ -0,0 +1,133 @@ +/* +Copyright 2019 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 internal + +import ( + "fmt" + "sort" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +type capManagersManager struct { + fieldManager Manager + maxUpdateManagers int + oldUpdatesManagerName string +} + +var _ Manager = &capManagersManager{} + +// NewCapManagersManager creates a new wrapped FieldManager which ensures that the number of managers from updates +// does not exceed maxUpdateManagers, by merging some of the oldest entries on each update. +func NewCapManagersManager(fieldManager Manager, maxUpdateManagers int) Manager { + return &capManagersManager{ + fieldManager: fieldManager, + maxUpdateManagers: maxUpdateManagers, + oldUpdatesManagerName: "ancient-changes", + } +} + +// Update implements Manager. +func (f *capManagersManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, manager) + if err != nil { + return object, managed, err + } + if managed, err = f.capUpdateManagers(managed); err != nil { + return nil, nil, fmt.Errorf("failed to cap update managers: %v", err) + } + return object, managed, nil +} + +// Apply implements Manager. +func (f *capManagersManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { + return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) +} + +// capUpdateManagers merges a number of the oldest update entries into versioned buckets, +// such that the number of entries from updates does not exceed f.maxUpdateManagers. +func (f *capManagersManager) capUpdateManagers(managed Managed) (newManaged Managed, err error) { + // Gather all entries from updates + updaters := []string{} + for manager, fields := range managed.Fields() { + if !fields.Applied() { + updaters = append(updaters, manager) + } + } + if len(updaters) <= f.maxUpdateManagers { + return managed, nil + } + + // If we have more than the maximum, sort the update entries by time, oldest first. + sort.Slice(updaters, func(i, j int) bool { + iTime, jTime, iSeconds, jSeconds := managed.Times()[updaters[i]], managed.Times()[updaters[j]], int64(0), int64(0) + if iTime != nil { + iSeconds = iTime.Unix() + } + if jTime != nil { + jSeconds = jTime.Unix() + } + if iSeconds != jSeconds { + return iSeconds < jSeconds + } + return updaters[i] < updaters[j] + }) + + // Merge the oldest updaters with versioned bucket managers until the number of updaters is under the cap + versionToFirstManager := map[string]string{} + for i, length := 0, len(updaters); i < len(updaters) && length > f.maxUpdateManagers; i++ { + manager := updaters[i] + vs := managed.Fields()[manager] + time := managed.Times()[manager] + version := string(vs.APIVersion()) + + // Create a new manager identifier for the versioned bucket entry. + // The version for this manager comes from the version of the update being merged into the bucket. + bucket, err := BuildManagerIdentifier(&metav1.ManagedFieldsEntry{ + Manager: f.oldUpdatesManagerName, + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: version, + }) + if err != nil { + return managed, fmt.Errorf("failed to create bucket manager for version %v: %v", version, err) + } + + // Merge the fieldets if this is not the first time the version was seen. + // Otherwise just record the manager name in versionToFirstManager + if first, ok := versionToFirstManager[version]; ok { + // If the bucket doesn't exists yet, create one. + if _, ok := managed.Fields()[bucket]; !ok { + s := managed.Fields()[first] + delete(managed.Fields(), first) + managed.Fields()[bucket] = s + } + + managed.Fields()[bucket] = fieldpath.NewVersionedSet(vs.Set().Union(managed.Fields()[bucket].Set()), vs.APIVersion(), vs.Applied()) + delete(managed.Fields(), manager) + length-- + + // Use the time from the update being merged into the bucket, since it is more recent. + managed.Times()[bucket] = time + } else { + versionToFirstManager[version] = manager + } + } + + return managed, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers_test.go new file mode 100644 index 0000000000..366b299a58 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/capmanagers_test.go @@ -0,0 +1,303 @@ +/* +Copyright 2019 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 internal_test + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + "time" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + internaltesting "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" + "k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +type fakeManager struct { + Manager internal.Manager + Error error +} + +var _ internal.Manager = &fakeManager{} + +func (f *fakeManager) Update(liveObj, newObj runtime.Object, managed internal.Managed, manager string) (runtime.Object, internal.Managed, error) { + if f.Error != nil { + return nil, nil, f.Error + } + if f.Manager != nil { + return f.Manager.Update(liveObj, newObj, managed, manager) + } + return newObj, managed, nil +} + +func (f *fakeManager) Apply(_, _ runtime.Object, _ internal.Managed, _ string, _ bool) (runtime.Object, internal.Managed, error) { + panic("not implemented") +} + +func TestCapManagersManagerMergesEntries(t *testing.T) { + f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), + "", + func(m internal.Manager) internal.Manager { + return internal.NewCapManagersManager(m, 3) + }) + + podWithLabels := func(labels ...string) runtime.Object { + labelMap := map[string]interface{}{} + for _, key := range labels { + labelMap[key] = "true" + } + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": labelMap, + }, + }, + } + obj.SetKind("Pod") + obj.SetAPIVersion("v1") + return obj + } + + if err := f.Update(podWithLabels("one"), "fieldmanager_test_update_1"); err != nil { + t.Fatalf("failed to update object: %v", err) + } + expectIdempotence(t, f) + + if err := f.Update(podWithLabels("one", "two"), "fieldmanager_test_update_2"); err != nil { + t.Fatalf("failed to update object: %v", err) + } + expectIdempotence(t, f) + + if err := f.Update(podWithLabels("one", "two", "three"), "fieldmanager_test_update_3"); err != nil { + t.Fatalf("failed to update object: %v", err) + } + expectIdempotence(t, f) + + if err := f.Update(podWithLabels("one", "two", "three", "four"), "fieldmanager_test_update_4"); err != nil { + t.Fatalf("failed to update object: %v", err) + } + expectIdempotence(t, f) + + if e, a := 3, len(f.ManagedFields()); e != a { + t.Fatalf("exected %v entries in managedFields, but got %v: %#v", e, a, f.ManagedFields()) + } + + if e, a := "ancient-changes", f.ManagedFields()[0].Manager; e != a { + t.Fatalf("exected first manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } + + if e, a := "fieldmanager_test_update_3", f.ManagedFields()[1].Manager; e != a { + t.Fatalf("exected second manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } + + if e, a := "fieldmanager_test_update_4", f.ManagedFields()[2].Manager; e != a { + t.Fatalf("exected third manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } + + expectManagesField(t, f, "ancient-changes", fieldpath.MakePathOrDie("metadata", "labels", "one")) + expectManagesField(t, f, "ancient-changes", fieldpath.MakePathOrDie("metadata", "labels", "two")) + expectManagesField(t, f, "fieldmanager_test_update_3", fieldpath.MakePathOrDie("metadata", "labels", "three")) + expectManagesField(t, f, "fieldmanager_test_update_4", fieldpath.MakePathOrDie("metadata", "labels", "four")) +} + +func TestCapUpdateManagers(t *testing.T) { + f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), + "", + func(m internal.Manager) internal.Manager { + return internal.NewCapManagersManager(m, 3) + }) + + set := func(fields ...string) *metav1.FieldsV1 { + s := fieldpath.NewSet() + for _, f := range fields { + s.Insert(fieldpath.MakePathOrDie(f)) + } + b, err := s.ToJSON() + if err != nil { + panic(fmt.Sprintf("error building ManagedFieldsEntry for test: %v", err)) + } + return metav1.NewFieldsV1(string(b)) + } + + entry := func(name string, version string, order int, fields *metav1.FieldsV1) metav1.ManagedFieldsEntry { + return metav1.ManagedFieldsEntry{ + Manager: name, + APIVersion: version, + Operation: "Update", + FieldsType: "FieldsV1", + FieldsV1: fields, + Time: &metav1.Time{Time: time.Time{}.Add(time.Hour * time.Duration(order))}, + } + } + + testCases := []struct { + name string + input []metav1.ManagedFieldsEntry + expected []metav1.ManagedFieldsEntry + }{ + { + name: "one version, no ancient changes", + input: []metav1.ManagedFieldsEntry{ + entry("update-manager1", "v1", 1, set("a")), + entry("update-manager2", "v1", 2, set("b")), + entry("update-manager3", "v1", 3, set("c")), + entry("update-manager4", "v1", 4, set("d")), + }, + expected: []metav1.ManagedFieldsEntry{ + entry("ancient-changes", "v1", 2, set("a", "b")), + entry("update-manager3", "v1", 3, set("c")), + entry("update-manager4", "v1", 4, set("d")), + }, + }, { + name: "one version, one ancient changes", + input: []metav1.ManagedFieldsEntry{ + entry("ancient-changes", "v1", 2, set("a", "b")), + entry("update-manager3", "v1", 3, set("c")), + entry("update-manager4", "v1", 4, set("d")), + entry("update-manager5", "v1", 5, set("e")), + }, + expected: []metav1.ManagedFieldsEntry{ + entry("ancient-changes", "v1", 3, set("a", "b", "c")), + entry("update-manager4", "v1", 4, set("d")), + entry("update-manager5", "v1", 5, set("e")), + }, + }, { + name: "two versions, no ancient changes", + input: []metav1.ManagedFieldsEntry{ + entry("update-manager1", "v1", 1, set("a")), + entry("update-manager2", "v2", 2, set("b")), + entry("update-manager3", "v1", 3, set("c")), + entry("update-manager4", "v1", 4, set("d")), + entry("update-manager5", "v1", 5, set("e")), + }, + expected: []metav1.ManagedFieldsEntry{ + entry("update-manager2", "v2", 2, set("b")), + entry("ancient-changes", "v1", 4, set("a", "c", "d")), + entry("update-manager5", "v1", 5, set("e")), + }, + }, { + name: "three versions, one ancient changes", + input: []metav1.ManagedFieldsEntry{ + entry("update-manager2", "v2", 2, set("b")), + entry("ancient-changes", "v1", 4, set("a", "c", "d")), + entry("update-manager5", "v1", 5, set("e")), + entry("update-manager6", "v3", 6, set("f")), + entry("update-manager7", "v2", 7, set("g")), + }, + expected: []metav1.ManagedFieldsEntry{ + entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), + entry("update-manager6", "v3", 6, set("f")), + entry("ancient-changes", "v2", 7, set("b", "g")), + }, + }, { + name: "three versions, two ancient changes", + input: []metav1.ManagedFieldsEntry{ + entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), + entry("update-manager6", "v3", 6, set("f")), + entry("ancient-changes", "v2", 7, set("b", "g")), + entry("update-manager8", "v3", 8, set("h")), + }, + expected: []metav1.ManagedFieldsEntry{ + entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), + entry("ancient-changes", "v2", 7, set("b", "g")), + entry("ancient-changes", "v3", 8, set("f", "h")), + }, + }, { + name: "four versions, two ancient changes", + input: []metav1.ManagedFieldsEntry{ + entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), + entry("update-manager6", "v3", 6, set("f")), + entry("ancient-changes", "v2", 7, set("b", "g")), + entry("update-manager8", "v4", 8, set("h")), + }, + expected: []metav1.ManagedFieldsEntry{ + entry("ancient-changes", "v1", 5, set("a", "c", "d", "e")), + entry("update-manager6", "v3", 6, set("f")), + entry("ancient-changes", "v2", 7, set("b", "g")), + entry("update-manager8", "v4", 8, set("h")), + }, + }, + } + + for _, tc := range testCases { + f.Reset() + live := f.Live() + accessor, err := meta.Accessor(live) + if err != nil { + t.Fatalf("%v: couldn't get accessor: %v", tc.name, err) + } + accessor.SetManagedFields(tc.input) + if err := f.Update(live, "no-op-update"); err != nil { + t.Fatalf("%v: failed to do no-op update to object: %v", tc.name, err) + } + + if e, a := tc.expected, f.ManagedFields(); !apiequality.Semantic.DeepEqual(e, a) { + t.Errorf("%v: unexpected value for managedFields:\nexpected: %v\n but got: %v", tc.name, mustMarshal(e), mustMarshal(a)) + } + expectIdempotence(t, f) + } +} + +// expectIdempotence does a no-op update and ensures that managedFields doesn't change by calling capUpdateManagers. +func expectIdempotence(t *testing.T, f managedfieldstest.TestFieldManager) { + before := []metav1.ManagedFieldsEntry{} + for _, m := range f.ManagedFields() { + before = append(before, *m.DeepCopy()) + } + + if err := f.Update(f.Live(), "no-op-update"); err != nil { + t.Fatalf("failed to do no-op update to object: %v", err) + } + + if after := f.ManagedFields(); !apiequality.Semantic.DeepEqual(before, after) { + t.Fatalf("exected idempotence, but managedFields changed:\nbefore: %v\n after: %v", mustMarshal(before), mustMarshal(after)) + } +} + +// expectManagesField ensures that manager m currently manages field path p. +func expectManagesField(t *testing.T, f managedfieldstest.TestFieldManager, m string, p fieldpath.Path) { + for _, e := range f.ManagedFields() { + if e.Manager == m { + var s fieldpath.Set + err := s.FromJSON(bytes.NewReader(e.FieldsV1.GetRawBytes())) + if err != nil { + t.Fatalf("error parsing managedFields for %v: %v: %#v", m, err, f.ManagedFields()) + } + if !s.Has(p) { + t.Fatalf("expected managedFields for %v to contain %v, but got:\n%v", m, p.String(), s.String()) + } + return + } + } + t.Fatalf("exected to find manager name %v, but got: %#v", m, f.ManagedFields()) +} + +func mustMarshal(i interface{}) string { + b, err := json.MarshalIndent(i, "", " ") + if err != nil { + panic(fmt.Sprintf("error marshalling %v to json: %v", i, err)) + } + return string(b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/conflict.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/conflict.go new file mode 100644 index 0000000000..1f07b004de --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/conflict.go @@ -0,0 +1,89 @@ +/* +Copyright 2019 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 internal + +import ( + "encoding/json" + "fmt" + "sort" + "strings" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/merge" +) + +// NewConflictError returns an error including details on the requests apply conflicts +func NewConflictError(conflicts merge.Conflicts) *errors.StatusError { + causes := []metav1.StatusCause{} + for _, conflict := range conflicts { + causes = append(causes, metav1.StatusCause{ + Type: metav1.CauseTypeFieldManagerConflict, + Message: fmt.Sprintf("conflict with %v", printManager(conflict.Manager)), + Field: conflict.Path.String(), + }) + } + return errors.NewApplyConflict(causes, getConflictMessage(conflicts)) +} + +func getConflictMessage(conflicts merge.Conflicts) string { + if len(conflicts) == 1 { + return fmt.Sprintf("Apply failed with 1 conflict: conflict with %v: %v", printManager(conflicts[0].Manager), conflicts[0].Path) + } + + m := map[string][]fieldpath.Path{} + for _, conflict := range conflicts { + m[conflict.Manager] = append(m[conflict.Manager], conflict.Path) + } + + uniqueManagers := []string{} + for manager := range m { + uniqueManagers = append(uniqueManagers, manager) + } + + // Print conflicts by sorted managers. + sort.Strings(uniqueManagers) + + messages := []string{} + for _, manager := range uniqueManagers { + messages = append(messages, fmt.Sprintf("conflicts with %v:", printManager(manager))) + for _, path := range m[manager] { + messages = append(messages, fmt.Sprintf("- %v", path)) + } + } + return fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), strings.Join(messages, "\n")) +} + +func printManager(manager string) string { + encodedManager := &metav1.ManagedFieldsEntry{} + if err := json.Unmarshal([]byte(manager), encodedManager); err != nil { + return fmt.Sprintf("%q", manager) + } + managerStr := fmt.Sprintf("%q", encodedManager.Manager) + if encodedManager.Subresource != "" { + managerStr = fmt.Sprintf("%s with subresource %q", managerStr, encodedManager.Subresource) + } + if encodedManager.Operation == metav1.ManagedFieldsOperationUpdate { + if encodedManager.Time == nil { + return fmt.Sprintf("%s using %v", managerStr, encodedManager.APIVersion) + } + return fmt.Sprintf("%s using %v at %v", managerStr, encodedManager.APIVersion, encodedManager.Time.UTC().Format(time.RFC3339)) + } + return managerStr +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/conflict_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/conflict_test.go new file mode 100644 index 0000000000..cf13711253 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/conflict_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2019 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 internal_test + +import ( + "net/http" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/merge" +) + +// TestNewConflictError tests that NewConflictError creates the correct StatusError for a given smd Conflicts +func TestNewConflictError(t *testing.T) { + testCases := []struct { + conflict merge.Conflicts + expected *errors.StatusError + }{ + { + conflict: merge.Conflicts{ + merge.Conflict{ + Manager: `{"manager":"foo","operation":"Update","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`, + Path: fieldpath.MakePathOrDie("spec", "replicas"), + }, + }, + expected: &errors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusConflict, + Reason: metav1.StatusReasonConflict, + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{ + { + Type: metav1.CauseTypeFieldManagerConflict, + Message: `conflict with "foo" using v1 at 2001-02-03T04:05:06Z`, + Field: ".spec.replicas", + }, + }, + }, + Message: `Apply failed with 1 conflict: conflict with "foo" using v1 at 2001-02-03T04:05:06Z: .spec.replicas`, + }, + }, + }, + { + conflict: merge.Conflicts{ + merge.Conflict{ + Manager: `{"manager":"foo","operation":"Update","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`, + Path: fieldpath.MakePathOrDie("spec", "replicas"), + }, + merge.Conflict{ + Manager: `{"manager":"bar","operation":"Apply"}`, + Path: fieldpath.MakePathOrDie("metadata", "labels", "app"), + }, + }, + expected: &errors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusConflict, + Reason: metav1.StatusReasonConflict, + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{ + { + Type: metav1.CauseTypeFieldManagerConflict, + Message: `conflict with "foo" using v1 at 2001-02-03T04:05:06Z`, + Field: ".spec.replicas", + }, + { + Type: metav1.CauseTypeFieldManagerConflict, + Message: `conflict with "bar"`, + Field: ".metadata.labels.app", + }, + }, + }, + Message: `Apply failed with 2 conflicts: conflicts with "bar": +- .metadata.labels.app +conflicts with "foo" using v1 at 2001-02-03T04:05:06Z: +- .spec.replicas`, + }, + }, + }, + { + conflict: merge.Conflicts{ + merge.Conflict{ + Manager: `{"manager":"foo","operation":"Update","subresource":"scale","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`, + Path: fieldpath.MakePathOrDie("spec", "replicas"), + }, + }, + expected: &errors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusConflict, + Reason: metav1.StatusReasonConflict, + Details: &metav1.StatusDetails{ + Causes: []metav1.StatusCause{ + { + Type: metav1.CauseTypeFieldManagerConflict, + Message: `conflict with "foo" with subresource "scale" using v1 at 2001-02-03T04:05:06Z`, + Field: ".spec.replicas", + }, + }, + }, + Message: `Apply failed with 1 conflict: conflict with "foo" with subresource "scale" using v1 at 2001-02-03T04:05:06Z: .spec.replicas`, + }, + }, + }, + } + for _, tc := range testCases { + actual := internal.NewConflictError(tc.conflict) + if !reflect.DeepEqual(tc.expected, actual) { + t.Errorf("Expected to get\n%+v\nbut got\n%+v", tc.expected.ErrStatus, actual.ErrStatus) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fieldmanager.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fieldmanager.go new file mode 100644 index 0000000000..2e2db65561 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fieldmanager.go @@ -0,0 +1,213 @@ +/* +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 internal + +import ( + "fmt" + "reflect" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" + "sigs.k8s.io/structured-merge-diff/v6/merge" +) + +// DefaultMaxUpdateManagers defines the default maximum retained number of managedFields entries from updates +// if the number of update managers exceeds this, the oldest entries will be merged until the number is below the maximum. +// TODO(jennybuckley): Determine if this is really the best value. Ideally we wouldn't unnecessarily merge too many entries. +const DefaultMaxUpdateManagers int = 10 + +// DefaultTrackOnCreateProbability defines the default probability that the field management of an object +// starts being tracked from the object's creation, instead of from the first time the object is applied to. +const DefaultTrackOnCreateProbability float32 = 1 + +var atMostEverySecond = NewAtMostEvery(time.Second) + +// FieldManager updates the managed fields and merges applied +// configurations. +type FieldManager struct { + fieldManager Manager + subresource string +} + +// NewFieldManager creates a new FieldManager that decodes, manages, then re-encodes managedFields +// on update and apply requests. +func NewFieldManager(f Manager, subresource string) *FieldManager { + return &FieldManager{fieldManager: f, subresource: subresource} +} + +// newDefaultFieldManager is a helper function which wraps a Manager with certain default logic. +func NewDefaultFieldManager(f Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, subresource string) *FieldManager { + return NewFieldManager( + NewVersionCheckManager( + NewLastAppliedUpdater( + NewLastAppliedManager( + NewProbabilisticSkipNonAppliedManager( + NewCapManagersManager( + NewBuildManagerInfoManager( + NewManagedFieldsUpdater( + NewStripMetaManager(f), + ), kind.GroupVersion(), subresource, + ), DefaultMaxUpdateManagers, + ), objectCreater, DefaultTrackOnCreateProbability, + ), typeConverter, objectConverter, kind.GroupVersion(), + ), + ), kind, + ), subresource, + ) +} + +func decodeLiveOrNew(liveObj, newObj runtime.Object, ignoreManagedFieldsFromRequestObject bool) (Managed, error) { + liveAccessor, err := meta.Accessor(liveObj) + if err != nil { + return nil, err + } + + // We take the managedFields of the live object in case the request tries to + // manually set managedFields via a subresource. + if ignoreManagedFieldsFromRequestObject { + return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields())) + } + + // If the object doesn't have metadata, we should just return without trying to + // set the managedFields at all, so creates/updates/patches will work normally. + newAccessor, err := meta.Accessor(newObj) + if err != nil { + return nil, err + } + + if isResetManagedFields(newAccessor.GetManagedFields()) { + return NewEmptyManaged(), nil + } + + // If the managed field is empty or we failed to decode it, + // let's try the live object. This is to prevent clients who + // don't understand managedFields from deleting it accidentally. + managed, err := DecodeManagedFields(newAccessor.GetManagedFields()) + if err != nil || len(managed.Fields()) == 0 { + return emptyManagedFieldsOnErr(DecodeManagedFields(liveAccessor.GetManagedFields())) + } + return managed, nil +} + +func emptyManagedFieldsOnErr(managed Managed, err error) (Managed, error) { + if err != nil { + return NewEmptyManaged(), nil + } + return managed, nil +} + +// Update is used when the object has already been merged (non-apply +// use-case), and simply updates the managed fields in the output +// object. +func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (object runtime.Object, err error) { + // First try to decode the managed fields provided in the update, + // This is necessary to allow directly updating managed fields. + isSubresource := f.subresource != "" + managed, err := decodeLiveOrNew(liveObj, newObj, isSubresource) + if err != nil { + return newObj, nil + } + + RemoveObjectManagedFields(newObj) + + if object, managed, err = f.fieldManager.Update(liveObj, newObj, managed, manager); err != nil { + return nil, err + } + + if err = EncodeObjectManagedFields(object, managed); err != nil { + return nil, fmt.Errorf("failed to encode managed fields: %v", err) + } + + return object, nil +} + +// UpdateNoErrors is the same as Update, but it will not return +// errors. If an error happens, we preserve the managedFields from +// liveObj. +func (f *FieldManager) UpdateNoErrors(liveObj, newObj runtime.Object, manager string) runtime.Object { + obj, err := f.Update(liveObj, newObj, manager) + if err != nil { + // Preserve the managedFields from the live object rather than + // stripping them entirely, to avoid silent data loss when the + // managedFields update fails (e.g. due to an unavailable + // conversion webhook). + // Note: meta.Accessor for liveObj and newObj below never return an error in this code branch, + // because if they would f.Update above would return "newObj, nil". Accordingly, the case + // where one of the accessors returns an error is not handled here. + if liveAccessor, aErr := meta.Accessor(liveObj); aErr == nil { + if newAccessor, aErr := meta.Accessor(newObj); aErr == nil { + atMostEverySecond.Do(func() { + //nolint:logcheck // Should not be reached. + klog.ErrorS(err, "[SHOULD NOT HAPPEN] failed to update managedFields (restored previous managedFields from live object)", "versionKind", + newObj.GetObjectKind().GroupVersionKind(), "namespace", newAccessor.GetNamespace(), "name", newAccessor.GetName()) + }) + newAccessor.SetManagedFields(liveAccessor.GetManagedFields()) + } + } + return newObj + } + return obj +} + +// Returns true if the managedFields indicate that the user is trying to +// reset the managedFields, i.e. if the list is non-nil but empty, or if +// the list has one empty item. +func isResetManagedFields(managedFields []metav1.ManagedFieldsEntry) bool { + if len(managedFields) == 0 { + return managedFields != nil + } + + if len(managedFields) == 1 { + return reflect.DeepEqual(managedFields[0], metav1.ManagedFieldsEntry{}) + } + + return false +} + +// Apply is used when server-side apply is called, as it merges the +// object and updates the managed fields. +func (f *FieldManager) Apply(liveObj, appliedObj runtime.Object, manager string, force bool) (object runtime.Object, err error) { + // If the object doesn't have metadata, apply isn't allowed. + accessor, err := meta.Accessor(liveObj) + if err != nil { + return nil, fmt.Errorf("couldn't get accessor: %v", err) + } + + // Decode the managed fields in the live object, since it isn't allowed in the patch. + managed, err := DecodeManagedFields(accessor.GetManagedFields()) + if err != nil { + return nil, fmt.Errorf("failed to decode managed fields: %v", err) + } + + object, managed, err = f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force) + if err != nil { + if conflicts, ok := err.(merge.Conflicts); ok { + return nil, NewConflictError(conflicts) + } + return nil, err + } + + if err = EncodeObjectManagedFields(object, managed); err != nil { + return nil, fmt.Errorf("failed to encode managed fields: %v", err) + } + + return object, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fieldmanager_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fieldmanager_test.go new file mode 100644 index 0000000000..67ec621532 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fieldmanager_test.go @@ -0,0 +1,105 @@ +/* +Copyright 2019 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 internal_test + +import ( + "encoding/json" + "errors" + "os" + "path/filepath" + "strings" + "testing" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + internaltesting "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +func TestFieldManagerUpdateNoErrors(t *testing.T) { + fm := &fakeManager{} + f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), + "", + func(m internal.Manager) internal.Manager { + fm.Manager = m + return fm + }) + + podWithLabels := func(labels ...string) runtime.Object { + labelMap := map[string]interface{}{} + for _, key := range labels { + labelMap[key] = "true" + } + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": labelMap, + }, + }, + } + obj.SetKind("Pod") + obj.SetAPIVersion("v1") + return obj + } + + f.UpdateNoErrors(podWithLabels("one"), "fieldmanager_test_update_1") + if len(f.ManagedFields()) == 0 { + t.Fatalf("expected managedFields to be set, but they are empty") + } + + before := []metav1.ManagedFieldsEntry{} + for _, m := range f.ManagedFields() { + before = append(before, *m.DeepCopy()) + } + + // Inject an error so UpdateNoErrors will hit the error code path. + fm.Error = errors.New("test error") + f.UpdateNoErrors(podWithLabels("one", "two"), "fieldmanager_test_update_1") + + if after := f.ManagedFields(); !apiequality.Semantic.DeepEqual(before, after) { + t.Fatalf("expected idempotence, but managedFields changed:\nbefore: %v\n after: %v", mustMarshal(before), mustMarshal(after)) + } +} + +var fakeTypeConverter = func() internal.TypeConverter { + data, err := os.ReadFile(filepath.Join( + strings.Repeat(".."+string(filepath.Separator), 8), + "api", "openapi-spec", "swagger.json")) + if err != nil { + panic(err) + } + convertedDefs := map[string]*spec.Schema{} + spec := spec.Swagger{} + if err := json.Unmarshal(data, &spec); err != nil { + panic(err) + } + + for k, v := range spec.Definitions { + vCopy := v + convertedDefs[k] = &vCopy + } + + typeConverter, err := internal.NewTypeConverter(convertedDefs, false) + if err != nil { + panic(err) + } + return typeConverter +}() diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fields.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fields.go new file mode 100644 index 0000000000..549412b41d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fields.go @@ -0,0 +1,46 @@ +/* +Copyright 2018 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 internal + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +// EmptyFields represents a set with no paths +// It looks like metav1.Fields{Raw: []byte("{}")} +var EmptyFields = func() metav1.FieldsV1 { + f, err := SetToFields(*fieldpath.NewSet()) + if err != nil { + panic("should never happen") + } + return f +}() + +// FieldsToSet creates a set paths from an input trie of fields +func FieldsToSet(f metav1.FieldsV1) (s fieldpath.Set, err error) { + err = s.FromJSON(f.GetRawReader()) + return s, err +} + +// SetToFields creates a trie of fields from an input set of paths +func SetToFields(s fieldpath.Set) (f metav1.FieldsV1, err error) { + raw, err := s.ToJSON() + f.SetRawBytes(raw) + return f, err +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fields_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fields_test.go new file mode 100644 index 0000000000..e397d2c681 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/fields_test.go @@ -0,0 +1,143 @@ +/* +Copyright 2018 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 internal + +import ( + "reflect" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +// TestFieldsRoundTrip tests that a fields trie can be round tripped as a path set +func TestFieldsRoundTrip(t *testing.T) { + tests := []metav1.FieldsV1{ + *metav1.NewFieldsV1(`{"f:metadata":{".":{},"f:name":{}}}`), + EmptyFields, + } + + for _, test := range tests { + set, err := FieldsToSet(test) + if err != nil { + t.Fatalf("Failed to create path set: %v", err) + } + output, err := SetToFields(set) + if err != nil { + t.Fatalf("Failed to create fields trie from path set: %v", err) + } + if !reflect.DeepEqual(test, output) { + t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output) + } + } +} + +// TestFieldsToSetError tests that errors are picked up by FieldsToSet +func TestFieldsToSetError(t *testing.T) { + tests := []struct { + fields metav1.FieldsV1 + errString string + }{ + { + fields: *metav1.NewFieldsV1( + `{"k:{invalid json}":{"f:name":{},".":{}}}`, + ), + errString: "ReadObjectCB", + }, + } + + for _, test := range tests { + _, err := FieldsToSet(test.fields) + if err == nil || !strings.Contains(err.Error(), test.errString) { + t.Fatalf("Expected error to contain %q but got: %v", test.errString, err) + } + } +} + +// TestSetToFieldsError tests that errors are picked up by SetToFields +func TestSetToFieldsError(t *testing.T) { + validName := "ok" + invalidPath := fieldpath.Path([]fieldpath.PathElement{{}, {FieldName: &validName}}) + + tests := []struct { + set fieldpath.Set + errString string + }{ + { + set: *fieldpath.NewSet(invalidPath), + errString: "invalid PathElement", + }, + } + + for _, test := range tests { + _, err := SetToFields(test.set) + if err == nil || !strings.Contains(err.Error(), test.errString) { + t.Fatalf("Expected error to contain %q but got: %v", test.errString, err) + } + } +} + +func BenchmarkSetToFields(b *testing.B) { + set := fieldpath.NewSet( + fieldpath.MakePathOrDie("foo", 0, "bar", "baz"), + fieldpath.MakePathOrDie("foo", 0, "bar", "zot"), + fieldpath.MakePathOrDie("foo", 0, "bar"), + fieldpath.MakePathOrDie("foo", 0), + fieldpath.MakePathOrDie("foo", 1, "bar", "baz"), + fieldpath.MakePathOrDie("foo", 1, "bar"), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "first")), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "first"), "bar"), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "second"), "bar"), + ) + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := SetToFields(*set) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkFieldsToSet(b *testing.B) { + set := fieldpath.NewSet( + fieldpath.MakePathOrDie("foo", 0, "bar", "baz"), + fieldpath.MakePathOrDie("foo", 0, "bar", "zot"), + fieldpath.MakePathOrDie("foo", 0, "bar"), + fieldpath.MakePathOrDie("foo", 0), + fieldpath.MakePathOrDie("foo", 1, "bar", "baz"), + fieldpath.MakePathOrDie("foo", 1, "bar"), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "first")), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "first"), "bar"), + fieldpath.MakePathOrDie("qux", fieldpath.KeyByFields("name", "second"), "bar"), + ) + fields, err := SetToFields(*set) + if err != nil { + b.Fatal(err) + } + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := FieldsToSet(fields) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastapplied.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastapplied.go new file mode 100644 index 0000000000..b00b6b8298 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastapplied.go @@ -0,0 +1,50 @@ +/* +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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/runtime" +) + +// LastAppliedConfigAnnotation is the annotation used to store the previous +// configuration of a resource for use in a three way diff by UpdateApplyAnnotation. +// +// This is a copy of the corev1 annotation since we don't want to depend on the whole package. +const LastAppliedConfigAnnotation = "kubectl.kubernetes.io/last-applied-configuration" + +// SetLastApplied sets the last-applied annotation the given value in +// the object. +func SetLastApplied(obj runtime.Object, value string) error { + accessor, err := meta.Accessor(obj) + if err != nil { + panic(fmt.Sprintf("couldn't get accessor: %v", err)) + } + var annotations = accessor.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + annotations[LastAppliedConfigAnnotation] = value + if err := apimachineryvalidation.ValidateAnnotationsSize(annotations); err != nil { + delete(annotations, LastAppliedConfigAnnotation) + } + accessor.SetAnnotations(annotations) + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedmanager.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedmanager.go new file mode 100644 index 0000000000..d58a1108dc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedmanager.go @@ -0,0 +1,171 @@ +/* +Copyright 2020 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 internal + +import ( + "encoding/json" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/merge" +) + +type lastAppliedManager struct { + fieldManager Manager + typeConverter TypeConverter + objectConverter runtime.ObjectConvertor + groupVersion schema.GroupVersion +} + +var _ Manager = &lastAppliedManager{} + +// NewLastAppliedManager converts the client-side apply annotation to +// server-side apply managed fields +func NewLastAppliedManager(fieldManager Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, groupVersion schema.GroupVersion) Manager { + return &lastAppliedManager{ + fieldManager: fieldManager, + typeConverter: typeConverter, + objectConverter: objectConverter, + groupVersion: groupVersion, + } +} + +// Update implements Manager. +func (f *lastAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + return f.fieldManager.Update(liveObj, newObj, managed, manager) +} + +// Apply will consider the last-applied annotation +// for upgrading an object managed by client-side apply to server-side apply +// without conflicts. +func (f *lastAppliedManager) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { + newLiveObj, newManaged, newErr := f.fieldManager.Apply(liveObj, newObj, managed, manager, force) + // Upgrade the client-side apply annotation only from kubectl server-side-apply. + // To opt-out of this behavior, users may specify a different field manager. + if manager != "kubectl" { + return newLiveObj, newManaged, newErr + } + + // Check if we have conflicts + if newErr == nil { + return newLiveObj, newManaged, newErr + } + conflicts, ok := newErr.(merge.Conflicts) + if !ok { + return newLiveObj, newManaged, newErr + } + conflictSet := conflictsToSet(conflicts) + + // Check if conflicts are allowed due to client-side apply, + // and if so, then force apply + allowedConflictSet, err := f.allowedConflictsFromLastApplied(liveObj) + if err != nil { + return newLiveObj, newManaged, newErr + } + if !conflictSet.Difference(allowedConflictSet).Empty() { + newConflicts := conflictsDifference(conflicts, allowedConflictSet) + return newLiveObj, newManaged, newConflicts + } + + return f.fieldManager.Apply(liveObj, newObj, managed, manager, true) +} + +func (f *lastAppliedManager) allowedConflictsFromLastApplied(liveObj runtime.Object) (*fieldpath.Set, error) { + var accessor, err = meta.Accessor(liveObj) + if err != nil { + panic(fmt.Sprintf("couldn't get accessor: %v", err)) + } + + // If there is no client-side apply annotation, then there is nothing to do + var annotations = accessor.GetAnnotations() + if annotations == nil { + return nil, fmt.Errorf("no last applied annotation") + } + var lastApplied, ok = annotations[LastAppliedConfigAnnotation] + if !ok || lastApplied == "" { + return nil, fmt.Errorf("no last applied annotation") + } + + liveObjVersioned, err := f.objectConverter.ConvertToVersion(liveObj, f.groupVersion) + if err != nil { + return nil, fmt.Errorf("failed to convert live obj to versioned: %v", err) + } + + liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned) + if err != nil { + return nil, fmt.Errorf("failed to convert live obj to typed: %v", err) + } + + var lastAppliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}} + err = json.Unmarshal([]byte(lastApplied), lastAppliedObj) + if err != nil { + return nil, fmt.Errorf("failed to decode last applied obj: %v in '%s'", err, lastApplied) + } + + if lastAppliedObj.GetAPIVersion() != f.groupVersion.String() { + return nil, fmt.Errorf("expected version of last applied to match live object '%s', but got '%s': %v", f.groupVersion.String(), lastAppliedObj.GetAPIVersion(), err) + } + + lastAppliedObjTyped, err := f.typeConverter.ObjectToTyped(lastAppliedObj) + if err != nil { + return nil, fmt.Errorf("failed to convert last applied to typed: %v", err) + } + + lastAppliedObjFieldSet, err := lastAppliedObjTyped.ToFieldSet() + if err != nil { + return nil, fmt.Errorf("failed to create fieldset for last applied object: %v", err) + } + + comparison, err := lastAppliedObjTyped.Compare(liveObjTyped) + if err != nil { + return nil, fmt.Errorf("failed to compare last applied object and live object: %v", err) + } + + // Remove fields in last applied that are different, added, or missing in + // the live object. + // Because last-applied fields don't match the live object fields, + // then we don't own these fields. + lastAppliedObjFieldSet = lastAppliedObjFieldSet. + Difference(comparison.Modified). + Difference(comparison.Added). + Difference(comparison.Removed) + + return lastAppliedObjFieldSet, nil +} + +// TODO: replace with merge.Conflicts.ToSet() +func conflictsToSet(conflicts merge.Conflicts) *fieldpath.Set { + conflictSet := fieldpath.NewSet() + for _, conflict := range []merge.Conflict(conflicts) { + conflictSet.Insert(conflict.Path) + } + return conflictSet +} + +func conflictsDifference(conflicts merge.Conflicts, s *fieldpath.Set) merge.Conflicts { + newConflicts := []merge.Conflict{} + for _, conflict := range []merge.Conflict(conflicts) { + if !s.Has(conflict.Path) { + newConflicts = append(newConflicts, conflict) + } + } + return newConflicts +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedmanager_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedmanager_test.go new file mode 100644 index 0000000000..7824409569 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedmanager_test.go @@ -0,0 +1,1007 @@ +/* +Copyright 2020 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 internal_test + +import ( + "fmt" + "reflect" + "testing" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + "k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest" + yamlutil "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/merge" + "sigs.k8s.io/yaml" +) + +type testArgs struct { + lastApplied []byte + original []byte + applied []byte + fieldManager string + expectConflictSet *fieldpath.Set +} + +// TestApplyUsingLastAppliedAnnotation tests that applying to an object +// created with the client-side apply last-applied annotation +// will not give conflicts +func TestApplyUsingLastAppliedAnnotation(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment")) + + tests := []testArgs{ + { + fieldManager: "kubectl", + lastApplied: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 3 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image-v1 + - name: my-c2 + image: my-image2 +`), + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app # missing from last-applied +spec: + replicas: 100 # does not match last-applied + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image-v2 # does no match last-applied + # note that second container in last-applied is missing +`), + applied: []byte(` +# test conflicts due to fields not allowed by last-applied + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-new-label # NOT allowed: update label +spec: + replicas: 333 # NOT allowed: update replicas + selector: + matchLabels: + app: my-new-label # allowed: update label + template: + metadata: + labels: + app: my-new-label # allowed: update-label + spec: + containers: + - name: my-c + image: my-image-new # NOT allowed: update image +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("metadata", "labels", "app"), + fieldpath.MakePathOrDie("spec", "replicas"), + fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"), + ), + }, + { + fieldManager: "kubectl", + lastApplied: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 3 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`), + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 # does not match last applied + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`), + applied: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-new-label +spec: + replicas: 3 # expect conflict + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("spec", "replicas"), + ), + }, + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`), + applied: []byte(` +# applied object matches original + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`), + }, + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 3 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`), + applied: []byte(` +# test allowed update with no conflicts + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-new-label # update label +spec: + replicas: 333 # update replicas + selector: + matchLabels: + app: my-new-label # update label + template: + metadata: + labels: + app: my-new-label # update-label + spec: + containers: + - name: my-c + image: my-image +`), + }, + { + fieldManager: "not_kubectl", + lastApplied: []byte(` +# expect conflicts because field manager is NOT kubectl + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 3 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image-v1 +`), + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 # does not match last-applied + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image-v2 # does no match last-applied +`), + applied: []byte(` +# test conflicts due to fields not allowed by last-applied + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-new-label # update label +spec: + replicas: 333 # update replicas + selector: + matchLabels: + app: my-new-label # update label + template: + metadata: + labels: + app: my-new-label # update-label + spec: + containers: + - name: my-c + image: my-image-new # update image +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("metadata", "labels", "app"), + fieldpath.MakePathOrDie("spec", "replicas"), + fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic + fieldpath.MakePathOrDie("spec", "template", "metadata", "labels", "app"), + fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"), + ), + }, + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 3 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`), + applied: []byte(` +# test allowed update with no conflicts + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-new-label +spec: + replicas: 3 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-new-image # update image +`), + }, + { + fieldManager: "not_kubectl", + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-app +spec: + replicas: 100 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`), + applied: []byte(` + +# expect changes to fail because field manager is not kubectl + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + labels: + app: my-new-label # update label +spec: + replicas: 3 # update replicas + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-new-image # update image +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("metadata", "labels", "app"), + fieldpath.MakePathOrDie("spec", "replicas"), + fieldpath.MakePathOrDie("spec", "template", "spec", "containers", fieldpath.KeyByFields("name", "my-c"), "image"), + ), + }, + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 3 +`), + applied: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 100 # update replicas +`), + }, + { + fieldManager: "kubectl", + lastApplied: []byte(` +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 3 +`), + original: []byte(` +apiVersion: apps/v1 # expect conflict due to apiVersion mismatch with last-applied +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 3 +`), + applied: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 100 # update replicas +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("spec", "replicas"), + ), + }, + { + fieldManager: "kubectl", + lastApplied: []byte(` +apiVerison: foo +kind: bar +spec: expect conflict due to invalid object +`), + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 3 +`), + applied: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 100 # update replicas +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("spec", "replicas"), + ), + }, + { + fieldManager: "kubectl", + // last-applied is empty + lastApplied: []byte{}, + original: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 3 +`), + applied: []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 100 # update replicas +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("spec", "replicas"), + ), + }, + } + + testConflicts(t, f, tests) +} + +func TestServiceApply(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Service")) + + tests := []testArgs{ + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: v1 +kind: Service +metadata: + name: test +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + selector: + old: test +`), + applied: []byte(` +# All accepted while using the same field manager + +apiVersion: v1 +kind: Service +metadata: + name: test +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8444 + selector: + new: test +`), + }, + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: v1 +kind: Service +metadata: + name: test +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + selector: + old: test +`), + applied: []byte(` +# Allowed to remove selectors while using the same field manager + +apiVersion: v1 +kind: Service +metadata: + name: test +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8444 + selector: {} +`), + }, + { + fieldManager: "not_kubectl", + original: []byte(` +apiVersion: v1 +kind: Service +metadata: + name: test +spec: + ports: + - name: https + port: 443 + protocol: TCP # TODO: issue - this is a defaulted field, should not be required in a new spec + targetPort: 8443 + selector: + old: test +`), + applied: []byte(` +# test selector update not allowed by last-applied + +apiVersion: v1 +kind: Service +metadata: + name: test +spec: + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8444 + selector: + new: test +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic + fieldpath.MakePathOrDie("spec", "ports", fieldpath.KeyByFields("port", 443, "protocol", "TCP"), "targetPort"), + ), + }, + } + + testConflicts(t, f, tests) +} + +func TestReplicationControllerApply(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ReplicationController")) + + tests := []testArgs{ + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: v1 +kind: ReplicationController +metadata: + name: test +spec: + replicas: 0 + selector: + old: test +`), + applied: []byte(` +# All accepted while using the same field manager + +apiVersion: v1 +kind: ReplicationController +metadata: + name: test +spec: + replicas: 3 + selector: + new: test +`), + }, + { + fieldManager: "not_kubectl", + original: []byte(` +apiVersion: v1 +kind: ReplicationController +metadata: + name: test +spec: + replicas: 0 + selector: + old: test +`), + applied: []byte(` +# test selector update not allowed by last-applied + +apiVersion: v1 +kind: ReplicationController +metadata: + name: test +spec: + replicas: 3 + selector: + new: test +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("spec", "selector"), // selector is atomic + fieldpath.MakePathOrDie("spec", "replicas"), + ), + }, + } + + testConflicts(t, f, tests) +} + +func TestPodApply(t *testing.T) { + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod")) + + tests := []testArgs{ + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: test +spec: + containers: + - args: + - -v=2 + command: + - controller + image: some.registry/app:latest + name: doJob + nodeName: definetlyControlPlane + nodeSelector: + node-role.kubernetes.io/master: "" +`), + applied: []byte(` +# All accepted while using the same field manager + +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: test +spec: + containers: + - args: + - -v=2 + command: + - controller + image: some.registry/app:latest + name: doJob + nodeSelector: + node-role.kubernetes.io/worker: "" +`), + }, + { + fieldManager: "not_kubectl", + original: []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: test +spec: + containers: + - args: + - -v=2 + command: + - controller + image: some.registry/app:latest + name: doJob + nodeName: definetlyControlPlane + nodeSelector: + node-role.kubernetes.io/master: "" +`), + applied: []byte(` +# test selector update not allowed by last-applied + +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: test +spec: + containers: + - args: + - -v=2 + command: + - controller + image: some.registry/app:latest + name: doJob + nodeName: definetlyControlPlane + nodeSelector: + node-role.kubernetes.io/master: "" + otherNodeType: "" +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("spec", "nodeSelector"), // selector is atomic + ), + }, + { + fieldManager: "not_kubectl", + original: []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: test +spec: + containers: + - args: + - -v=2 + command: + - controller + image: some.registry/app:latest + name: doJob + nodeName: definetlyControlPlane + nodeSelector: + node-role.kubernetes.io/master: "" +`), + applied: []byte(` +# purging selector not allowed for different manager + +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: test +spec: + containers: + - args: + - -v=2 + command: + - controller + image: some.registry/app:latest + name: doJob + nodeName: another + nodeSelector: {} +`), + expectConflictSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("spec", "nodeSelector"), // selector is atomic + fieldpath.MakePathOrDie("spec", "nodeName"), + ), + }, + { + fieldManager: "kubectl", + original: []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: test +spec: + containers: + - args: + - -v=2 + command: + - controller + image: some.registry/app:latest + name: doJob + nodeName: definetlyControlPlane + nodeSelector: + node-role.kubernetes.io/master: "" +`), + applied: []byte(` +# same manager could purge nodeSelector + +apiVersion: v1 +kind: Pod +metadata: + name: test + namespace: test +spec: + containers: + - args: + - -v=2 + command: + - controller + image: some.registry/app:latest + name: doJob + nodeName: another + nodeSelector: {} +`), + }, + } + + testConflicts(t, f, tests) +} + +func testConflicts(t *testing.T, f managedfieldstest.TestFieldManager, tests []testArgs) { + for i, test := range tests { + t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { + f.Reset() + + originalObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal(test.original, &originalObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + + if test.lastApplied == nil { + test.lastApplied = test.original + } + if err := setLastAppliedFromEncoded(originalObj, test.lastApplied); err != nil { + t.Errorf("failed to set last applied: %v", err) + } + + if err := f.Update(originalObj, "test_client_side_apply"); err != nil { + t.Errorf("failed to apply object: %v", err) + } + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal(test.applied, &appliedObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + + err := f.Apply(appliedObj, test.fieldManager, false) + + if test.expectConflictSet == nil { + if err != nil { + t.Errorf("expected no error but got %v", err) + } + } else { + if err == nil || !apierrors.IsConflict(err) { + t.Errorf("expected to get conflicts but got %v", err) + } + + expectedConflicts := merge.Conflicts{} + test.expectConflictSet.Iterate(func(p fieldpath.Path) { + expectedConflicts = append(expectedConflicts, merge.Conflict{ + Manager: fmt.Sprintf(`{"manager":"test_client_side_apply","operation":"Update","apiVersion":"%s"}`, f.APIVersion()), + Path: p, + }) + }) + expectedConflictErr := internal.NewConflictError(expectedConflicts) + if !reflect.DeepEqual(expectedConflictErr, err) { + t.Errorf("expected to get\n%+v\nbut got\n%+v", expectedConflictErr, err) + } + + // Yet force should resolve all conflicts + err = f.Apply(appliedObj, test.fieldManager, true) + if err != nil { + t.Errorf("unexpected error during force ownership apply: %v", err) + } + + } + + // Eventually resource should contain applied changes + if !apiequality.Semantic.DeepDerivative(appliedObj, f.Live()) { + t.Errorf("expected equal resource: \n%#v, got: \n%#v", appliedObj, f.Live()) + } + }) + } +} + +func yamlToJSON(y []byte) (string, error) { + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal(y, &obj.Object); err != nil { + return "", fmt.Errorf("error decoding YAML: %v", err) + } + serialization, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return "", fmt.Errorf("error encoding object: %v", err) + } + json, err := yamlutil.ToJSON(serialization) + if err != nil { + return "", fmt.Errorf("error converting to json: %v", err) + } + return string(json), nil +} + +func setLastAppliedFromEncoded(obj runtime.Object, lastApplied []byte) error { + lastAppliedJSON, err := yamlToJSON(lastApplied) + if err != nil { + return err + } + return internal.SetLastApplied(obj, lastAppliedJSON) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedupdater.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedupdater.go new file mode 100644 index 0000000000..06e6c5d8ce --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedupdater.go @@ -0,0 +1,102 @@ +/* +Copyright 2020 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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +type lastAppliedUpdater struct { + fieldManager Manager +} + +var _ Manager = &lastAppliedUpdater{} + +// NewLastAppliedUpdater sets the client-side apply annotation up to date with +// server-side apply managed fields +func NewLastAppliedUpdater(fieldManager Manager) Manager { + return &lastAppliedUpdater{ + fieldManager: fieldManager, + } +} + +// Update implements Manager. +func (f *lastAppliedUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + return f.fieldManager.Update(liveObj, newObj, managed, manager) +} + +// server-side apply managed fields +func (f *lastAppliedUpdater) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { + liveObj, managed, err := f.fieldManager.Apply(liveObj, newObj, managed, manager, force) + if err != nil { + return liveObj, managed, err + } + + // Sync the client-side apply annotation only from kubectl server-side apply. + // To opt-out of this behavior, users may specify a different field manager. + // + // If the client-side apply annotation doesn't exist, + // then continue because we have no annotation to update + if manager == "kubectl" && hasLastApplied(liveObj) { + lastAppliedValue, err := buildLastApplied(newObj) + if err != nil { + return nil, nil, fmt.Errorf("failed to build last-applied annotation: %v", err) + } + err = SetLastApplied(liveObj, lastAppliedValue) + if err != nil { + return nil, nil, fmt.Errorf("failed to set last-applied annotation: %v", err) + } + } + return liveObj, managed, err +} + +func hasLastApplied(obj runtime.Object) bool { + var accessor, err = meta.Accessor(obj) + if err != nil { + panic(fmt.Sprintf("couldn't get accessor: %v", err)) + } + var annotations = accessor.GetAnnotations() + if annotations == nil { + return false + } + lastApplied, ok := annotations[LastAppliedConfigAnnotation] + return ok && len(lastApplied) > 0 +} + +func buildLastApplied(obj runtime.Object) (string, error) { + obj = obj.DeepCopyObject() + + var accessor, err = meta.Accessor(obj) + if err != nil { + panic(fmt.Sprintf("couldn't get accessor: %v", err)) + } + + // Remove the annotation from the object before encoding the object + var annotations = accessor.GetAnnotations() + delete(annotations, LastAppliedConfigAnnotation) + accessor.SetAnnotations(annotations) + + lastApplied, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return "", fmt.Errorf("couldn't encode object into last applied annotation: %v", err) + } + return string(lastApplied), nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedupdater_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedupdater_test.go new file mode 100644 index 0000000000..ede066ea24 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/lastappliedupdater_test.go @@ -0,0 +1,268 @@ +/* +Copyright 2020 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 internal_test + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + internaltesting "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" + "sigs.k8s.io/yaml" +) + +func TestLastAppliedUpdater(t *testing.T) { + f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("apps/v1", "Deployment"), + "", + func(m internal.Manager) internal.Manager { + return internal.NewLastAppliedUpdater(m) + }) + + originalLastApplied := `nonempty` + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + appliedDeployment := []byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment + annotations: + "kubectl.kubernetes.io/last-applied-configuration": "` + originalLastApplied + `" + labels: + app: my-app +spec: + replicas: 20 + selector: + matchLabels: + app: my-app + template: + metadata: + labels: + app: my-app + spec: + containers: + - name: my-c + image: my-image +`) + if err := yaml.Unmarshal(appliedDeployment, &appliedObj.Object); err != nil { + t.Errorf("error decoding YAML: %v", err) + } + + if err := f.Apply(appliedObj, "NOT-KUBECTL", false); err != nil { + t.Errorf("error applying object: %v", err) + } + + lastApplied, err := getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + + if lastApplied != originalLastApplied { + t.Errorf("expected last applied annotation to be %q and NOT be updated, but got: %q", originalLastApplied, lastApplied) + } + + if err := f.Apply(appliedObj, "kubectl", false); err != nil { + t.Errorf("error applying object: %v", err) + } + + lastApplied, err = getLastApplied(f.Live()) + if err != nil { + t.Errorf("failed to get last applied: %v", err) + } + + if lastApplied == originalLastApplied || + !strings.Contains(lastApplied, "my-app") || + !strings.Contains(lastApplied, "my-image") { + t.Errorf("expected last applied annotation to be updated, but got: %q", lastApplied) + } +} + +func TestLargeLastApplied(t *testing.T) { + tests := []struct { + name string + oldObject *unstructured.Unstructured + newObject *unstructured.Unstructured + }{ + { + name: "old object + new object last-applied annotation is too big", + oldObject: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + err := json.Unmarshal([]byte(` +{ + "metadata": { + "name": "large-update-test-cm", + "namespace": "default", + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "nonempty" + } + }, + "apiVersion": "v1", + "kind": "ConfigMap", + "data": { + "k": "v" + } +}`), &u) + if err != nil { + panic(err) + } + return u + }(), + newObject: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + err := json.Unmarshal([]byte(` +{ + "metadata": { + "name": "large-update-test-cm", + "namespace": "default", + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "nonempty" + } + }, + "apiVersion": "v1", + "kind": "ConfigMap", + "data": { + "k": "v" + } +}`), &u) + if err != nil { + panic(err) + } + for i := 0; i < 9999; i++ { + unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) + unstructured.SetNestedField(u.Object, "A", "data", unique) + } + return u + }(), + }, + { + name: "old object + new object annotations + new object last-applied annotation is too big", + oldObject: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + err := json.Unmarshal([]byte(` +{ + "metadata": { + "name": "large-update-test-cm", + "namespace": "default", + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "nonempty" + } + }, + "apiVersion": "v1", + "kind": "ConfigMap", + "data": { + "k": "v" + } +}`), &u) + if err != nil { + panic(err) + } + for i := 0; i < 2000; i++ { + unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) + unstructured.SetNestedField(u.Object, "A", "data", unique) + } + return u + }(), + newObject: func() *unstructured.Unstructured { + u := &unstructured.Unstructured{} + err := json.Unmarshal([]byte(` +{ + "metadata": { + "name": "large-update-test-cm", + "namespace": "default", + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "nonempty" + } + }, + "apiVersion": "v1", + "kind": "ConfigMap", + "data": { + "k": "v" + } +}`), &u) + if err != nil { + panic(err) + } + for i := 0; i < 2000; i++ { + unique := fmt.Sprintf("this-key-is-very-long-so-as-to-create-a-very-large-serialized-fieldset-%v", i) + unstructured.SetNestedField(u.Object, "A", "data", unique) + unstructured.SetNestedField(u.Object, "A", "metadata", "annotations", unique) + } + return u + }(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap"), + "", + func(m internal.Manager) internal.Manager { + return internal.NewLastAppliedUpdater(m) + }) + + if err := f.Apply(test.oldObject, "kubectl", false); err != nil { + t.Errorf("Error applying object: %v", err) + } + + lastApplied, err := getLastApplied(f.Live()) + if err != nil { + t.Errorf("Failed to access last applied annotation: %v", err) + } + if len(lastApplied) == 0 || lastApplied == "nonempty" { + t.Errorf("Expected an updated last-applied annotation, but got: %q", lastApplied) + } + + if err := f.Apply(test.newObject, "kubectl", false); err != nil { + t.Errorf("Error applying object: %v", err) + } + + accessor := meta.NewAccessor() + annotations, err := accessor.Annotations(f.Live()) + if err != nil { + t.Errorf("Failed to access annotations: %v", err) + } + if annotations == nil { + t.Errorf("No annotations on obj: %v", f.Live()) + } + lastApplied, ok := annotations[internal.LastAppliedConfigAnnotation] + if ok || len(lastApplied) > 0 { + t.Errorf("Expected no last applied annotation, but got last applied with length: %d", len(lastApplied)) + } + }) + } +} + +func getLastApplied(obj runtime.Object) (string, error) { + accessor := meta.NewAccessor() + annotations, err := accessor.Annotations(obj) + if err != nil { + return "", fmt.Errorf("failed to access annotations: %v", err) + } + if annotations == nil { + return "", fmt.Errorf("no annotations on obj: %v", obj) + } + + lastApplied, ok := annotations[internal.LastAppliedConfigAnnotation] + if !ok { + return "", fmt.Errorf("expected last applied annotation, but got none for object: %v", obj) + } + return lastApplied, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfields.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfields.go new file mode 100644 index 0000000000..bba2014e20 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfields.go @@ -0,0 +1,248 @@ +/* +Copyright 2018 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 internal + +import ( + "encoding/json" + "fmt" + "sort" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +// ManagedInterface groups a fieldpath.ManagedFields together with the timestamps associated with each operation. +type ManagedInterface interface { + // Fields gets the fieldpath.ManagedFields. + Fields() fieldpath.ManagedFields + + // Times gets the timestamps associated with each operation. + Times() map[string]*metav1.Time +} + +type managedStruct struct { + fields fieldpath.ManagedFields + times map[string]*metav1.Time +} + +var _ ManagedInterface = &managedStruct{} + +// Fields implements ManagedInterface. +func (m *managedStruct) Fields() fieldpath.ManagedFields { + return m.fields +} + +// Times implements ManagedInterface. +func (m *managedStruct) Times() map[string]*metav1.Time { + return m.times +} + +// NewEmptyManaged creates an empty ManagedInterface. +func NewEmptyManaged() ManagedInterface { + return NewManaged(fieldpath.ManagedFields{}, map[string]*metav1.Time{}) +} + +// NewManaged creates a ManagedInterface from a fieldpath.ManagedFields and the timestamps associated with each operation. +func NewManaged(f fieldpath.ManagedFields, t map[string]*metav1.Time) ManagedInterface { + return &managedStruct{ + fields: f, + times: t, + } +} + +// RemoveObjectManagedFields removes the ManagedFields from the object +// before we merge so that it doesn't appear in the ManagedFields +// recursively. +func RemoveObjectManagedFields(obj runtime.Object) { + accessor, err := meta.Accessor(obj) + if err != nil { + panic(fmt.Sprintf("couldn't get accessor: %v", err)) + } + accessor.SetManagedFields(nil) +} + +// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields +func EncodeObjectManagedFields(obj runtime.Object, managed ManagedInterface) error { + accessor, err := meta.Accessor(obj) + if err != nil { + panic(fmt.Sprintf("couldn't get accessor: %v", err)) + } + + encodedManagedFields, err := encodeManagedFields(managed) + if err != nil { + return fmt.Errorf("failed to convert back managed fields to API: %v", err) + } + accessor.SetManagedFields(encodedManagedFields) + + return nil +} + +// DecodeManagedFields converts ManagedFields from the wire format (api format) +// to the format used by sigs.k8s.io/structured-merge-diff +func DecodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (ManagedInterface, error) { + managed := managedStruct{} + managed.fields = make(fieldpath.ManagedFields, len(encodedManagedFields)) + managed.times = make(map[string]*metav1.Time, len(encodedManagedFields)) + + for i, encodedVersionedSet := range encodedManagedFields { + switch encodedVersionedSet.Operation { + case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate: + default: + return nil, fmt.Errorf("operation must be `Apply` or `Update`") + } + if len(encodedVersionedSet.APIVersion) < 1 { + return nil, fmt.Errorf("apiVersion must not be empty") + } + switch encodedVersionedSet.FieldsType { + case "FieldsV1": + // Valid case. + case "": + return nil, fmt.Errorf("missing fieldsType in managed fields entry %d", i) + default: + return nil, fmt.Errorf("invalid fieldsType %q in managed fields entry %d", encodedVersionedSet.FieldsType, i) + } + manager, err := BuildManagerIdentifier(&encodedVersionedSet) + if err != nil { + return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err) + } + managed.fields[manager], err = decodeVersionedSet(&encodedVersionedSet) + if err != nil { + return nil, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err) + } + managed.times[manager] = encodedVersionedSet.Time + } + return &managed, nil +} + +// BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry +func BuildManagerIdentifier(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) { + encodedManagerCopy := *encodedManager + + // Never include fields type in the manager identifier + encodedManagerCopy.FieldsType = "" + + // Never include the fields in the manager identifier + encodedManagerCopy.FieldsV1 = nil + + // Never include the time in the manager identifier + encodedManagerCopy.Time = nil + + // For appliers, don't include the APIVersion in the manager identifier, + // so it will always have the same manager identifier each time it applied. + if encodedManager.Operation == metav1.ManagedFieldsOperationApply { + encodedManagerCopy.APIVersion = "" + } + + // Use the remaining fields to build the manager identifier + b, err := json.Marshal(&encodedManagerCopy) + if err != nil { + return "", fmt.Errorf("error marshalling manager identifier: %v", err) + } + + return string(b), nil +} + +func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (versionedSet fieldpath.VersionedSet, err error) { + fields := EmptyFields + if encodedVersionedSet.FieldsV1 != nil { + fields = *encodedVersionedSet.FieldsV1 + } + set, err := FieldsToSet(fields) + if err != nil { + return nil, fmt.Errorf("error decoding set: %v", err) + } + return fieldpath.NewVersionedSet(&set, fieldpath.APIVersion(encodedVersionedSet.APIVersion), encodedVersionedSet.Operation == metav1.ManagedFieldsOperationApply), nil +} + +// encodeManagedFields converts ManagedFields from the format used by +// sigs.k8s.io/structured-merge-diff to the wire format (api format) +func encodeManagedFields(managed ManagedInterface) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) { + if len(managed.Fields()) == 0 { + return nil, nil + } + encodedManagedFields = []metav1.ManagedFieldsEntry{} + for manager := range managed.Fields() { + versionedSet := managed.Fields()[manager] + v, err := encodeManagerVersionedSet(manager, versionedSet) + if err != nil { + return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err) + } + if t, ok := managed.Times()[manager]; ok { + v.Time = t + } + encodedManagedFields = append(encodedManagedFields, *v) + } + return sortEncodedManagedFields(encodedManagedFields) +} + +func sortEncodedManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (sortedManagedFields []metav1.ManagedFieldsEntry, err error) { + sort.Slice(encodedManagedFields, func(i, j int) bool { + p, q := encodedManagedFields[i], encodedManagedFields[j] + + if p.Operation != q.Operation { + return p.Operation < q.Operation + } + + pSeconds, qSeconds := int64(0), int64(0) + if p.Time != nil { + pSeconds = p.Time.Unix() + } + if q.Time != nil { + qSeconds = q.Time.Unix() + } + if pSeconds != qSeconds { + return pSeconds < qSeconds + } + + if p.Manager != q.Manager { + return p.Manager < q.Manager + } + + if p.APIVersion != q.APIVersion { + return p.APIVersion < q.APIVersion + } + return p.Subresource < q.Subresource + }) + + return encodedManagedFields, nil +} + +func encodeManagerVersionedSet(manager string, versionedSet fieldpath.VersionedSet) (encodedVersionedSet *metav1.ManagedFieldsEntry, err error) { + encodedVersionedSet = &metav1.ManagedFieldsEntry{} + + // Get as many fields as we can from the manager identifier + err = json.Unmarshal([]byte(manager), encodedVersionedSet) + if err != nil { + return nil, fmt.Errorf("error unmarshalling manager identifier %v: %v", manager, err) + } + + // Get the APIVersion, Operation, and Fields from the VersionedSet + encodedVersionedSet.APIVersion = string(versionedSet.APIVersion()) + if versionedSet.Applied() { + encodedVersionedSet.Operation = metav1.ManagedFieldsOperationApply + } + encodedVersionedSet.FieldsType = "FieldsV1" + fields, err := SetToFields(*versionedSet.Set()) + if err != nil { + return nil, fmt.Errorf("error encoding set: %v", err) + } + encodedVersionedSet.FieldsV1 = &fields + + return encodedVersionedSet, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfields_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfields_test.go new file mode 100644 index 0000000000..2d8cb12405 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfields_test.go @@ -0,0 +1,520 @@ +/* +Copyright 2018 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 internal + +import ( + "fmt" + "reflect" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sigs.k8s.io/yaml" +) + +// TestHasFieldsType makes sure that we fail if we don't have a +// FieldsType set properly. +func TestHasFieldsType(t *testing.T) { + var unmarshaled []metav1.ManagedFieldsEntry + if err := yaml.Unmarshal([]byte(`- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:field: {} + manager: foo + operation: Apply +`), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + if _, err := DecodeManagedFields(unmarshaled); err != nil { + t.Fatalf("did not expect decoding error but got: %v", err) + } + + // Invalid fieldsType V2. + if err := yaml.Unmarshal([]byte(`- apiVersion: v1 + fieldsType: FieldsV2 + fieldsV1: + f:field: {} + manager: foo + operation: Apply +`), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + if _, err := DecodeManagedFields(unmarshaled); err == nil { + t.Fatal("Expect decoding error but got none") + } + + // Missing fieldsType. + if err := yaml.Unmarshal([]byte(`- apiVersion: v1 + fieldsV1: + f:field: {} + manager: foo + operation: Apply +`), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + if _, err := DecodeManagedFields(unmarshaled); err == nil { + t.Fatal("Expect decoding error but got none") + } +} + +// TestHasAPIVersion makes sure that we fail if we don't have an +// APIVersion set. +func TestHasAPIVersion(t *testing.T) { + var unmarshaled []metav1.ManagedFieldsEntry + if err := yaml.Unmarshal([]byte(`- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:field: {} + manager: foo + operation: Apply +`), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + if _, err := DecodeManagedFields(unmarshaled); err != nil { + t.Fatalf("did not expect decoding error but got: %v", err) + } + + // Missing apiVersion. + unmarshaled = nil + if err := yaml.Unmarshal([]byte(`- fieldsType: FieldsV1 + fieldsV1: + f:field: {} + manager: foo + operation: Apply +`), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + if _, err := DecodeManagedFields(unmarshaled); err == nil { + t.Fatal("Expect decoding error but got none") + } +} + +// TestHasOperation makes sure that we fail if we don't have an +// Operation set properly. +func TestHasOperation(t *testing.T) { + var unmarshaled []metav1.ManagedFieldsEntry + if err := yaml.Unmarshal([]byte(`- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:field: {} + manager: foo + operation: Apply +`), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + if _, err := DecodeManagedFields(unmarshaled); err != nil { + t.Fatalf("did not expect decoding error but got: %v", err) + } + + // Invalid operation. + if err := yaml.Unmarshal([]byte(`- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:field: {} + manager: foo + operation: Invalid +`), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + if _, err := DecodeManagedFields(unmarshaled); err == nil { + t.Fatal("Expect decoding error but got none") + } + + // Missing operation. + unmarshaled = nil + if err := yaml.Unmarshal([]byte(`- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:field: {} + manager: foo +`), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + if _, err := DecodeManagedFields(unmarshaled); err == nil { + t.Fatal("Expect decoding error but got none") + } +} + +// TestRoundTripManagedFields will roundtrip ManagedFields from the wire format +// (api format) to the format used by sigs.k8s.io/structured-merge-diff and back +func TestRoundTripManagedFields(t *testing.T) { + tests := []string{ + `null +`, + `- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + v:3: + f:alsoPi: {} + v:3.1415: + f:pi: {} + v:false: + f:notTrue: {} + manager: foo + operation: Update + time: "2001-02-03T04:05:06Z" +- apiVersion: v1beta1 + fieldsType: FieldsV1 + fieldsV1: + i:5: + f:i: {} + manager: foo + operation: Update + time: "2011-12-13T14:15:16Z" +`, + `- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:spec: + f:containers: + k:{"name":"c"}: + f:image: {} + f:name: {} + manager: foo + operation: Apply +`, + `- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:apiVersion: {} + f:kind: {} + f:metadata: + f:labels: + f:app: {} + f:name: {} + f:spec: + f:replicas: {} + f:selector: + f:matchLabels: + f:app: {} + f:template: + f:medatada: + f:labels: + f:app: {} + f:spec: + f:containers: + k:{"name":"nginx"}: + .: {} + f:image: {} + f:name: {} + f:ports: + i:0: + f:containerPort: {} + manager: foo + operation: Update +`, + `- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:allowVolumeExpansion: {} + f:apiVersion: {} + f:kind: {} + f:metadata: + f:name: {} + f:parameters: + f:resturl: {} + f:restuser: {} + f:secretName: {} + f:secretNamespace: {} + f:provisioner: {} + manager: foo + operation: Apply +`, + `- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:apiVersion: {} + f:kind: {} + f:metadata: + f:name: {} + f:spec: + f:group: {} + f:names: + f:kind: {} + f:plural: {} + f:shortNames: + i:0: {} + f:singular: {} + f:scope: {} + f:versions: + k:{"name":"v1"}: + f:name: {} + f:served: {} + f:storage: {} + manager: foo + operation: Update +`, + `- apiVersion: v1 + fieldsType: FieldsV1 + fieldsV1: + f:spec: + f:replicas: {} + manager: foo + operation: Update + subresource: scale +`, + } + + for _, test := range tests { + t.Run(test, func(t *testing.T) { + var unmarshaled []metav1.ManagedFieldsEntry + if err := yaml.Unmarshal([]byte(test), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + decoded, err := DecodeManagedFields(unmarshaled) + if err != nil { + t.Fatalf("did not expect decoding error but got: %v", err) + } + encoded, err := encodeManagedFields(decoded) + if err != nil { + t.Fatalf("did not expect encoding error but got: %v", err) + } + marshaled, err := yaml.Marshal(&encoded) + if err != nil { + t.Fatalf("did not expect yaml marshalling error but got: %v", err) + } + if !reflect.DeepEqual(string(marshaled), test) { + t.Fatalf("expected:\n%v\nbut got:\n%v", test, string(marshaled)) + } + }) + } +} + +func TestBuildManagerIdentifier(t *testing.T) { + tests := []struct { + managedFieldsEntry string + expected string + }{ + { + managedFieldsEntry: ` +apiVersion: v1 +fieldsV1: + f:apiVersion: {} +manager: foo +operation: Update +time: "2001-02-03T04:05:06Z" +`, + expected: "{\"manager\":\"foo\",\"operation\":\"Update\",\"apiVersion\":\"v1\"}", + }, + { + managedFieldsEntry: ` +apiVersion: v1 +fieldsV1: + f:apiVersion: {} +manager: foo +operation: Apply +time: "2001-02-03T04:05:06Z" +`, + expected: "{\"manager\":\"foo\",\"operation\":\"Apply\"}", + }, + { + managedFieldsEntry: ` +apiVersion: v1 +fieldsV1: + f:apiVersion: {} +manager: foo +operation: Apply +subresource: scale +time: "2001-02-03T04:05:06Z" +`, + expected: "{\"manager\":\"foo\",\"operation\":\"Apply\",\"subresource\":\"scale\"}", + }, + } + + for _, test := range tests { + t.Run(test.managedFieldsEntry, func(t *testing.T) { + var unmarshaled metav1.ManagedFieldsEntry + if err := yaml.Unmarshal([]byte(test.managedFieldsEntry), &unmarshaled); err != nil { + t.Fatalf("did not expect yaml unmarshalling error but got: %v", err) + } + decoded, err := BuildManagerIdentifier(&unmarshaled) + if err != nil { + t.Fatalf("did not expect decoding error but got: %v", err) + } + if !reflect.DeepEqual(decoded, test.expected) { + t.Fatalf("expected:\n%v\nbut got:\n%v", test.expected, decoded) + } + }) + } +} + +func TestSortEncodedManagedFields(t *testing.T) { + tests := []struct { + name string + managedFields []metav1.ManagedFieldsEntry + expected []metav1.ManagedFieldsEntry + }{ + { + name: "empty", + managedFields: []metav1.ManagedFieldsEntry{}, + expected: []metav1.ManagedFieldsEntry{}, + }, + { + name: "nil", + managedFields: nil, + expected: nil, + }, + { + name: "remains untouched", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + }, + }, + { + name: "manager without time first", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + }, + }, + { + name: "manager without time first name last", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + }, + }, + { + name: "apply first", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + }, + }, + { + name: "newest last", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, + }, + }, + { + name: "manager last", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + }, + }, + { + name: "manager sorted", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "g", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "f", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, + {Manager: "i", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, + {Manager: "h", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "e", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2003-01-01T01:00:00Z")}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "g", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "h", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "i", Operation: metav1.ManagedFieldsOperationApply, Time: nil}, + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")}, + {Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, + {Manager: "f", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")}, + {Manager: "e", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2003-01-01T01:00:00Z")}, + }, + }, + { + name: "sort drops nanoseconds", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 1, time.UTC)}}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 2, time.UTC)}}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 3, time.UTC)}}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 2, time.UTC)}}, + {Manager: "b", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 3, time.UTC)}}, + {Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Date(2000, time.January, 0, 0, 0, 0, 1, time.UTC)}}, + }, + }, + { + name: "entries with subresource field", + managedFields: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Subresource: "status"}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Subresource: "scale"}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply}, + }, + expected: []metav1.ManagedFieldsEntry{ + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Subresource: "scale"}, + {Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Subresource: "status"}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sorted, err := sortEncodedManagedFields(test.managedFields) + if err != nil { + t.Fatalf("did not expect error when sorting but got: %v", err) + } + if !reflect.DeepEqual(sorted, test.expected) { + t.Fatalf("expected:\n%v\nbut got:\n%v", test.expected, sorted) + } + }) + } +} + +func parseTimeOrPanic(s string) *metav1.Time { + t, err := time.Parse(time.RFC3339, s) + if err != nil { + panic(fmt.Sprintf("failed to parse time %s, got: %v", s, err)) + } + return &metav1.Time{Time: t.UTC()} +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfieldsupdater.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfieldsupdater.go new file mode 100644 index 0000000000..66215d87a1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfieldsupdater.go @@ -0,0 +1,82 @@ +/* +Copyright 2020 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 internal + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +type managedFieldsUpdater struct { + fieldManager Manager +} + +var _ Manager = &managedFieldsUpdater{} + +// NewManagedFieldsUpdater is responsible for updating the managedfields +// in the object, updating the time of the operation as necessary. For +// updates, it uses a hard-coded manager to detect if things have +// changed, and swaps back the correct manager after the operation is +// done. +func NewManagedFieldsUpdater(fieldManager Manager) Manager { + return &managedFieldsUpdater{ + fieldManager: fieldManager, + } +} + +// Update implements Manager. +func (f *managedFieldsUpdater) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + self := "current-operation" + object, managed, err := f.fieldManager.Update(liveObj, newObj, managed, self) + if err != nil { + return object, managed, err + } + + // If the current operation took any fields from anything, it means the object changed, + // so update the timestamp of the managedFieldsEntry and merge with any previous updates from the same manager + if vs, ok := managed.Fields()[self]; ok { + delete(managed.Fields(), self) + + if previous, ok := managed.Fields()[manager]; ok { + managed.Fields()[manager] = fieldpath.NewVersionedSet(vs.Set().Union(previous.Set()), vs.APIVersion(), vs.Applied()) + } else { + managed.Fields()[manager] = vs + } + + managed.Times()[manager] = &metav1.Time{Time: time.Now().UTC()} + } + + return object, managed, nil +} + +// Apply implements Manager. +func (f *managedFieldsUpdater) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { + object, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) + if err != nil { + return object, managed, err + } + if object != nil { + managed.Times()[fieldManager] = &metav1.Time{Time: time.Now().UTC()} + } else { + object = liveObj.DeepCopyObject() + RemoveObjectManagedFields(object) + } + return object, managed, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfieldsupdater_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfieldsupdater_test.go new file mode 100644 index 0000000000..631eed7f7b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/managedfieldsupdater_test.go @@ -0,0 +1,524 @@ +/* +Copyright 2020 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 internal_test + +import ( + "fmt" + "reflect" + "testing" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + "sigs.k8s.io/yaml" +) + +func TestManagedFieldsUpdateDoesModifyTime(t *testing.T) { + var err error + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) + + err = updateObject(f, "fieldmanager_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + previousManagedFields := f.ManagedFields() + + time.Sleep(time.Second) + + err = updateObject(f, "fieldmanager_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key": "new-value" + } + }`)) + if err != nil { + t.Fatal(err) + } + newManagedFields := f.ManagedFields() + + if previousManagedFields[0].Time.Equal(newManagedFields[0].Time) { + t.Errorf("ManagedFields time has not been updated:\n%v", newManagedFields) + } +} + +func TestManagedFieldsApplyDoesModifyTime(t *testing.T) { + var err error + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) + + err = applyObject(f, "fieldmanager_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + previousManagedFields := f.ManagedFields() + + time.Sleep(time.Second) + + err = applyObject(f, "fieldmanager_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key": "new-value" + } + }`)) + if err != nil { + t.Fatal(err) + } + newManagedFields := f.ManagedFields() + + if previousManagedFields[0].Time.Equal(newManagedFields[0].Time) { + t.Errorf("ManagedFields time has not been updated:\n%v", newManagedFields) + } +} + +func TestManagedFieldsUpdateWithoutChangesDoesNotModifyTime(t *testing.T) { + var err error + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) + + err = updateObject(f, "fieldmanager_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + previousManagedFields := f.ManagedFields() + + time.Sleep(time.Second) + + err = updateObject(f, "fieldmanager_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + newManagedFields := f.ManagedFields() + + if !previousManagedFields[0].Time.Equal(newManagedFields[0].Time) { + t.Errorf("ManagedFields time has changed:\nBefore:\n%v\nAfter:\n%v", previousManagedFields, newManagedFields) + } +} + +func TestManagedFieldsApplyWithoutChangesDoesNotModifyTime(t *testing.T) { + var err error + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) + + err = applyObject(f, "fieldmanager_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + previousManagedFields := f.ManagedFields() + + time.Sleep(time.Second) + + err = applyObject(f, "fieldmanager_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + newManagedFields := f.ManagedFields() + + if !previousManagedFields[0].Time.Equal(newManagedFields[0].Time) { + t.Errorf("ManagedFields time has changed:\nBefore:\n%v\nAfter:\n%v", previousManagedFields, newManagedFields) + } +} + +func TestNonManagedFieldsUpdateDoesNotModifyTime(t *testing.T) { + var err error + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) + + err = updateObject(f, "fieldmanager_a_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_a": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + err = updateObject(f, "fieldmanager_b_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_b": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + previousManagedFields := f.ManagedFields() + previousEntries := map[string]v1.ManagedFieldsEntry{} + for _, entry := range previousManagedFields { + previousEntries[entry.Manager] = entry + } + + time.Sleep(time.Second) + + err = updateObject(f, "fieldmanager_a_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_a": "value", + "key_b": "new-value" + } + }`)) + if err != nil { + t.Fatal(err) + } + newManagedFields := f.ManagedFields() + newEntries := map[string]v1.ManagedFieldsEntry{} + for _, entry := range newManagedFields { + newEntries[entry.Manager] = entry + } + + if _, ok := newEntries["fieldmanager_b_test"]; ok { + t.Errorf("FieldManager B ManagedFields has changed:\n%v", newEntries["fieldmanager_b_test"]) + } +} + +func TestNonManagedFieldsApplyDoesNotModifyTime(t *testing.T) { + var err error + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) + + err = applyObject(f, "fieldmanager_a_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_a": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + err = applyObject(f, "fieldmanager_b_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_b": "value" + } + }`)) + if err != nil { + t.Fatal(err) + } + previousManagedFields := f.ManagedFields() + previousEntries := map[string]v1.ManagedFieldsEntry{} + for _, entry := range previousManagedFields { + previousEntries[entry.Manager] = entry + } + + time.Sleep(time.Second) + + err = applyObject(f, "fieldmanager_a_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_a": "new-value" + } + }`)) + if err != nil { + t.Fatal(err) + } + newManagedFields := f.ManagedFields() + newEntries := map[string]v1.ManagedFieldsEntry{} + for _, entry := range newManagedFields { + newEntries[entry.Manager] = entry + } + + if !previousEntries["fieldmanager_b_test"].Time.Equal(newEntries["fieldmanager_b_test"].Time) { + t.Errorf("FieldManager B ManagedFields time changed:\nBefore:\n%v\nAfter:\n%v", + previousEntries["fieldmanager_b_test"], newEntries["fieldmanager_b_test"]) + } +} + +func TestTakingOverManagedFieldsDuringUpdateDoesNotModifyPreviousManagerTime(t *testing.T) { + var err error + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) + + err = updateObject(f, "fieldmanager_a_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_a": "value", + "key_b": value" + } + }`)) + if err != nil { + t.Fatal(err) + } + previousManagedFields := f.ManagedFields() + previousEntries := map[string]v1.ManagedFieldsEntry{} + for _, entry := range previousManagedFields { + previousEntries[entry.Manager] = entry + } + + time.Sleep(time.Second) + + err = updateObject(f, "fieldmanager_b_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_b": "new-value" + } + }`)) + if err != nil { + t.Fatal(err) + } + newManagedFields := f.ManagedFields() + newEntries := map[string]v1.ManagedFieldsEntry{} + for _, entry := range newManagedFields { + newEntries[entry.Manager] = entry + } + + if !previousEntries["fieldmanager_a_test"].Time.Equal(newEntries["fieldmanager_a_test"].Time) { + t.Errorf("FieldManager A ManagedFields time has been updated:\nBefore:\n%v\nAfter:\n%v", + previousEntries["fieldmanager_a_test"], newEntries["fieldmanager_a_test"]) + } +} + +func TestTakingOverManagedFieldsDuringApplyDoesNotModifyPreviousManagerTime(t *testing.T) { + var err error + f := managedfieldstest.NewTestFieldManager(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "ConfigMap")) + + err = applyObject(f, "fieldmanager_a_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_a": "value", + "key_b": value" + } + }`)) + if err != nil { + t.Fatal(err) + } + previousManagedFields := f.ManagedFields() + previousEntries := map[string]v1.ManagedFieldsEntry{} + for _, entry := range previousManagedFields { + previousEntries[entry.Manager] = entry + } + + time.Sleep(time.Second) + + err = applyObject(f, "fieldmanager_b_test", []byte(`{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "configmap" + }, + "data": { + "key_b": "new-value" + } + }`)) + if err != nil { + t.Fatal(err) + } + newManagedFields := f.ManagedFields() + newEntries := map[string]v1.ManagedFieldsEntry{} + for _, entry := range newManagedFields { + newEntries[entry.Manager] = entry + } + + if !previousEntries["fieldmanager_a_test"].Time.Equal(newEntries["fieldmanager_a_test"].Time) { + t.Errorf("FieldManager A ManagedFields time has been updated:\nBefore:\n%v\nAfter:\n%v", + previousEntries["fieldmanager_a_test"], newEntries["fieldmanager_a_test"]) + } +} + +type NoopManager struct{} + +func (NoopManager) Apply(liveObj, appliedObj runtime.Object, managed internal.Managed, fieldManager string, force bool) (runtime.Object, internal.Managed, error) { + return nil, managed, nil +} + +func (NoopManager) Update(liveObj, newObj runtime.Object, managed internal.Managed, manager string) (runtime.Object, internal.Managed, error) { + return nil, nil, nil +} + +func updateObject(f managedfieldstest.TestFieldManager, fieldManagerName string, object []byte) error { + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal(object, &obj.Object); err != nil { + return fmt.Errorf("error decoding YAML: %v", err) + } + if err := f.Update(obj, fieldManagerName); err != nil { + return fmt.Errorf("failed to update object: %v", err) + } + return nil +} + +func applyObject(f managedfieldstest.TestFieldManager, fieldManagerName string, object []byte) error { + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal(object, &obj.Object); err != nil { + return fmt.Errorf("error decoding YAML: %v", err) + } + if err := f.Apply(obj, fieldManagerName, true); err != nil { + return fmt.Errorf("failed to apply object: %v", err) + } + return nil +} + +// Ensures that if ManagedFieldsUpdater gets a nil value from its nested manager +// chain (meaning the operation was a no-op), then the ManagedFieldsUpdater +// itself will return a copy of the input live object, with its managed fields +// removed +func TestNilNewObjectReplacedWithDeepCopyExcludingManagedFields(t *testing.T) { + // Initialize our "live object" with some managed fields + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "pod", + "labels": {"app": "nginx"}, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:labels": { + "f:app": {} + } + } + }, + "manager": "fieldmanager_test", + "operation": "Apply", + "time": "2021-11-11T18:41:17Z" + } + ] + } + }`), &obj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + accessor, err := meta.Accessor(obj) + if err != nil { + t.Fatalf("couldn't get accessor: %v", err) + } + + // Decode the managed fields in the live object, since it isn't allowed in the patch. + managed, err := internal.DecodeManagedFields(accessor.GetManagedFields()) + if err != nil { + t.Fatalf("failed to decode managed fields: %v", err) + } + + updater := internal.NewManagedFieldsUpdater(NoopManager{}) + + newObject, _, err := updater.Apply(obj, obj.DeepCopyObject(), managed, "some_manager", false) + if err != nil { + t.Fatalf("failed to apply configuration %v", err) + } + + if newObject == obj { + t.Fatalf("returned newObject must not be the same instance as the passed in liveObj") + } + + // Rip off managed fields of live, and check that it is deeply + // equal to newObject + liveWithoutManaged := obj.DeepCopyObject() + internal.RemoveObjectManagedFields(liveWithoutManaged) + + if !reflect.DeepEqual(liveWithoutManaged, newObject) { + t.Fatalf("returned newObject must be deeply equal to the input live object, without managed fields") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/manager.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/manager.go new file mode 100644 index 0000000000..78830d0cf5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/manager.go @@ -0,0 +1,52 @@ +/* +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 internal + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +// Managed groups a fieldpath.ManagedFields together with the timestamps associated with each operation. +type Managed interface { + // Fields gets the fieldpath.ManagedFields. + Fields() fieldpath.ManagedFields + + // Times gets the timestamps associated with each operation. + Times() map[string]*metav1.Time +} + +// Manager updates the managed fields and merges applied configurations. +type Manager interface { + // Update is used when the object has already been merged (non-apply + // use-case), and simply updates the managed fields in the output + // object. + // * `liveObj` is not mutated by this function + // * `newObj` may be mutated by this function + // Returns the new object with managedFields removed, and the object's new + // proposed managedFields separately. + Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) + + // Apply is used when server-side apply is called, as it merges the + // object and updates the managed fields. + // * `liveObj` is not mutated by this function + // * `newObj` may be mutated by this function + // Returns the new object with managedFields removed, and the object's new + // proposed managedFields separately. + Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/pathelement.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/pathelement.go new file mode 100644 index 0000000000..1b5dddfd70 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/pathelement.go @@ -0,0 +1,140 @@ +/* +Copyright 2018 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 internal + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/value" +) + +const ( + // Field indicates that the content of this path element is a field's name + Field = "f" + + // Value indicates that the content of this path element is a field's value + Value = "v" + + // Index indicates that the content of this path element is an index in an array + Index = "i" + + // Key indicates that the content of this path element is a key value map + Key = "k" + + // Separator separates the type of a path element from the contents + Separator = ":" +) + +// NewPathElement parses a serialized path element +func NewPathElement(s string) (fieldpath.PathElement, error) { + split := strings.SplitN(s, Separator, 2) + if len(split) < 2 { + return fieldpath.PathElement{}, fmt.Errorf("missing colon: %v", s) + } + switch split[0] { + case Field: + return fieldpath.PathElement{ + FieldName: &split[1], + }, nil + case Value: + val, err := value.FromJSON([]byte(split[1])) + if err != nil { + return fieldpath.PathElement{}, err + } + return fieldpath.PathElement{ + Value: &val, + }, nil + case Index: + i, err := strconv.Atoi(split[1]) + if err != nil { + return fieldpath.PathElement{}, err + } + return fieldpath.PathElement{ + Index: &i, + }, nil + case Key: + kv := map[string]json.RawMessage{} + err := json.Unmarshal([]byte(split[1]), &kv) + if err != nil { + return fieldpath.PathElement{}, err + } + fields := value.FieldList{} + for k, v := range kv { + b, err := json.Marshal(v) + if err != nil { + return fieldpath.PathElement{}, err + } + val, err := value.FromJSON(b) + if err != nil { + return fieldpath.PathElement{}, err + } + + fields = append(fields, value.Field{ + Name: k, + Value: val, + }) + } + return fieldpath.PathElement{ + Key: &fields, + }, nil + default: + // Ignore unknown key types + return fieldpath.PathElement{}, nil + } +} + +// PathElementString serializes a path element +func PathElementString(pe fieldpath.PathElement) (string, error) { + switch { + case pe.FieldName != nil: + return Field + Separator + *pe.FieldName, nil + case pe.Key != nil: + kv := map[string]json.RawMessage{} + for _, k := range *pe.Key { + b, err := value.ToJSON(k.Value) + if err != nil { + return "", err + } + m := json.RawMessage{} + err = json.Unmarshal(b, &m) + if err != nil { + return "", err + } + kv[k.Name] = m + } + b, err := json.Marshal(kv) + if err != nil { + return "", err + } + return Key + ":" + string(b), nil + case pe.Value != nil: + b, err := value.ToJSON(*pe.Value) + if err != nil { + return "", err + } + return Value + ":" + string(b), nil + case pe.Index != nil: + return Index + ":" + strconv.Itoa(*pe.Index), nil + default: + return "", errors.New("Invalid type of path element") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/pathelement_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/pathelement_test.go new file mode 100644 index 0000000000..bd119e03c2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/pathelement_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2018 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 internal + +import "testing" + +func TestPathElementRoundTrip(t *testing.T) { + tests := []string{ + `i:0`, + `i:1234`, + `f:`, + `f:spec`, + `f:more-complicated-string`, + `k:{"name":"my-container"}`, + `k:{"port":"8080","protocol":"TCP"}`, + `k:{"optionalField":null}`, + `k:{"jsonField":{"A":1,"B":null,"C":"D","E":{"F":"G"}}}`, + `k:{"listField":["1","2","3"]}`, + `v:null`, + `v:"some-string"`, + `v:1234`, + `v:{"some":"json"}`, + } + + for _, test := range tests { + t.Run(test, func(t *testing.T) { + pe, err := NewPathElement(test) + if err != nil { + t.Fatalf("Failed to create path element: %v", err) + } + output, err := PathElementString(pe) + if err != nil { + t.Fatalf("Failed to create string from path element: %v", err) + } + if test != output { + t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output) + } + }) + } +} + +func TestPathElementIgnoreUnknown(t *testing.T) { + _, err := NewPathElement("r:Hello") + if err != nil { + t.Fatalf("Unknown qualifiers should be ignored") + } +} + +func TestNewPathElementError(t *testing.T) { + tests := []string{ + ``, + `no-colon`, + `i:index is not a number`, + `i:1.23`, + `i:`, + `v:invalid json`, + `v:`, + `k:invalid json`, + `k:{"name":invalid}`, + } + + for _, test := range tests { + t.Run(test, func(t *testing.T) { + _, err := NewPathElement(test) + if err == nil { + t.Fatalf("Expected error, no error found") + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/runtimetypeconverter.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/runtimetypeconverter.go new file mode 100644 index 0000000000..366ff73363 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/runtimetypeconverter.go @@ -0,0 +1,62 @@ +/* +Copyright 2025 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 internal + +import ( + "fmt" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/structured-merge-diff/v6/typed" +) + +type schemeTypeConverter struct { + scheme *runtime.Scheme + parser *typed.Parser +} + +var _ TypeConverter = &schemeTypeConverter{} + +// NewSchemeTypeConverter creates a TypeConverter that uses the provided scheme to +// convert between runtime.Objects and TypedValues. +func NewSchemeTypeConverter(scheme *runtime.Scheme, parser *typed.Parser) TypeConverter { + return &schemeTypeConverter{scheme: scheme, parser: parser} +} + +func (tc schemeTypeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) { + gvk := obj.GetObjectKind().GroupVersionKind() + name, err := tc.scheme.ToOpenAPIDefinitionName(gvk) + if err != nil { + return nil, err + } + t := tc.parser.Type(name) + switch o := obj.(type) { + case *unstructured.Unstructured: + return t.FromUnstructured(o.UnstructuredContent(), opts...) + default: + return t.FromStructured(obj, opts...) + } +} + +func (tc schemeTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { + vu := value.AsValue().Unstructured() + switch o := vu.(type) { + case map[string]interface{}: + return &unstructured.Unstructured{Object: o}, nil + default: + return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/skipnonapplied.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/skipnonapplied.go new file mode 100644 index 0000000000..15357a34d8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/skipnonapplied.go @@ -0,0 +1,92 @@ +/* +Copyright 2019 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 internal + +import ( + "fmt" + "math/rand" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" +) + +type skipNonAppliedManager struct { + fieldManager Manager + objectCreater runtime.ObjectCreater + beforeApplyManagerName string + probability float32 +} + +var _ Manager = &skipNonAppliedManager{} + +// NewSkipNonAppliedManager creates a new wrapped FieldManager that only starts tracking managers after the first apply. +func NewSkipNonAppliedManager(fieldManager Manager, objectCreater runtime.ObjectCreater) Manager { + return NewProbabilisticSkipNonAppliedManager(fieldManager, objectCreater, 0.0) +} + +// NewProbabilisticSkipNonAppliedManager creates a new wrapped FieldManager that starts tracking managers after the first apply, +// or starts tracking on create with p probability. +func NewProbabilisticSkipNonAppliedManager(fieldManager Manager, objectCreater runtime.ObjectCreater, p float32) Manager { + return &skipNonAppliedManager{ + fieldManager: fieldManager, + objectCreater: objectCreater, + beforeApplyManagerName: "before-first-apply", + probability: p, + } +} + +// Update implements Manager. +func (f *skipNonAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + accessor, err := meta.Accessor(liveObj) + if err != nil { + return newObj, managed, nil + } + + // If managed fields is empty, we need to determine whether to skip tracking managed fields. + if len(managed.Fields()) == 0 { + // Check if the operation is a create, by checking whether lastObj's UID is empty. + // If the operation is create, P(tracking managed fields) = f.probability + // If the operation is update, skip tracking managed fields, since we already know managed fields is empty. + if len(accessor.GetUID()) == 0 { + if f.probability <= rand.Float32() { + return newObj, managed, nil + } + } else { + return newObj, managed, nil + } + } + return f.fieldManager.Update(liveObj, newObj, managed, manager) +} + +// Apply implements Manager. +func (f *skipNonAppliedManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { + if len(managed.Fields()) == 0 { + gvk := appliedObj.GetObjectKind().GroupVersionKind() + emptyObj, err := f.objectCreater.New(gvk) + if err != nil { + return nil, nil, fmt.Errorf("failed to create empty object of type %v: %v", gvk, err) + } + if unstructured, isUnstructured := emptyObj.(runtime.Unstructured); isUnstructured { + unstructured.GetObjectKind().SetGroupVersionKind(gvk) + } + liveObj, managed, err = f.fieldManager.Update(emptyObj, liveObj, managed, f.beforeApplyManagerName) + if err != nil { + return nil, nil, fmt.Errorf("failed to create manager for existing fields: %v", err) + } + } + return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/skipnonapplied_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/skipnonapplied_test.go new file mode 100644 index 0000000000..2863a76326 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/skipnonapplied_test.go @@ -0,0 +1,133 @@ +/* +Copyright 2019 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 internal_test + +import ( + "encoding/json" + "strings" + "testing" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + internaltesting "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" + "sigs.k8s.io/yaml" +) + +func TestNoUpdateBeforeFirstApply(t *testing.T) { + f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m internal.Manager) internal.Manager { + return internal.NewSkipNonAppliedManager(m, &internaltesting.FakeObjectCreater{}) + }) + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "pod", + "labels": {"app": "nginx"} + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "nginx:latest" + }] + } + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + if err := f.Apply(appliedObj, "fieldmanager_test_apply", false); err != nil { + t.Fatalf("failed to update object: %v", err) + } + + if e, a := 1, len(f.ManagedFields()); e != a { + t.Fatalf("exected %v entries in managedFields, but got %v: %#v", e, a, f.ManagedFields()) + } + + if e, a := "fieldmanager_test_apply", f.ManagedFields()[0].Manager; e != a { + t.Fatalf("exected manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } +} + +func TestUpdateBeforeFirstApply(t *testing.T) { + f := internaltesting.NewTestFieldManagerImpl(fakeTypeConverter, schema.FromAPIVersionAndKind("v1", "Pod"), "", func(m internal.Manager) internal.Manager { + return internal.NewSkipNonAppliedManager(m, &internaltesting.FakeObjectCreater{}) + }) + + updatedObj := &unstructured.Unstructured{} + if err := json.Unmarshal([]byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": {"labels": {"app": "my-nginx"}}}`), updatedObj); err != nil { + t.Fatalf("Failed to unmarshal object: %v", err) + } + + if err := f.Update(updatedObj, "fieldmanager_test_update"); err != nil { + t.Fatalf("failed to update object: %v", err) + } + + if m := f.ManagedFields(); len(m) != 0 { + t.Fatalf("managedFields were tracked on update only: %v", m) + } + + appliedObj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(`{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "pod", + "labels": {"app": "nginx"} + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "nginx:latest" + }] + } + }`), &appliedObj.Object); err != nil { + t.Fatalf("error decoding YAML: %v", err) + } + + err := f.Apply(appliedObj, "fieldmanager_test_apply", false) + apiStatus, _ := err.(apierrors.APIStatus) + if err == nil || !apierrors.IsConflict(err) || len(apiStatus.Status().Details.Causes) != 1 { + t.Fatalf("Expecting to get one conflict but got %v", err) + } + + if e, a := ".metadata.labels.app", apiStatus.Status().Details.Causes[0].Field; e != a { + t.Fatalf("Expecting to conflict on field %q but conflicted on field %q: %v", e, a, err) + } + + if e, a := "before-first-apply", apiStatus.Status().Details.Causes[0].Message; !strings.Contains(a, e) { + t.Fatalf("Expecting conflict message to contain %q but got %q: %v", e, a, err) + } + + if err := f.Apply(appliedObj, "fieldmanager_test_apply", true); err != nil { + t.Fatalf("failed to update object: %v", err) + } + + if e, a := 2, len(f.ManagedFields()); e != a { + t.Fatalf("exected %v entries in managedFields, but got %v: %#v", e, a, f.ManagedFields()) + } + + if e, a := "fieldmanager_test_apply", f.ManagedFields()[0].Manager; e != a { + t.Fatalf("exected first manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } + + if e, a := "before-first-apply", f.ManagedFields()[1].Manager; e != a { + t.Fatalf("exected second manager name to be %v, but got %v: %#v", e, a, f.ManagedFields()) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/stripmeta.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/stripmeta.go new file mode 100644 index 0000000000..8a2b7e4e63 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/stripmeta.go @@ -0,0 +1,90 @@ +/* +Copyright 2019 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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +type stripMetaManager struct { + fieldManager Manager + + // stripSet is the list of fields that should never be part of a mangedFields. + stripSet *fieldpath.Set +} + +var _ Manager = &stripMetaManager{} + +// NewStripMetaManager creates a new Manager that strips metadata and typemeta fields from the manager's fieldset. +func NewStripMetaManager(fieldManager Manager) Manager { + return &stripMetaManager{ + fieldManager: fieldManager, + stripSet: fieldpath.NewSet( + fieldpath.MakePathOrDie("apiVersion"), + fieldpath.MakePathOrDie("kind"), + fieldpath.MakePathOrDie("metadata"), + fieldpath.MakePathOrDie("metadata", "name"), + fieldpath.MakePathOrDie("metadata", "namespace"), + fieldpath.MakePathOrDie("metadata", "creationTimestamp"), + fieldpath.MakePathOrDie("metadata", "selfLink"), + fieldpath.MakePathOrDie("metadata", "uid"), + fieldpath.MakePathOrDie("metadata", "clusterName"), + fieldpath.MakePathOrDie("metadata", "generation"), + fieldpath.MakePathOrDie("metadata", "managedFields"), + fieldpath.MakePathOrDie("metadata", "resourceVersion"), + ), + } +} + +// Update implements Manager. +func (f *stripMetaManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + newObj, managed, err := f.fieldManager.Update(liveObj, newObj, managed, manager) + if err != nil { + return nil, nil, err + } + f.stripFields(managed.Fields(), manager) + return newObj, managed, nil +} + +// Apply implements Manager. +func (f *stripMetaManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { + newObj, managed, err := f.fieldManager.Apply(liveObj, appliedObj, managed, manager, force) + if err != nil { + return nil, nil, err + } + f.stripFields(managed.Fields(), manager) + return newObj, managed, nil +} + +// stripFields removes a predefined set of paths found in typed from managed +func (f *stripMetaManager) stripFields(managed fieldpath.ManagedFields, manager string) { + vs, ok := managed[manager] + if ok { + if vs == nil { + panic(fmt.Sprintf("Found unexpected nil manager which should never happen: %s", manager)) + } + newSet := vs.Set().Difference(f.stripSet) + if newSet.Empty() { + delete(managed, manager) + } else { + managed[manager] = fieldpath.NewVersionedSet(newSet, vs.APIVersion(), vs.Applied()) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/structuredmerge.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/structuredmerge.go new file mode 100644 index 0000000000..8e9a270108 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/structuredmerge.go @@ -0,0 +1,190 @@ +/* +Copyright 2019 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 internal + +import ( + "fmt" + + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/merge" + "sigs.k8s.io/structured-merge-diff/v6/typed" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type structuredMergeManager struct { + typeConverter TypeConverter + objectConverter runtime.ObjectConvertor + objectDefaulter runtime.ObjectDefaulter + groupVersion schema.GroupVersion + hubVersion schema.GroupVersion + updater merge.Updater +} + +var _ Manager = &structuredMergeManager{} + +// NewStructuredMergeManager creates a new Manager that merges apply requests +// and update managed fields for other types of requests. +func NewStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]fieldpath.Filter) (Manager, error) { + if typeConverter == nil { + return nil, fmt.Errorf("typeconverter must not be nil") + } + return &structuredMergeManager{ + typeConverter: typeConverter, + objectConverter: objectConverter, + objectDefaulter: objectDefaulter, + groupVersion: gv, + hubVersion: hub, + updater: merge.Updater{ + Converter: newVersionConverter(typeConverter, objectConverter, hub), // This is the converter provided to SMD from k8s + IgnoreFilter: resetFields, + }, + }, nil +} + +// NewCRDStructuredMergeManager creates a new Manager specifically for +// CRDs. This allows for the possibility of fields which are not defined +// in models, as well as having no models defined at all. +func NewCRDStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]fieldpath.Filter) (_ Manager, err error) { + return &structuredMergeManager{ + typeConverter: typeConverter, + objectConverter: objectConverter, + objectDefaulter: objectDefaulter, + groupVersion: gv, + hubVersion: hub, + updater: merge.Updater{ + Converter: newCRDVersionConverter(typeConverter, objectConverter, hub), + IgnoreFilter: resetFields, + }, + }, nil +} + +func objectGVKNN(obj runtime.Object) string { + name := "" + namespace := "" + if accessor, err := meta.Accessor(obj); err == nil { + name = accessor.GetName() + namespace = accessor.GetNamespace() + } + + return fmt.Sprintf("%v/%v; %v", namespace, name, obj.GetObjectKind().GroupVersionKind()) +} + +// Update implements Manager. +func (f *structuredMergeManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + newObjVersioned, err := f.toVersioned(newObj) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert new object (%v) to proper version (%v): %v", objectGVKNN(newObj), f.groupVersion, err) + } + liveObjVersioned, err := f.toVersioned(liveObj) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert live object (%v) to proper version: %v", objectGVKNN(liveObj), err) + } + newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned, typed.AllowDuplicates) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert new object (%v) to smd typed: %v", objectGVKNN(newObjVersioned), err) + } + liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned, typed.AllowDuplicates) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert live object (%v) to smd typed: %v", objectGVKNN(liveObjVersioned), err) + } + apiVersion := fieldpath.APIVersion(f.groupVersion.String()) + + // TODO(apelisse) use the first return value when unions are implemented + _, managedFields, err := f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed.Fields(), manager) + if err != nil { + return nil, nil, fmt.Errorf("failed to update ManagedFields (%v): %v", objectGVKNN(newObjVersioned), err) + } + managed = NewManaged(managedFields, managed.Times()) + + return newObj, managed, nil +} + +// Apply implements Manager. +func (f *structuredMergeManager) Apply(liveObj, patchObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) { + // Check that the patch object has the same version as the live object + if patchVersion := patchObj.GetObjectKind().GroupVersionKind().GroupVersion(); patchVersion != f.groupVersion { + return nil, nil, + errors.NewBadRequest( + fmt.Sprintf("Incorrect version specified in apply patch. "+ + "Specified patch version: %s, expected: %s", + patchVersion, f.groupVersion)) + } + + patchObjMeta, err := meta.Accessor(patchObj) + if err != nil { + return nil, nil, fmt.Errorf("couldn't get accessor: %v", err) + } + if patchObjMeta.GetManagedFields() != nil { + return nil, nil, errors.NewBadRequest("metadata.managedFields must be nil") + } + + liveObjVersioned, err := f.toVersioned(liveObj) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert live object (%v) to proper version: %v", objectGVKNN(liveObj), err) + } + + // Don't allow duplicates in the applied object. + patchObjTyped, err := f.typeConverter.ObjectToTyped(patchObj) + if err != nil { + return nil, nil, fmt.Errorf("failed to create typed patch object (%v): %v", objectGVKNN(patchObj), err) + } + + liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned, typed.AllowDuplicates) + if err != nil { + return nil, nil, fmt.Errorf("failed to create typed live object (%v): %v", objectGVKNN(liveObjVersioned), err) + } + + apiVersion := fieldpath.APIVersion(f.groupVersion.String()) + newObjTyped, managedFields, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed.Fields(), manager, force) + if err != nil { + return nil, nil, err + } + managed = NewManaged(managedFields, managed.Times()) + + if newObjTyped == nil { + return nil, managed, nil + } + + newObj, err := f.typeConverter.TypedToObject(newObjTyped) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert new typed object (%v) to object: %v", objectGVKNN(patchObj), err) + } + + newObjVersioned, err := f.toVersioned(newObj) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert new object (%v) to proper version: %v", objectGVKNN(patchObj), err) + } + f.objectDefaulter.Default(newObjVersioned) + + newObjUnversioned, err := f.toUnversioned(newObjVersioned) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert to unversioned (%v): %v", objectGVKNN(patchObj), err) + } + return newObjUnversioned, managed, nil +} + +func (f *structuredMergeManager) toVersioned(obj runtime.Object) (runtime.Object, error) { + return f.objectConverter.ConvertToVersion(obj, f.groupVersion) +} + +func (f *structuredMergeManager) toUnversioned(obj runtime.Object) (runtime.Object, error) { + return f.objectConverter.ConvertToVersion(obj, f.hubVersion) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/testdata/swagger.json b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/testdata/swagger.json new file mode 100644 index 0000000000..b067dfc29f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/testdata/swagger.json @@ -0,0 +1,4195 @@ +{ + "definitions": { + "io.k8s.api.apps.v1.Deployment": { + "description": "Deployment enables declarative updates for Pods and ReplicaSets.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object metadata." + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentSpec", + "description": "Specification of the desired behavior of the Deployment." + }, + "status": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentStatus", + "description": "Most recently observed status of the Deployment." + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "Deployment", + "version": "v1" + } + ] + }, + "io.k8s.api.apps.v1.DeploymentCondition": { + "description": "DeploymentCondition describes the state of a deployment at a certain point.", + "properties": { + "lastTransitionTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Last time the condition transitioned from one status to another." + }, + "lastUpdateTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "The last time this condition was updated." + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of deployment condition.", + "type": "string" + } + }, + "required": [ + "type", + "status" + ], + "type": "object" + }, + "io.k8s.api.apps.v1.DeploymentSpec": { + "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", + "properties": { + "minReadySeconds": { + "description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", + "format": "int32", + "type": "integer" + }, + "paused": { + "description": "Indicates that the deployment is paused.", + "type": "boolean" + }, + "progressDeadlineSeconds": { + "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.", + "format": "int32", + "type": "integer" + }, + "replicas": { + "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", + "format": "int32", + "type": "integer" + }, + "revisionHistoryLimit": { + "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.", + "format": "int32", + "type": "integer" + }, + "selector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels." + }, + "strategy": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentStrategy", + "description": "The deployment strategy to use to replace existing pods with new ones.", + "x-kubernetes-patch-strategy": "retainKeys" + }, + "template": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", + "description": "Template describes the pods that will be created." + } + }, + "required": [ + "selector", + "template" + ], + "type": "object" + }, + "io.k8s.api.apps.v1.DeploymentStatus": { + "description": "DeploymentStatus is the most recently observed status of the Deployment.", + "properties": { + "availableReplicas": { + "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "format": "int32", + "type": "integer" + }, + "collisionCount": { + "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", + "format": "int32", + "type": "integer" + }, + "conditions": { + "description": "Represents the latest available observations of a deployment's current state.", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentCondition" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "observedGeneration": { + "description": "The generation observed by the deployment controller.", + "format": "int64", + "type": "integer" + }, + "readyReplicas": { + "description": "Total number of ready pods targeted by this deployment.", + "format": "int32", + "type": "integer" + }, + "replicas": { + "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", + "format": "int32", + "type": "integer" + }, + "unavailableReplicas": { + "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "format": "int32", + "type": "integer" + }, + "updatedReplicas": { + "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1.DeploymentStrategy": { + "description": "DeploymentStrategy describes how to replace existing pods with new ones.", + "properties": { + "rollingUpdate": { + "$ref": "#/definitions/io.k8s.api.apps.v1.RollingUpdateDeployment", + "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." + }, + "type": { + "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1.RollingUpdateDeployment": { + "description": "Spec to control the desired behavior of rolling update.", + "properties": { + "maxSurge": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." + }, + "maxUnavailable": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1beta1.Deployment": { + "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object metadata." + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentSpec", + "description": "Specification of the desired behavior of the Deployment." + }, + "status": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentStatus", + "description": "Most recently observed status of the Deployment." + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "Deployment", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.apps.v1beta1.DeploymentCondition": { + "description": "DeploymentCondition describes the state of a deployment at a certain point.", + "properties": { + "lastTransitionTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Last time the condition transitioned from one status to another." + }, + "lastUpdateTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "The last time this condition was updated." + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of deployment condition.", + "type": "string" + } + }, + "required": [ + "type", + "status" + ], + "type": "object" + }, + "io.k8s.api.apps.v1beta1.DeploymentRollback": { + "description": "DEPRECATED. DeploymentRollback stores the information required to rollback a deployment.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "name": { + "description": "Required: This must match the Name of a deployment.", + "type": "string" + }, + "rollbackTo": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta1.RollbackConfig", + "description": "The config of this deployment rollback." + }, + "updatedAnnotations": { + "additionalProperties": { + "type": "string" + }, + "description": "The annotations to be updated to a deployment", + "type": "object" + } + }, + "required": [ + "name", + "rollbackTo" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "DeploymentRollback", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.apps.v1beta1.DeploymentSpec": { + "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", + "properties": { + "minReadySeconds": { + "description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", + "format": "int32", + "type": "integer" + }, + "paused": { + "description": "Indicates that the deployment is paused.", + "type": "boolean" + }, + "progressDeadlineSeconds": { + "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.", + "format": "int32", + "type": "integer" + }, + "replicas": { + "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", + "format": "int32", + "type": "integer" + }, + "revisionHistoryLimit": { + "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 2.", + "format": "int32", + "type": "integer" + }, + "rollbackTo": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta1.RollbackConfig", + "description": "DEPRECATED. The config this deployment is rolling back to. Will be cleared after rollback is done." + }, + "selector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment." + }, + "strategy": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentStrategy", + "description": "The deployment strategy to use to replace existing pods with new ones.", + "x-kubernetes-patch-strategy": "retainKeys" + }, + "template": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", + "description": "Template describes the pods that will be created." + } + }, + "required": [ + "template" + ], + "type": "object" + }, + "io.k8s.api.apps.v1beta1.DeploymentStatus": { + "description": "DeploymentStatus is the most recently observed status of the Deployment.", + "properties": { + "availableReplicas": { + "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "format": "int32", + "type": "integer" + }, + "collisionCount": { + "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", + "format": "int32", + "type": "integer" + }, + "conditions": { + "description": "Represents the latest available observations of a deployment's current state.", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta1.DeploymentCondition" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "observedGeneration": { + "description": "The generation observed by the deployment controller.", + "format": "int64", + "type": "integer" + }, + "readyReplicas": { + "description": "Total number of ready pods targeted by this deployment.", + "format": "int32", + "type": "integer" + }, + "replicas": { + "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", + "format": "int32", + "type": "integer" + }, + "unavailableReplicas": { + "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "format": "int32", + "type": "integer" + }, + "updatedReplicas": { + "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1beta1.DeploymentStrategy": { + "description": "DeploymentStrategy describes how to replace existing pods with new ones.", + "properties": { + "rollingUpdate": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta1.RollingUpdateDeployment", + "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." + }, + "type": { + "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1beta1.RollbackConfig": { + "description": "DEPRECATED.", + "properties": { + "revision": { + "description": "The revision to rollback to. If set to 0, rollback to the last revision.", + "format": "int64", + "type": "integer" + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1beta1.RollingUpdateDeployment": { + "description": "Spec to control the desired behavior of rolling update.", + "properties": { + "maxSurge": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." + }, + "maxUnavailable": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1beta2.Deployment": { + "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object metadata." + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DeploymentSpec", + "description": "Specification of the desired behavior of the Deployment." + }, + "status": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DeploymentStatus", + "description": "Most recently observed status of the Deployment." + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "apps", + "kind": "Deployment", + "version": "v1beta2" + } + ] + }, + "io.k8s.api.apps.v1beta2.DeploymentCondition": { + "description": "DeploymentCondition describes the state of a deployment at a certain point.", + "properties": { + "lastTransitionTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Last time the condition transitioned from one status to another." + }, + "lastUpdateTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "The last time this condition was updated." + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of deployment condition.", + "type": "string" + } + }, + "required": [ + "type", + "status" + ], + "type": "object" + }, + "io.k8s.api.apps.v1beta2.DeploymentSpec": { + "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", + "properties": { + "minReadySeconds": { + "description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", + "format": "int32", + "type": "integer" + }, + "paused": { + "description": "Indicates that the deployment is paused.", + "type": "boolean" + }, + "progressDeadlineSeconds": { + "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s.", + "format": "int32", + "type": "integer" + }, + "replicas": { + "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", + "format": "int32", + "type": "integer" + }, + "revisionHistoryLimit": { + "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.", + "format": "int32", + "type": "integer" + }, + "selector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels." + }, + "strategy": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DeploymentStrategy", + "description": "The deployment strategy to use to replace existing pods with new ones.", + "x-kubernetes-patch-strategy": "retainKeys" + }, + "template": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", + "description": "Template describes the pods that will be created." + } + }, + "required": [ + "selector", + "template" + ], + "type": "object" + }, + "io.k8s.api.apps.v1beta2.DeploymentStatus": { + "description": "DeploymentStatus is the most recently observed status of the Deployment.", + "properties": { + "availableReplicas": { + "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "format": "int32", + "type": "integer" + }, + "collisionCount": { + "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", + "format": "int32", + "type": "integer" + }, + "conditions": { + "description": "Represents the latest available observations of a deployment's current state.", + "items": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta2.DeploymentCondition" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "observedGeneration": { + "description": "The generation observed by the deployment controller.", + "format": "int64", + "type": "integer" + }, + "readyReplicas": { + "description": "Total number of ready pods targeted by this deployment.", + "format": "int32", + "type": "integer" + }, + "replicas": { + "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", + "format": "int32", + "type": "integer" + }, + "unavailableReplicas": { + "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "format": "int32", + "type": "integer" + }, + "updatedReplicas": { + "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1beta2.DeploymentStrategy": { + "description": "DeploymentStrategy describes how to replace existing pods with new ones.", + "properties": { + "rollingUpdate": { + "$ref": "#/definitions/io.k8s.api.apps.v1beta2.RollingUpdateDeployment", + "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." + }, + "type": { + "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.apps.v1beta2.RollingUpdateDeployment": { + "description": "Spec to control the desired behavior of rolling update.", + "properties": { + "maxSurge": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." + }, + "maxUnavailable": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource": { + "description": "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.", + "properties": { + "fsType": { + "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + "type": "string" + }, + "partition": { + "description": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).", + "format": "int32", + "type": "integer" + }, + "readOnly": { + "description": "Specify \"true\" to force and set the ReadOnly property in VolumeMounts to \"true\". If omitted, the default is \"false\". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + "type": "boolean" + }, + "volumeID": { + "description": "Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + "type": "string" + } + }, + "required": [ + "volumeID" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Affinity": { + "description": "Affinity is a group of affinity scheduling rules.", + "properties": { + "nodeAffinity": { + "$ref": "#/definitions/io.k8s.api.core.v1.NodeAffinity", + "description": "Describes node affinity scheduling rules for the pod." + }, + "podAffinity": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinity", + "description": "Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s))." + }, + "podAntiAffinity": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodAntiAffinity", + "description": "Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s))." + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.AttachedVolume": { + "description": "AttachedVolume describes a volume attached to a node", + "properties": { + "devicePath": { + "description": "DevicePath represents the device path where the volume should be available", + "type": "string" + }, + "name": { + "description": "Name of the attached volume", + "type": "string" + } + }, + "required": [ + "name", + "devicePath" + ], + "type": "object" + }, + "io.k8s.api.core.v1.AzureDiskVolumeSource": { + "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.", + "properties": { + "cachingMode": { + "description": "Host Caching mode: None, Read Only, Read Write.", + "type": "string" + }, + "diskName": { + "description": "The Name of the data disk in the blob storage", + "type": "string" + }, + "diskURI": { + "description": "The URI the data disk in the blob storage", + "type": "string" + }, + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + "type": "string" + }, + "kind": { + "description": "Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared", + "type": "string" + }, + "readOnly": { + "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + } + }, + "required": [ + "diskName", + "diskURI" + ], + "type": "object" + }, + "io.k8s.api.core.v1.AzureFileVolumeSource": { + "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", + "properties": { + "readOnly": { + "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + }, + "secretName": { + "description": "the name of secret that contains Azure Storage Account Name and Key", + "type": "string" + }, + "shareName": { + "description": "Share Name", + "type": "string" + } + }, + "required": [ + "secretName", + "shareName" + ], + "type": "object" + }, + "io.k8s.api.core.v1.CSIVolumeSource": { + "description": "Represents a source location of a volume to mount, managed by an external CSI driver", + "properties": { + "driver": { + "description": "Driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster.", + "type": "string" + }, + "fsType": { + "description": "Filesystem type to mount. Ex. \"ext4\", \"xfs\", \"ntfs\". If not provided, the empty value is passed to the associated CSI driver which will determine the default filesystem to apply.", + "type": "string" + }, + "nodePublishSecretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", + "description": "NodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secret references are passed." + }, + "readOnly": { + "description": "Specifies a read-only configuration for the volume. Defaults to false (read/write).", + "type": "boolean" + }, + "volumeAttributes": { + "additionalProperties": { + "type": "string" + }, + "description": "VolumeAttributes stores driver-specific properties that are passed to the CSI driver. Consult your driver's documentation for supported values.", + "type": "object" + } + }, + "required": [ + "driver" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Capabilities": { + "description": "Adds and removes POSIX capabilities from running containers.", + "properties": { + "add": { + "description": "Added capabilities", + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "description": "Removed capabilities", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.CephFSVolumeSource": { + "description": "Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.", + "properties": { + "monitors": { + "description": "Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + "items": { + "type": "string" + }, + "type": "array" + }, + "path": { + "description": "Optional: Used as the mounted root, rather than the full Ceph tree, default is /", + "type": "string" + }, + "readOnly": { + "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + "type": "boolean" + }, + "secretFile": { + "description": "Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + "type": "string" + }, + "secretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", + "description": "Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it" + }, + "user": { + "description": "Optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + "type": "string" + } + }, + "required": [ + "monitors" + ], + "type": "object" + }, + "io.k8s.api.core.v1.CinderVolumeSource": { + "description": "Represents a cinder volume resource in Openstack. A Cinder volume must exist before mounting to a container. The volume must also be in the same region as the kubelet. Cinder volumes support ownership management and SELinux relabeling.", + "properties": { + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + "type": "string" + }, + "readOnly": { + "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + "type": "boolean" + }, + "secretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", + "description": "Optional: points to a secret object containing parameters used to connect to OpenStack." + }, + "volumeID": { + "description": "volume id used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + "type": "string" + } + }, + "required": [ + "volumeID" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ClientIPConfig": { + "description": "ClientIPConfig represents the configurations of Client IP based session affinity.", + "properties": { + "timeoutSeconds": { + "description": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be >0 && <=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours).", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ComponentCondition": { + "description": "Information about the condition of a component.", + "properties": { + "error": { + "description": "Condition error code for a component. For example, a health check error code.", + "type": "string" + }, + "message": { + "description": "Message about the condition for a component. For example, information about a health check.", + "type": "string" + }, + "status": { + "description": "Status of the condition for a component. Valid values for \"Healthy\": \"True\", \"False\", or \"Unknown\".", + "type": "string" + }, + "type": { + "description": "Type of condition for a component. Valid value: \"Healthy\"", + "type": "string" + } + }, + "required": [ + "type", + "status" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ConfigMapEnvSource": { + "description": "ConfigMapEnvSource selects a ConfigMap to populate the environment variables with.\n\nThe contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables.", + "properties": { + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap must be defined", + "type": "boolean" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ConfigMapKeySelector": { + "description": "Selects a key from a ConfigMap.", + "properties": { + "key": { + "description": "The key to select.", + "type": "string" + }, + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ConfigMapNodeConfigSource": { + "description": "ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node.", + "properties": { + "kubeletConfigKey": { + "description": "KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.", + "type": "string" + }, + "name": { + "description": "Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.", + "type": "string" + }, + "namespace": { + "description": "Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.", + "type": "string" + }, + "resourceVersion": { + "description": "ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type": "string" + }, + "uid": { + "description": "UID is the metadata.UID of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + "type": "string" + } + }, + "required": [ + "namespace", + "name", + "kubeletConfigKey" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ConfigMapProjection": { + "description": "Adapts a ConfigMap into a projected volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a projected volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. Note that this is identical to a configmap volume source without the default mode.", + "properties": { + "items": { + "description": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" + }, + "type": "array" + }, + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its keys must be defined", + "type": "boolean" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ConfigMapVolumeSource": { + "description": "Adapts a ConfigMap into a volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. ConfigMap volumes support ownership management and SELinux relabeling.", + "properties": { + "defaultMode": { + "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + "format": "int32", + "type": "integer" + }, + "items": { + "description": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" + }, + "type": "array" + }, + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the ConfigMap or its keys must be defined", + "type": "boolean" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.Container": { + "description": "A single application container that you want to run within a pod.", + "properties": { + "args": { + "description": "Arguments to the entrypoint. The container image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "description": "Entrypoint array. Not executed within a shell. The container image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "description": "List of environment variables to set in the container. Cannot be updated.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.EnvVar" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "envFrom": { + "description": "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.EnvFromSource" + }, + "type": "array" + }, + "image": { + "description": "Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.", + "type": "string" + }, + "imagePullPolicy": { + "description": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images", + "type": "string" + }, + "lifecycle": { + "$ref": "#/definitions/io.k8s.api.core.v1.Lifecycle", + "description": "Actions that the management system should take in response to container lifecycle events. Cannot be updated." + }, + "livenessProbe": { + "$ref": "#/definitions/io.k8s.api.core.v1.Probe", + "description": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" + }, + "name": { + "description": "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.", + "type": "string" + }, + "ports": { + "description": "List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Cannot be updated.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "containerPort", + "x-kubernetes-patch-strategy": "merge" + }, + "readinessProbe": { + "$ref": "#/definitions/io.k8s.api.core.v1.Probe", + "description": "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" + }, + "resources": { + "$ref": "#/definitions/io.k8s.api.core.v1.ResourceRequirements", + "description": "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/" + }, + "securityContext": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecurityContext", + "description": "Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" + }, + "startupProbe": { + "$ref": "#/definitions/io.k8s.api.core.v1.Probe", + "description": "StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. This is an alpha feature enabled by the StartupProbe feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes" + }, + "stdin": { + "description": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", + "type": "boolean" + }, + "stdinOnce": { + "description": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", + "type": "boolean" + }, + "terminationMessagePath": { + "description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", + "type": "string" + }, + "terminationMessagePolicy": { + "description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.", + "type": "string" + }, + "tty": { + "description": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", + "type": "boolean" + }, + "volumeDevices": { + "description": "volumeDevices is the list of block devices to be used by the container. This is a beta feature.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.VolumeDevice" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "devicePath", + "x-kubernetes-patch-strategy": "merge" + }, + "volumeMounts": { + "description": "Pod volumes to mount into the container's filesystem. Cannot be updated.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.VolumeMount" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "mountPath", + "x-kubernetes-patch-strategy": "merge" + }, + "workingDir": { + "description": "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ContainerImage": { + "description": "Describe a container image", + "properties": { + "names": { + "description": "Names by which this image is known. e.g. [\"registry.k8s.io/hyperkube:v1.0.7\", \"dockerhub.io/google_containers/hyperkube:v1.0.7\"]", + "items": { + "type": "string" + }, + "type": "array" + }, + "sizeBytes": { + "description": "The size of the image in bytes.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "names" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ContainerPort": { + "description": "ContainerPort represents a network port in a single container.", + "properties": { + "containerPort": { + "description": "Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536.", + "format": "int32", + "type": "integer" + }, + "hostIP": { + "description": "What host IP to bind the external port to.", + "type": "string" + }, + "hostPort": { + "description": "Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.", + "format": "int32", + "type": "integer" + }, + "name": { + "description": "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services.", + "type": "string" + }, + "protocol": { + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\".", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ContainerState": { + "description": "ContainerState holds a possible state of container. Only one of its members may be specified. If none of them is specified, the default one is ContainerStateWaiting.", + "properties": { + "running": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStateRunning", + "description": "Details about a running container" + }, + "terminated": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStateTerminated", + "description": "Details about a terminated container" + }, + "waiting": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStateWaiting", + "description": "Details about a waiting container" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ContainerStateRunning": { + "description": "ContainerStateRunning is a running state of a container.", + "properties": { + "startedAt": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Time at which the container was last (re-)started" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ContainerStateTerminated": { + "description": "ContainerStateTerminated is a terminated state of a container.", + "properties": { + "containerID": { + "description": "Container's ID in the format 'docker://'", + "type": "string" + }, + "exitCode": { + "description": "Exit status from the last termination of the container", + "format": "int32", + "type": "integer" + }, + "finishedAt": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Time at which the container last terminated" + }, + "message": { + "description": "Message regarding the last termination of the container", + "type": "string" + }, + "reason": { + "description": "(brief) reason from the last termination of the container", + "type": "string" + }, + "signal": { + "description": "Signal from the last termination of the container", + "format": "int32", + "type": "integer" + }, + "startedAt": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Time at which previous execution of the container started" + } + }, + "required": [ + "exitCode" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ContainerStateWaiting": { + "description": "ContainerStateWaiting is a waiting state of a container.", + "properties": { + "message": { + "description": "Message regarding why the container is not yet running.", + "type": "string" + }, + "reason": { + "description": "(brief) reason the container is not yet running.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ContainerStatus": { + "description": "ContainerStatus contains details for the current status of this container.", + "properties": { + "containerID": { + "description": "Container's ID in the format 'docker://'.", + "type": "string" + }, + "image": { + "description": "The image the container is running. More info: https://kubernetes.io/docs/concepts/containers/images", + "type": "string" + }, + "imageID": { + "description": "ImageID of the container's image.", + "type": "string" + }, + "lastState": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerState", + "description": "Details about the container's last termination condition." + }, + "name": { + "description": "This must be a DNS_LABEL. Each container in a pod must have a unique name. Cannot be updated.", + "type": "string" + }, + "ready": { + "description": "Specifies whether the container has passed its readiness probe.", + "type": "boolean" + }, + "restartCount": { + "description": "The number of times the container has been restarted, currently based on the number of dead containers that have not yet been removed. Note that this is calculated from dead containers. But those containers are subject to garbage collection. This value will get capped at 5 by GC.", + "format": "int32", + "type": "integer" + }, + "started": { + "description": "Specifies whether the container has passed its startup probe. Initialized as false, becomes true after startupProbe is considered successful. Resets to false when the container is restarted, or if kubelet loses state temporarily. Is always true when no startupProbe is defined.", + "type": "boolean" + }, + "state": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerState", + "description": "Details about the container's current condition." + } + }, + "required": [ + "name", + "ready", + "restartCount", + "image", + "imageID" + ], + "type": "object" + }, + "io.k8s.api.core.v1.DownwardAPIProjection": { + "description": "Represents downward API info for projecting into a projected volume. Note that this is identical to a downwardAPI volume source without the default mode.", + "properties": { + "items": { + "description": "Items is a list of DownwardAPIVolume file", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeFile" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.DownwardAPIVolumeFile": { + "description": "DownwardAPIVolumeFile represents information to create the file containing the pod field", + "properties": { + "fieldRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.ObjectFieldSelector", + "description": "Required: Selects a field of the pod: only annotations, labels, name and namespace are supported." + }, + "mode": { + "description": "Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + "format": "int32", + "type": "integer" + }, + "path": { + "description": "Required: Path is the relative path name of the file to be created. Must not be absolute or contain the '..' path. Must be utf-8 encoded. The first item of the relative path must not start with '..'", + "type": "string" + }, + "resourceFieldRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.ResourceFieldSelector", + "description": "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported." + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "io.k8s.api.core.v1.DownwardAPIVolumeSource": { + "description": "DownwardAPIVolumeSource represents a volume containing downward API info. Downward API volumes support ownership management and SELinux relabeling.", + "properties": { + "defaultMode": { + "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + "format": "int32", + "type": "integer" + }, + "items": { + "description": "Items is a list of downward API volume file", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeFile" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.EmptyDirVolumeSource": { + "description": "Represents an empty directory for a pod. Empty directory volumes support ownership management and SELinux relabeling.", + "properties": { + "medium": { + "description": "What type of storage medium should back this directory. The default is \"\" which means to use the node's default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", + "type": "string" + }, + "sizeLimit": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity", + "description": "Total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.EnvFromSource": { + "description": "EnvFromSource represents the source of a set of ConfigMaps", + "properties": { + "configMapRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapEnvSource", + "description": "The ConfigMap to select from" + }, + "prefix": { + "description": "An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.", + "type": "string" + }, + "secretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecretEnvSource", + "description": "The Secret to select from" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.EnvVar": { + "description": "EnvVar represents an environment variable present in a Container.", + "properties": { + "name": { + "description": "Name of the environment variable. Must be a C_IDENTIFIER.", + "type": "string" + }, + "value": { + "description": "Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to \"\".", + "type": "string" + }, + "valueFrom": { + "$ref": "#/definitions/io.k8s.api.core.v1.EnvVarSource", + "description": "Source for the environment variable's value. Cannot be used if value is not empty." + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "io.k8s.api.core.v1.EnvVarSource": { + "description": "EnvVarSource represents a source for the value of an EnvVar.", + "properties": { + "configMapKeyRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapKeySelector", + "description": "Selects a key of a ConfigMap." + }, + "fieldRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.ObjectFieldSelector", + "description": "Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs." + }, + "resourceFieldRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.ResourceFieldSelector", + "description": "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported." + }, + "secretKeyRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector", + "description": "Selects a key of a secret in the pod's namespace" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.EphemeralContainer": { + "description": "An EphemeralContainer is a container that may be added temporarily to an existing pod for user-initiated activities such as debugging. Ephemeral containers have no resource or scheduling guarantees, and they will not be restarted when they exit or when a pod is removed or restarted. If an ephemeral container causes a pod to exceed its resource allocation, the pod may be evicted. Ephemeral containers may not be added by directly updating the pod spec. They must be added via the pod's ephemeralcontainers subresource, and they will appear in the pod spec once added.", + "properties": { + "args": { + "description": "Arguments to the entrypoint. The container image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "description": "Entrypoint array. Not executed within a shell. The container image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "description": "List of environment variables to set in the container. Cannot be updated.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.EnvVar" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "envFrom": { + "description": "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.EnvFromSource" + }, + "type": "array" + }, + "image": { + "description": "Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images", + "type": "string" + }, + "imagePullPolicy": { + "description": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images", + "type": "string" + }, + "lifecycle": { + "$ref": "#/definitions/io.k8s.api.core.v1.Lifecycle", + "description": "Lifecycle is not allowed for ephemeral containers." + }, + "livenessProbe": { + "$ref": "#/definitions/io.k8s.api.core.v1.Probe", + "description": "Probes are not allowed for ephemeral containers." + }, + "name": { + "description": "Name of the ephemeral container specified as a DNS_LABEL. This name must be unique among all containers, init containers and ephemeral containers.", + "type": "string" + }, + "ports": { + "description": "Ports are not allowed for ephemeral containers.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort" + }, + "type": "array" + }, + "readinessProbe": { + "$ref": "#/definitions/io.k8s.api.core.v1.Probe", + "description": "Probes are not allowed for ephemeral containers." + }, + "resources": { + "$ref": "#/definitions/io.k8s.api.core.v1.ResourceRequirements", + "description": "Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod." + }, + "securityContext": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecurityContext", + "description": "SecurityContext is not allowed for ephemeral containers." + }, + "startupProbe": { + "$ref": "#/definitions/io.k8s.api.core.v1.Probe", + "description": "Probes are not allowed for ephemeral containers." + }, + "stdin": { + "description": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", + "type": "boolean" + }, + "stdinOnce": { + "description": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", + "type": "boolean" + }, + "targetContainerName": { + "description": "If set, the name of the container from PodSpec that this ephemeral container targets. The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container. If not set then the ephemeral container is run in whatever namespaces are shared for the pod. Note that the container runtime must support this feature.", + "type": "string" + }, + "terminationMessagePath": { + "description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", + "type": "string" + }, + "terminationMessagePolicy": { + "description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.", + "type": "string" + }, + "tty": { + "description": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", + "type": "boolean" + }, + "volumeDevices": { + "description": "volumeDevices is the list of block devices to be used by the container. This is a beta feature.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.VolumeDevice" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "devicePath", + "x-kubernetes-patch-strategy": "merge" + }, + "volumeMounts": { + "description": "Pod volumes to mount into the container's filesystem. Cannot be updated.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.VolumeMount" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "mountPath", + "x-kubernetes-patch-strategy": "merge" + }, + "workingDir": { + "description": "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ExecAction": { + "description": "ExecAction describes a \"run in container\" action.", + "properties": { + "command": { + "description": "Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.FCVolumeSource": { + "description": "Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.", + "properties": { + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + "type": "string" + }, + "lun": { + "description": "Optional: FC target lun number", + "format": "int32", + "type": "integer" + }, + "readOnly": { + "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + }, + "targetWWNs": { + "description": "Optional: FC target worldwide names (WWNs)", + "items": { + "type": "string" + }, + "type": "array" + }, + "wwids": { + "description": "Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.FlexVolumeSource": { + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", + "properties": { + "driver": { + "description": "Driver is the name of the driver to use for this volume.", + "type": "string" + }, + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.", + "type": "string" + }, + "options": { + "additionalProperties": { + "type": "string" + }, + "description": "Optional: Extra command options if any.", + "type": "object" + }, + "readOnly": { + "description": "Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + }, + "secretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", + "description": "Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts." + } + }, + "required": [ + "driver" + ], + "type": "object" + }, + "io.k8s.api.core.v1.FlockerVolumeSource": { + "description": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.", + "properties": { + "datasetName": { + "description": "Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated", + "type": "string" + }, + "datasetUUID": { + "description": "UUID of the dataset. This is unique identifier of a Flocker dataset", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.GCEPersistentDiskVolumeSource": { + "description": "Represents a Persistent Disk resource in Google Compute Engine.\n\nA GCE PD must exist before mounting to a container. The disk must also be in the same GCE project and zone as the kubelet. A GCE PD can only be mounted as read/write once or read-only many times. GCE PDs support ownership management and SELinux relabeling.", + "properties": { + "fsType": { + "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + "type": "string" + }, + "partition": { + "description": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + "format": "int32", + "type": "integer" + }, + "pdName": { + "description": "Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + "type": "string" + }, + "readOnly": { + "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + "type": "boolean" + } + }, + "required": [ + "pdName" + ], + "type": "object" + }, + "io.k8s.api.core.v1.GitRepoVolumeSource": { + "description": "Represents a volume that is populated with the contents of a git repository. Git repo volumes do not support ownership management. Git repo volumes support SELinux relabeling.\n\nDEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container.", + "properties": { + "directory": { + "description": "Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.", + "type": "string" + }, + "repository": { + "description": "Repository URL", + "type": "string" + }, + "revision": { + "description": "Commit hash for the specified revision.", + "type": "string" + } + }, + "required": [ + "repository" + ], + "type": "object" + }, + "io.k8s.api.core.v1.GlusterfsVolumeSource": { + "description": "Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.", + "properties": { + "endpoints": { + "description": "EndpointsName is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + "type": "string" + }, + "path": { + "description": "Path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + "type": "string" + }, + "readOnly": { + "description": "ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + "type": "boolean" + } + }, + "required": [ + "endpoints", + "path" + ], + "type": "object" + }, + "io.k8s.api.core.v1.HTTPGetAction": { + "description": "HTTPGetAction describes an action based on HTTP Get requests.", + "properties": { + "host": { + "description": "Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead.", + "type": "string" + }, + "httpHeaders": { + "description": "Custom headers to set in the request. HTTP allows repeated headers.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.HTTPHeader" + }, + "type": "array" + }, + "path": { + "description": "Path to access on the HTTP server.", + "type": "string" + }, + "port": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME." + }, + "scheme": { + "description": "Scheme to use for connecting to the host. Defaults to HTTP.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "io.k8s.api.core.v1.HTTPHeader": { + "description": "HTTPHeader describes a custom header to be used in HTTP probes", + "properties": { + "name": { + "description": "The header field name", + "type": "string" + }, + "value": { + "description": "The header field value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Handler": { + "description": "Handler defines a specific action that should be taken", + "properties": { + "exec": { + "$ref": "#/definitions/io.k8s.api.core.v1.ExecAction", + "description": "One and only one of the following should be specified. Exec specifies the action to take." + }, + "httpGet": { + "$ref": "#/definitions/io.k8s.api.core.v1.HTTPGetAction", + "description": "HTTPGet specifies the http request to perform." + }, + "tcpSocket": { + "$ref": "#/definitions/io.k8s.api.core.v1.TCPSocketAction", + "description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.HostAlias": { + "description": "HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.", + "properties": { + "hostnames": { + "description": "Hostnames for the above IP address.", + "items": { + "type": "string" + }, + "type": "array" + }, + "ip": { + "description": "IP address of the host file entry.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.HostPathVolumeSource": { + "description": "Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.", + "properties": { + "path": { + "description": "Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", + "type": "string" + }, + "type": { + "description": "Type for HostPath Volume Defaults to \"\" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ISCSIVolumeSource": { + "description": "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", + "properties": { + "chapAuthDiscovery": { + "description": "whether support iSCSI Discovery CHAP authentication", + "type": "boolean" + }, + "chapAuthSession": { + "description": "whether support iSCSI Session CHAP authentication", + "type": "boolean" + }, + "fsType": { + "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi", + "type": "string" + }, + "initiatorName": { + "description": "Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection.", + "type": "string" + }, + "iqn": { + "description": "Target iSCSI Qualified Name.", + "type": "string" + }, + "iscsiInterface": { + "description": "iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp).", + "type": "string" + }, + "lun": { + "description": "iSCSI Target Lun number.", + "format": "int32", + "type": "integer" + }, + "portals": { + "description": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "items": { + "type": "string" + }, + "type": "array" + }, + "readOnly": { + "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.", + "type": "boolean" + }, + "secretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", + "description": "CHAP Secret for iSCSI target and initiator authentication" + }, + "targetPortal": { + "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + "type": "string" + } + }, + "required": [ + "targetPortal", + "iqn", + "lun" + ], + "type": "object" + }, + "io.k8s.api.core.v1.KeyToPath": { + "description": "Maps a string key to a path within a volume.", + "properties": { + "key": { + "description": "The key to project.", + "type": "string" + }, + "mode": { + "description": "Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + "format": "int32", + "type": "integer" + }, + "path": { + "description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'.", + "type": "string" + } + }, + "required": [ + "key", + "path" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Lifecycle": { + "description": "Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.", + "properties": { + "postStart": { + "$ref": "#/definitions/io.k8s.api.core.v1.Handler", + "description": "PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks" + }, + "preStop": { + "$ref": "#/definitions/io.k8s.api.core.v1.Handler", + "description": "PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod's termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod's termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.LocalObjectReference": { + "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", + "properties": { + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.LocalVolumeSource": { + "description": "Local represents directly-attached storage with node affinity (Beta feature)", + "properties": { + "fsType": { + "description": "Filesystem type to mount. It applies only when the Path is a block device. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default value is to auto-select a fileystem if unspecified.", + "type": "string" + }, + "path": { + "description": "The full path to the volume on the node. It can be either a directory or block device (disk, partition, ...).", + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "io.k8s.api.core.v1.NFSVolumeSource": { + "description": "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.", + "properties": { + "path": { + "description": "Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + "type": "string" + }, + "readOnly": { + "description": "ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + "type": "boolean" + }, + "server": { + "description": "Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + "type": "string" + } + }, + "required": [ + "server", + "path" + ], + "type": "object" + }, + "io.k8s.api.core.v1.NodeAffinity": { + "description": "Node affinity is a group of node affinity scheduling rules.", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PreferredSchedulingTerm" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelector", + "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node." + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.NodeSelector": { + "description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.", + "properties": { + "nodeSelectorTerms": { + "description": "Required. A list of node selector terms. The terms are ORed.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorTerm" + }, + "type": "array" + } + }, + "required": [ + "nodeSelectorTerms" + ], + "type": "object" + }, + "io.k8s.api.core.v1.NodeSelectorRequirement": { + "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "The label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.", + "type": "string" + }, + "values": { + "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "io.k8s.api.core.v1.NodeSelectorTerm": { + "description": "A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.", + "properties": { + "matchExpressions": { + "description": "A list of node selector requirements by node's labels.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorRequirement" + }, + "type": "array" + }, + "matchFields": { + "description": "A list of node selector requirements by node's fields.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorRequirement" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ObjectFieldSelector": { + "description": "ObjectFieldSelector selects an APIVersioned field of an object.", + "properties": { + "apiVersion": { + "description": "Version of the schema the FieldPath is written in terms of, defaults to \"v1\".", + "type": "string" + }, + "fieldPath": { + "description": "Path of the field to select in the specified API version.", + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ObjectReference": { + "description": "ObjectReference contains enough information to let you inspect or modify the referred object.", + "properties": { + "apiVersion": { + "description": "API version of the referent.", + "type": "string" + }, + "fieldPath": { + "description": "If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: \"spec.containers{name}\" (where \"name\" refers to the name of the container that triggered the event) or if no container name is specified \"spec.containers[2]\" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.", + "type": "string" + }, + "kind": { + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "namespace": { + "description": "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", + "type": "string" + }, + "resourceVersion": { + "description": "Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" + }, + "uid": { + "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource": { + "description": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).", + "properties": { + "claimName": { + "description": "ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", + "type": "string" + }, + "readOnly": { + "description": "Will force the ReadOnly setting in VolumeMounts. Default false.", + "type": "boolean" + } + }, + "required": [ + "claimName" + ], + "type": "object" + }, + "io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource": { + "description": "Represents a Photon Controller persistent disk resource.", + "properties": { + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + "type": "string" + }, + "pdID": { + "description": "ID that identifies Photon Controller persistent disk", + "type": "string" + } + }, + "required": [ + "pdID" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Pod": { + "description": "Pod is a collection of containers that can run on a host. This resource is created by clients and scheduled onto hosts.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodSpec", + "description": "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" + }, + "status": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodStatus", + "description": "Most recently observed status of the pod. This data may not be up to date. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "", + "kind": "Pod", + "version": "v1" + } + ] + }, + "io.k8s.api.core.v1.PodAffinity": { + "description": "Pod affinity is a group of inter pod affinity scheduling rules.", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.WeightedPodAffinityTerm" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PodAffinityTerm": { + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running", + "properties": { + "labelSelector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "A label query over a set of resources, in this case pods." + }, + "namespaces": { + "description": "namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means \"this pod's namespace\"", + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "io.k8s.api.core.v1.PodAntiAffinity": { + "description": "Pod anti affinity is a group of inter pod anti affinity scheduling rules.", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "description": "The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.WeightedPodAffinityTerm" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "description": "If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PodCondition": { + "description": "PodCondition contains details for the current condition of this pod.", + "properties": { + "lastProbeTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Last time we probed the condition." + }, + "lastTransitionTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Last time the condition transitioned from one status to another." + }, + "message": { + "description": "Human-readable message indicating details about last transition.", + "type": "string" + }, + "reason": { + "description": "Unique, one-word, CamelCase reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status is the status of the condition. Can be True, False, Unknown. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", + "type": "string" + }, + "type": { + "description": "Type is the type of the condition. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", + "type": "string" + } + }, + "required": [ + "type", + "status" + ], + "type": "object" + }, + "io.k8s.api.core.v1.PodDNSConfig": { + "description": "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + "properties": { + "nameservers": { + "description": "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.", + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "description": "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodDNSConfigOption" + }, + "type": "array" + }, + "searches": { + "description": "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PodDNSConfigOption": { + "description": "PodDNSConfigOption defines DNS resolver options of a pod.", + "properties": { + "name": { + "description": "Required.", + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PodIP": { + "description": "IP address information for entries in the (plural) PodIPs field. Each entry includes:\n IP: An IP address allocated to the pod. Routable at least within the cluster.", + "properties": { + "ip": { + "description": "ip is an IP address (IPv4 or IPv6) assigned to the pod", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PodReadinessGate": { + "description": "PodReadinessGate contains the reference to a pod condition", + "properties": { + "conditionType": { + "description": "ConditionType refers to a condition in the pod's condition list with matching type.", + "type": "string" + } + }, + "required": [ + "conditionType" + ], + "type": "object" + }, + "io.k8s.api.core.v1.PodSecurityContext": { + "description": "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.", + "properties": { + "fsGroup": { + "description": "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume.", + "format": "int64", + "type": "integer" + }, + "runAsGroup": { + "description": "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.", + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "description": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "type": "boolean" + }, + "runAsUser": { + "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container.", + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "$ref": "#/definitions/io.k8s.api.core.v1.SELinuxOptions", + "description": "The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container." + }, + "supplementalGroups": { + "description": "A list of groups applied to the first process run in each container, in addition to the container's primary GID. If unspecified, no groups will be added to any container.", + "items": { + "format": "int64", + "type": "integer" + }, + "type": "array" + }, + "sysctls": { + "description": "Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.Sysctl" + }, + "type": "array" + }, + "windowsOptions": { + "$ref": "#/definitions/io.k8s.api.core.v1.WindowsSecurityContextOptions", + "description": "The Windows specific settings applied to all containers. If unspecified, the options within a container's SecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PodSpec": { + "description": "PodSpec is a description of a pod.", + "properties": { + "activeDeadlineSeconds": { + "description": "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.", + "format": "int64", + "type": "integer" + }, + "affinity": { + "$ref": "#/definitions/io.k8s.api.core.v1.Affinity", + "description": "If specified, the pod's scheduling constraints" + }, + "automountServiceAccountToken": { + "description": "AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.", + "type": "boolean" + }, + "containers": { + "description": "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.Container" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "dnsConfig": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodDNSConfig", + "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy." + }, + "dnsPolicy": { + "description": "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.", + "type": "string" + }, + "enableServiceLinks": { + "description": "EnableServiceLinks indicates whether information about services should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Defaults to true.", + "type": "boolean" + }, + "ephemeralContainers": { + "description": "List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing pod to perform user-initiated actions such as debugging. This list cannot be specified when creating a pod, and it cannot be modified by updating the pod spec. In order to add an ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.EphemeralContainer" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "hostAliases": { + "description": "HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.HostAlias" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "ip", + "x-kubernetes-patch-strategy": "merge" + }, + "hostIPC": { + "description": "Use the host's ipc namespace. Optional: Default to false.", + "type": "boolean" + }, + "hostNetwork": { + "description": "Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false.", + "type": "boolean" + }, + "hostPID": { + "description": "Use the host's pid namespace. Optional: Default to false.", + "type": "boolean" + }, + "hostname": { + "description": "Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value.", + "type": "string" + }, + "imagePullSecrets": { + "description": "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "initContainers": { + "description": "List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.Container" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "nodeName": { + "description": "NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements.", + "type": "string" + }, + "nodeSelector": { + "additionalProperties": { + "type": "string" + }, + "description": "NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/", + "type": "object" + }, + "overhead": { + "additionalProperties": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" + }, + "description": "Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. This field will be autopopulated at admission time by the RuntimeClass admission controller. If the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. The RuntimeClass admission controller will reject Pod create requests which have the overhead already set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead This field is alpha-level as of Kubernetes v1.16, and is only honored by servers that enable the PodOverhead feature.", + "type": "object" + }, + "preemptionPolicy": { + "description": "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is alpha-level and is only honored by servers that enable the NonPreemptingPriority feature.", + "type": "string" + }, + "priority": { + "description": "The priority value. Various system components use this field to find the priority of the pod. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority.", + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "description": "If specified, indicates the pod's priority. \"system-node-critical\" and \"system-cluster-critical\" are two special keywords which indicate the highest priorities with the former being the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.", + "type": "string" + }, + "readinessGates": { + "description": "If specified, all readiness gates will be evaluated for pod readiness. A pod is ready when all its containers are ready AND all conditions specified in the readiness gates have status equal to \"True\" More info: https://git.k8s.io/enhancements/keps/sig-network/580-pod-readiness-gates", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodReadinessGate" + }, + "type": "array" + }, + "restartPolicy": { + "description": "Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy", + "type": "string" + }, + "runtimeClassName": { + "description": "RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run. If unset or empty, the \"legacy\" RuntimeClass will be used, which is an implicit class with an empty definition that uses the default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class This is a beta feature as of Kubernetes v1.14.", + "type": "string" + }, + "schedulerName": { + "description": "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.", + "type": "string" + }, + "securityContext": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodSecurityContext", + "description": "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field." + }, + "serviceAccount": { + "description": "DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.", + "type": "string" + }, + "serviceAccountName": { + "description": "ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + "type": "string" + }, + "shareProcessNamespace": { + "description": "Share a single process namespace between all of the containers in a pod. When this is set containers will be able to view and signal processes from other containers in the same pod, and the first process in each container will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both be set. Optional: Default to false.", + "type": "boolean" + }, + "subdomain": { + "description": "If specified, the fully qualified Pod hostname will be \"...svc.\". If not specified, the pod will not have a domainname at all.", + "type": "string" + }, + "terminationGracePeriodSeconds": { + "description": "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.", + "format": "int64", + "type": "integer" + }, + "tolerations": { + "description": "If specified, the pod's tolerations.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.Toleration" + }, + "type": "array" + }, + "topologySpreadConstraints": { + "description": "TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. All topologySpreadConstraints are ANDed.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.TopologySpreadConstraint" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "topologyKey", + "whenUnsatisfiable" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "topologyKey", + "x-kubernetes-patch-strategy": "merge" + }, + "volumes": { + "description": "List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.Volume" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + } + }, + "required": [ + "containers" + ], + "type": "object" + }, + "io.k8s.api.core.v1.PodStatus": { + "description": "PodStatus represents information about the status of a pod. Status may trail the actual state of a system, especially if the node that hosts the pod cannot contact the control plane.", + "properties": { + "conditions": { + "description": "Current service state of pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodCondition" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "containerStatuses": { + "description": "The list has one entry per container in the manifest. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStatus" + }, + "type": "array" + }, + "ephemeralContainerStatuses": { + "description": "Status for any ephemeral containers that have run in this pod.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStatus" + }, + "type": "array" + }, + "hostIP": { + "description": "IP address of the host to which the pod is assigned. Empty if not yet scheduled.", + "type": "string" + }, + "initContainerStatuses": { + "description": "The list has one entry per init container in the manifest. The most recent successful init container will have ready = true, the most recently started container will have startTime set. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.ContainerStatus" + }, + "type": "array" + }, + "message": { + "description": "A human readable message indicating details about why the pod is in this condition.", + "type": "string" + }, + "nominatedNodeName": { + "description": "nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be scheduled right away as preemption victims receive their graceful termination periods. This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to give the resources on this node to a higher priority pod that is created after preemption. As a result, this field may be different than PodSpec.nodeName when the pod is scheduled.", + "type": "string" + }, + "phase": { + "description": "The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. The conditions array, the reason and message fields, and the individual container status arrays contain more detail about the pod's status. There are five possible phase values:\n\nPending: The pod has been accepted by the Kubernetes system, but one or more of the container images has not been created. This includes time before being scheduled as well as time spent downloading images over the network, which could take a while. Running: The pod has been bound to a node, and all of the containers have been created. At least one container is still running, or is in the process of starting or restarting. Succeeded: All containers in the pod have terminated in success, and will not be restarted. Failed: All containers in the pod have terminated, and at least one container has terminated in failure. The container either exited with non-zero status or was terminated by the system. Unknown: For some reason the state of the pod could not be obtained, typically due to an error in communicating with the host of the pod.\n\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase", + "type": "string" + }, + "podIP": { + "description": "IP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.", + "type": "string" + }, + "podIPs": { + "description": "podIPs holds the IP addresses allocated to the pod. If this field is specified, the 0th entry must match the podIP field. Pods may be allocated at most 1 value for each of IPv4 and IPv6. This list is empty if no IPs have been allocated yet.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodIP" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "ip", + "x-kubernetes-patch-strategy": "merge" + }, + "qosClass": { + "description": "The Quality of Service (QOS) classification assigned to the pod based on resource requirements See PodQOSClass type for available QOS classes More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#quality-of-service-classes", + "type": "string" + }, + "reason": { + "description": "A brief CamelCase message indicating details about why the pod is in this state. e.g. 'Evicted'", + "type": "string" + }, + "startTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "RFC 3339 date and time at which the object was acknowledged by the Kubelet. This is before the Kubelet pulled the container image(s) for the pod." + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PodTemplate": { + "description": "PodTemplate describes a template for creating copies of a predefined pod.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "template": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", + "description": "Template defines the pods that will be created from this pod template. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "", + "kind": "PodTemplate", + "version": "v1" + } + ] + }, + "io.k8s.api.core.v1.PodTemplateSpec": { + "description": "PodTemplateSpec describes the data a pod should have when created from a template", + "properties": { + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodSpec", + "description": "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.PortworxVolumeSource": { + "description": "PortworxVolumeSource represents a Portworx volume resource.", + "properties": { + "fsType": { + "description": "FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified.", + "type": "string" + }, + "readOnly": { + "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + }, + "volumeID": { + "description": "VolumeID uniquely identifies a Portworx volume", + "type": "string" + } + }, + "required": [ + "volumeID" + ], + "type": "object" + }, + "io.k8s.api.core.v1.PreferredSchedulingTerm": { + "description": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", + "properties": { + "preference": { + "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelectorTerm", + "description": "A node selector term, associated with the corresponding weight." + }, + "weight": { + "description": "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "weight", + "preference" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Probe": { + "description": "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", + "properties": { + "exec": { + "$ref": "#/definitions/io.k8s.api.core.v1.ExecAction", + "description": "One and only one of the following should be specified. Exec specifies the action to take." + }, + "failureThreshold": { + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "httpGet": { + "$ref": "#/definitions/io.k8s.api.core.v1.HTTPGetAction", + "description": "HTTPGet specifies the http request to perform." + }, + "initialDelaySeconds": { + "description": "Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "description": "How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "description": "Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "$ref": "#/definitions/io.k8s.api.core.v1.TCPSocketAction", + "description": "TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported" + }, + "timeoutSeconds": { + "description": "Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ProjectedVolumeSource": { + "description": "Represents a projected volume source", + "properties": { + "defaultMode": { + "description": "Mode bits to use on created files by default. Must be a value between 0 and 0777. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + "format": "int32", + "type": "integer" + }, + "sources": { + "description": "list of volume projections", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.VolumeProjection" + }, + "type": "array" + } + }, + "required": [ + "sources" + ], + "type": "object" + }, + "io.k8s.api.core.v1.QuobyteVolumeSource": { + "description": "Represents a Quobyte mount that lasts the lifetime of a pod. Quobyte volumes do not support ownership management or SELinux relabeling.", + "properties": { + "group": { + "description": "Group to map volume access to Default is no group", + "type": "string" + }, + "readOnly": { + "description": "ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false.", + "type": "boolean" + }, + "registry": { + "description": "Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes", + "type": "string" + }, + "tenant": { + "description": "Tenant owning the given Quobyte volume in the Backend Used with dynamically provisioned Quobyte volumes, value is set by the plugin", + "type": "string" + }, + "user": { + "description": "User to map volume access to Defaults to serivceaccount user", + "type": "string" + }, + "volume": { + "description": "Volume is a string that references an already created Quobyte volume by name.", + "type": "string" + } + }, + "required": [ + "registry", + "volume" + ], + "type": "object" + }, + "io.k8s.api.core.v1.RBDVolumeSource": { + "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", + "properties": { + "fsType": { + "description": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd", + "type": "string" + }, + "image": { + "description": "The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + "type": "string" + }, + "keyring": { + "description": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + "type": "string" + }, + "monitors": { + "description": "A collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + "items": { + "type": "string" + }, + "type": "array" + }, + "pool": { + "description": "The rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + "type": "string" + }, + "readOnly": { + "description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + "type": "boolean" + }, + "secretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", + "description": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it" + }, + "user": { + "description": "The rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + "type": "string" + } + }, + "required": [ + "monitors", + "image" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ResourceFieldSelector": { + "description": "ResourceFieldSelector represents container resources (cpu, memory) and their output format", + "properties": { + "containerName": { + "description": "Container name: required for volumes, optional for env vars", + "type": "string" + }, + "divisor": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity", + "description": "Specifies the output format of the exposed resources, defaults to \"1\"" + }, + "resource": { + "description": "Required: resource to select", + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ResourceRequirements": { + "description": "ResourceRequirements describes the compute resource requirements.", + "properties": { + "limits": { + "additionalProperties": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" + }, + "description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", + "type": "object" + }, + "requests": { + "additionalProperties": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity" + }, + "description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", + "type": "object" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.SELinuxOptions": { + "description": "SELinuxOptions are the labels to be applied to the container", + "properties": { + "level": { + "description": "Level is SELinux level label that applies to the container.", + "type": "string" + }, + "role": { + "description": "Role is a SELinux role label that applies to the container.", + "type": "string" + }, + "type": { + "description": "Type is a SELinux type label that applies to the container.", + "type": "string" + }, + "user": { + "description": "User is a SELinux user label that applies to the container.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ScaleIOVolumeSource": { + "description": "ScaleIOVolumeSource represents a persistent ScaleIO volume", + "properties": { + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Default is \"xfs\".", + "type": "string" + }, + "gateway": { + "description": "The host address of the ScaleIO API Gateway.", + "type": "string" + }, + "protectionDomain": { + "description": "The name of the ScaleIO Protection Domain for the configured storage.", + "type": "string" + }, + "readOnly": { + "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + }, + "secretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", + "description": "SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail." + }, + "sslEnabled": { + "description": "Flag to enable/disable SSL communication with Gateway, default false", + "type": "boolean" + }, + "storageMode": { + "description": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned.", + "type": "string" + }, + "storagePool": { + "description": "The ScaleIO Storage Pool associated with the protection domain.", + "type": "string" + }, + "system": { + "description": "The name of the storage system as configured in ScaleIO.", + "type": "string" + }, + "volumeName": { + "description": "The name of a volume already created in the ScaleIO system that is associated with this volume source.", + "type": "string" + } + }, + "required": [ + "gateway", + "system", + "secretRef" + ], + "type": "object" + }, + "io.k8s.api.core.v1.ScopeSelector": { + "description": "A scope selector represents the AND of the selectors represented by the scoped-resource selector requirements.", + "properties": { + "matchExpressions": { + "description": "A list of scope selector requirements by scope of the resources.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.ScopedResourceSelectorRequirement" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ScopedResourceSelectorRequirement": { + "description": "A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.", + "properties": { + "operator": { + "description": "Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.", + "type": "string" + }, + "scopeName": { + "description": "The name of the scope that the selector applies to.", + "type": "string" + }, + "values": { + "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "scopeName", + "operator" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Secret": { + "description": "Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "data": { + "additionalProperties": { + "format": "byte", + "type": "string" + }, + "description": "Data contains the secret data. Each key must consist of alphanumeric characters, '-', '_' or '.'. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4", + "type": "object" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "stringData": { + "additionalProperties": { + "type": "string" + }, + "description": "stringData allows specifying non-binary secret data in string form. It is provided as a write-only input field for convenience. All keys and values are merged into the data field on write, overwriting any existing values. The stringData field is never output when reading from the API.", + "type": "object" + }, + "type": { + "description": "Used to facilitate programmatic handling of secret data.", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "", + "kind": "Secret", + "version": "v1" + } + ] + }, + "io.k8s.api.core.v1.SecretEnvSource": { + "description": "SecretEnvSource selects a Secret to populate the environment variables with.\n\nThe contents of the target Secret's Data field will represent the key-value pairs as environment variables.", + "properties": { + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the Secret must be defined", + "type": "boolean" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.SecretKeySelector": { + "description": "SecretKeySelector selects a key of a Secret.", + "properties": { + "key": { + "description": "The key of the secret to select from. Must be a valid secret key.", + "type": "string" + }, + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the Secret or its key must be defined", + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "io.k8s.api.core.v1.SecretProjection": { + "description": "Adapts a secret into a projected volume.\n\nThe contents of the target Secret's Data field will be presented in a projected volume as files using the keys in the Data field as the file names. Note that this is identical to a secret volume source without the default mode.", + "properties": { + "items": { + "description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" + }, + "type": "array" + }, + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "optional": { + "description": "Specify whether the Secret or its key must be defined", + "type": "boolean" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.SecretReference": { + "description": "SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace", + "properties": { + "name": { + "description": "Name is unique within a namespace to reference a secret resource.", + "type": "string" + }, + "namespace": { + "description": "Namespace defines the space within which the secret name must be unique.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.SecretVolumeSource": { + "description": "Adapts a Secret into a volume.\n\nThe contents of the target Secret's Data field will be presented in a volume as files using the keys in the Data field as the file names. Secret volumes support ownership management and SELinux relabeling.", + "properties": { + "defaultMode": { + "description": "Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + "format": "int32", + "type": "integer" + }, + "items": { + "description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.KeyToPath" + }, + "type": "array" + }, + "optional": { + "description": "Specify whether the Secret or its keys must be defined", + "type": "boolean" + }, + "secretName": { + "description": "Name of the secret in the pod's namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.SecurityContext": { + "description": "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", + "properties": { + "allowPrivilegeEscalation": { + "description": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN", + "type": "boolean" + }, + "capabilities": { + "$ref": "#/definitions/io.k8s.api.core.v1.Capabilities", + "description": "The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime." + }, + "privileged": { + "description": "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.", + "type": "boolean" + }, + "procMount": { + "description": "procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled.", + "type": "string" + }, + "readOnlyRootFilesystem": { + "description": "Whether this container has a read-only root filesystem. Default is false.", + "type": "boolean" + }, + "runAsGroup": { + "description": "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "description": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "type": "boolean" + }, + "runAsUser": { + "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "$ref": "#/definitions/io.k8s.api.core.v1.SELinuxOptions", + "description": "The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." + }, + "windowsOptions": { + "$ref": "#/definitions/io.k8s.api.core.v1.WindowsSecurityContextOptions", + "description": "The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence." + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.ServiceAccountTokenProjection": { + "description": "ServiceAccountTokenProjection represents a projected service account token volume. This projection can be used to insert a service account token into the pods runtime filesystem for use against APIs (Kubernetes API Server or otherwise).", + "properties": { + "audience": { + "description": "Audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver.", + "type": "string" + }, + "expirationSeconds": { + "description": "ExpirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes.", + "format": "int64", + "type": "integer" + }, + "path": { + "description": "Path is the path relative to the mount point of the file to project the token into.", + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "io.k8s.api.core.v1.SessionAffinityConfig": { + "description": "SessionAffinityConfig represents the configurations of session affinity.", + "properties": { + "clientIP": { + "$ref": "#/definitions/io.k8s.api.core.v1.ClientIPConfig", + "description": "clientIP contains the configurations of Client IP based session affinity." + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.StorageOSVolumeSource": { + "description": "Represents a StorageOS persistent volume resource.", + "properties": { + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + "type": "string" + }, + "readOnly": { + "description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + "type": "boolean" + }, + "secretRef": { + "$ref": "#/definitions/io.k8s.api.core.v1.LocalObjectReference", + "description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted." + }, + "volumeName": { + "description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.", + "type": "string" + }, + "volumeNamespace": { + "description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.Sysctl": { + "description": "Sysctl defines a kernel parameter to be set", + "properties": { + "name": { + "description": "Name of a property to set", + "type": "string" + }, + "value": { + "description": "Value of a property to set", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "io.k8s.api.core.v1.TCPSocketAction": { + "description": "TCPSocketAction describes an action based on opening a socket", + "properties": { + "host": { + "description": "Optional: Host name to connect to, defaults to the pod IP.", + "type": "string" + }, + "port": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME." + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Toleration": { + "description": "The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator .", + "properties": { + "effect": { + "description": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.", + "type": "string" + }, + "key": { + "description": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys.", + "type": "string" + }, + "operator": { + "description": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.", + "type": "string" + }, + "tolerationSeconds": { + "description": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.", + "format": "int64", + "type": "integer" + }, + "value": { + "description": "Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.TopologySelectorLabelRequirement": { + "description": "A topology selector requirement is a selector that matches given label. This is an alpha feature and may change in the future.", + "properties": { + "key": { + "description": "The label key that the selector applies to.", + "type": "string" + }, + "values": { + "description": "An array of string values. One value must match the label to be selected. Each entry in Values is ORed.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "values" + ], + "type": "object" + }, + "io.k8s.api.core.v1.TopologySelectorTerm": { + "description": "A topology selector term represents the result of label queries. A null or empty topology selector term matches no objects. The requirements of them are ANDed. It provides a subset of functionality as NodeSelectorTerm. This is an alpha feature and may change in the future.", + "properties": { + "matchLabelExpressions": { + "description": "A list of topology selector requirements by labels.", + "items": { + "$ref": "#/definitions/io.k8s.api.core.v1.TopologySelectorLabelRequirement" + }, + "type": "array" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.TopologySpreadConstraint": { + "description": "TopologySpreadConstraint specifies how to spread matching pods among the given topology.", + "properties": { + "labelSelector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain." + }, + "maxSkew": { + "description": "MaxSkew describes the degree to which pods may be unevenly distributed. It's the maximum permitted difference between the number of matching pods in any two topology domains of a given topology type. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: | zone1 | zone2 | zone3 | | P | P | | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 1/1/1; scheduling it onto zone1(zone2) would make the ActualSkew(2-0) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. It's a required field. Default value is 1 and 0 is not allowed.", + "format": "int32", + "type": "integer" + }, + "topologyKey": { + "description": "TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each as a \"bucket\", and try to put balanced number of pods into each bucket. It's a required field.", + "type": "string" + }, + "whenUnsatisfiable": { + "description": "WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy the spread constraint. - DoNotSchedule (default) tells the scheduler not to schedule it - ScheduleAnyway tells the scheduler to still schedule it It's considered as \"Unsatisfiable\" if and only if placing incoming pod on any topology violates \"MaxSkew\". For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler won't make it *more* imbalanced. It's a required field.", + "type": "string" + } + }, + "required": [ + "maxSkew", + "topologyKey", + "whenUnsatisfiable" + ], + "type": "object" + }, + "io.k8s.api.core.v1.Volume": { + "description": "Volume represents a named volume in a pod that may be accessed by any container in the pod.", + "properties": { + "awsElasticBlockStore": { + "$ref": "#/definitions/io.k8s.api.core.v1.AWSElasticBlockStoreVolumeSource", + "description": "AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore" + }, + "azureDisk": { + "$ref": "#/definitions/io.k8s.api.core.v1.AzureDiskVolumeSource", + "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod." + }, + "azureFile": { + "$ref": "#/definitions/io.k8s.api.core.v1.AzureFileVolumeSource", + "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod." + }, + "cephfs": { + "$ref": "#/definitions/io.k8s.api.core.v1.CephFSVolumeSource", + "description": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime" + }, + "cinder": { + "$ref": "#/definitions/io.k8s.api.core.v1.CinderVolumeSource", + "description": "Cinder represents a cinder volume attached and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md" + }, + "configMap": { + "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapVolumeSource", + "description": "ConfigMap represents a configMap that should populate this volume" + }, + "csi": { + "$ref": "#/definitions/io.k8s.api.core.v1.CSIVolumeSource", + "description": "CSI (Container Storage Interface) represents storage that is handled by an external CSI driver (Alpha feature)." + }, + "downwardAPI": { + "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIVolumeSource", + "description": "DownwardAPI represents downward API about the pod that should populate this volume" + }, + "emptyDir": { + "$ref": "#/definitions/io.k8s.api.core.v1.EmptyDirVolumeSource", + "description": "EmptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir" + }, + "fc": { + "$ref": "#/definitions/io.k8s.api.core.v1.FCVolumeSource", + "description": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod." + }, + "flexVolume": { + "$ref": "#/definitions/io.k8s.api.core.v1.FlexVolumeSource", + "description": "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin." + }, + "flocker": { + "$ref": "#/definitions/io.k8s.api.core.v1.FlockerVolumeSource", + "description": "Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running" + }, + "gcePersistentDisk": { + "$ref": "#/definitions/io.k8s.api.core.v1.GCEPersistentDiskVolumeSource", + "description": "GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" + }, + "gitRepo": { + "$ref": "#/definitions/io.k8s.api.core.v1.GitRepoVolumeSource", + "description": "GitRepo represents a git repository at a particular revision. DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container." + }, + "glusterfs": { + "$ref": "#/definitions/io.k8s.api.core.v1.GlusterfsVolumeSource", + "description": "Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md" + }, + "hostPath": { + "$ref": "#/definitions/io.k8s.api.core.v1.HostPathVolumeSource", + "description": "HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath" + }, + "iscsi": { + "$ref": "#/definitions/io.k8s.api.core.v1.ISCSIVolumeSource", + "description": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md" + }, + "name": { + "description": "Volume's name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + }, + "nfs": { + "$ref": "#/definitions/io.k8s.api.core.v1.NFSVolumeSource", + "description": "NFS represents an NFS mount on the host that shares a pod's lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" + }, + "persistentVolumeClaim": { + "$ref": "#/definitions/io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource", + "description": "PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims" + }, + "photonPersistentDisk": { + "$ref": "#/definitions/io.k8s.api.core.v1.PhotonPersistentDiskVolumeSource", + "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" + }, + "portworxVolume": { + "$ref": "#/definitions/io.k8s.api.core.v1.PortworxVolumeSource", + "description": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine" + }, + "projected": { + "$ref": "#/definitions/io.k8s.api.core.v1.ProjectedVolumeSource", + "description": "Items for all in one resources secrets, configmaps, and downward API" + }, + "quobyte": { + "$ref": "#/definitions/io.k8s.api.core.v1.QuobyteVolumeSource", + "description": "Quobyte represents a Quobyte mount on the host that shares a pod's lifetime" + }, + "rbd": { + "$ref": "#/definitions/io.k8s.api.core.v1.RBDVolumeSource", + "description": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md" + }, + "scaleIO": { + "$ref": "#/definitions/io.k8s.api.core.v1.ScaleIOVolumeSource", + "description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes." + }, + "secret": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecretVolumeSource", + "description": "Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret" + }, + "storageos": { + "$ref": "#/definitions/io.k8s.api.core.v1.StorageOSVolumeSource", + "description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes." + }, + "vsphereVolume": { + "$ref": "#/definitions/io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource", + "description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "io.k8s.api.core.v1.VolumeDevice": { + "description": "volumeDevice describes a mapping of a raw block device within a container.", + "properties": { + "devicePath": { + "description": "devicePath is the path inside of the container that the device will be mapped to.", + "type": "string" + }, + "name": { + "description": "name must match the name of a persistentVolumeClaim in the pod", + "type": "string" + } + }, + "required": [ + "name", + "devicePath" + ], + "type": "object" + }, + "io.k8s.api.core.v1.VolumeMount": { + "description": "VolumeMount describes a mounting of a Volume within a container.", + "properties": { + "mountPath": { + "description": "Path within the container at which the volume should be mounted. Must not contain ':'.", + "type": "string" + }, + "mountPropagation": { + "description": "mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10.", + "type": "string" + }, + "name": { + "description": "This must match the Name of a Volume.", + "type": "string" + }, + "readOnly": { + "description": "Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.", + "type": "boolean" + }, + "subPath": { + "description": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root).", + "type": "string" + }, + "subPathExpr": { + "description": "Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to \"\" (volume's root). SubPathExpr and SubPath are mutually exclusive.", + "type": "string" + } + }, + "required": [ + "name", + "mountPath" + ], + "type": "object" + }, + "io.k8s.api.core.v1.VolumeNodeAffinity": { + "description": "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.", + "properties": { + "required": { + "$ref": "#/definitions/io.k8s.api.core.v1.NodeSelector", + "description": "Required specifies hard node constraints that must be met." + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.VolumeProjection": { + "description": "Projection that may be projected along with other supported volume types", + "properties": { + "configMap": { + "$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapProjection", + "description": "information about the configMap data to project" + }, + "downwardAPI": { + "$ref": "#/definitions/io.k8s.api.core.v1.DownwardAPIProjection", + "description": "information about the downwardAPI data to project" + }, + "secret": { + "$ref": "#/definitions/io.k8s.api.core.v1.SecretProjection", + "description": "information about the secret data to project" + }, + "serviceAccountToken": { + "$ref": "#/definitions/io.k8s.api.core.v1.ServiceAccountTokenProjection", + "description": "information about the serviceAccountToken data to project" + } + }, + "type": "object" + }, + "io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource": { + "description": "Represents a vSphere volume resource.", + "properties": { + "fsType": { + "description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + "type": "string" + }, + "storagePolicyID": { + "description": "Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.", + "type": "string" + }, + "storagePolicyName": { + "description": "Storage Policy Based Management (SPBM) profile name.", + "type": "string" + }, + "volumePath": { + "description": "Path that identifies vSphere volume vmdk", + "type": "string" + } + }, + "required": [ + "volumePath" + ], + "type": "object" + }, + "io.k8s.api.core.v1.WeightedPodAffinityTerm": { + "description": "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)", + "properties": { + "podAffinityTerm": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodAffinityTerm", + "description": "Required. A pod affinity term, associated with the corresponding weight." + }, + "weight": { + "description": "weight associated with matching the corresponding podAffinityTerm, in the range 1-100.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "weight", + "podAffinityTerm" + ], + "type": "object" + }, + "io.k8s.api.core.v1.WindowsSecurityContextOptions": { + "description": "WindowsSecurityContextOptions contain Windows-specific options and credentials.", + "properties": { + "gmsaCredentialSpec": { + "description": "GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. This field is alpha-level and is only honored by servers that enable the WindowsGMSA feature flag.", + "type": "string" + }, + "gmsaCredentialSpecName": { + "description": "GMSACredentialSpecName is the name of the GMSA credential spec to use. This field is alpha-level and is only honored by servers that enable the WindowsGMSA feature flag.", + "type": "string" + }, + "runAsUserName": { + "description": "The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. This field is beta-level and may be disabled with the WindowsRunAsUserName feature flag.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.extensions.v1beta1.Deployment": { + "description": "DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for more information. Deployment enables declarative updates for Pods and ReplicaSets.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", + "description": "Standard object metadata." + }, + "spec": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentSpec", + "description": "Specification of the desired behavior of the Deployment." + }, + "status": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentStatus", + "description": "Most recently observed status of the Deployment." + } + }, + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "extensions", + "kind": "Deployment", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.extensions.v1beta1.DeploymentCondition": { + "description": "DeploymentCondition describes the state of a deployment at a certain point.", + "properties": { + "lastTransitionTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Last time the condition transitioned from one status to another." + }, + "lastUpdateTime": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "The last time this condition was updated." + }, + "message": { + "description": "A human readable message indicating details about the transition.", + "type": "string" + }, + "reason": { + "description": "The reason for the condition's last transition.", + "type": "string" + }, + "status": { + "description": "Status of the condition, one of True, False, Unknown.", + "type": "string" + }, + "type": { + "description": "Type of deployment condition.", + "type": "string" + } + }, + "required": [ + "type", + "status" + ], + "type": "object" + }, + "io.k8s.api.extensions.v1beta1.DeploymentRollback": { + "description": "DEPRECATED. DeploymentRollback stores the information required to rollback a deployment.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "name": { + "description": "Required: This must match the Name of a deployment.", + "type": "string" + }, + "rollbackTo": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollbackConfig", + "description": "The config of this deployment rollback." + }, + "updatedAnnotations": { + "additionalProperties": { + "type": "string" + }, + "description": "The annotations to be updated to a deployment", + "type": "object" + } + }, + "required": [ + "name", + "rollbackTo" + ], + "type": "object", + "x-kubernetes-group-version-kind": [ + { + "group": "extensions", + "kind": "DeploymentRollback", + "version": "v1beta1" + } + ] + }, + "io.k8s.api.extensions.v1beta1.DeploymentSpec": { + "description": "DeploymentSpec is the specification of the desired behavior of the Deployment.", + "properties": { + "minReadySeconds": { + "description": "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", + "format": "int32", + "type": "integer" + }, + "paused": { + "description": "Indicates that the deployment is paused and will not be processed by the deployment controller.", + "type": "boolean" + }, + "progressDeadlineSeconds": { + "description": "The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. This is set to the max value of int32 (i.e. 2147483647) by default, which means \"no deadline\".", + "format": "int32", + "type": "integer" + }, + "replicas": { + "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.", + "format": "int32", + "type": "integer" + }, + "revisionHistoryLimit": { + "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. This is set to the max value of int32 (i.e. 2147483647) by default, which means \"retaining all old ReplicaSets\".", + "format": "int32", + "type": "integer" + }, + "rollbackTo": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollbackConfig", + "description": "DEPRECATED. The config this deployment is rolling back to. Will be cleared after rollback is done." + }, + "selector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment." + }, + "strategy": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentStrategy", + "description": "The deployment strategy to use to replace existing pods with new ones.", + "x-kubernetes-patch-strategy": "retainKeys" + }, + "template": { + "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec", + "description": "Template describes the pods that will be created." + } + }, + "required": [ + "template" + ], + "type": "object" + }, + "io.k8s.api.extensions.v1beta1.DeploymentStatus": { + "description": "DeploymentStatus is the most recently observed status of the Deployment.", + "properties": { + "availableReplicas": { + "description": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", + "format": "int32", + "type": "integer" + }, + "collisionCount": { + "description": "Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.", + "format": "int32", + "type": "integer" + }, + "conditions": { + "description": "Represents the latest available observations of a deployment's current state.", + "items": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.DeploymentCondition" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge" + }, + "observedGeneration": { + "description": "The generation observed by the deployment controller.", + "format": "int64", + "type": "integer" + }, + "readyReplicas": { + "description": "Total number of ready pods targeted by this deployment.", + "format": "int32", + "type": "integer" + }, + "replicas": { + "description": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", + "format": "int32", + "type": "integer" + }, + "unavailableReplicas": { + "description": "Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.", + "format": "int32", + "type": "integer" + }, + "updatedReplicas": { + "description": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "io.k8s.api.extensions.v1beta1.DeploymentStrategy": { + "description": "DeploymentStrategy describes how to replace existing pods with new ones.", + "properties": { + "rollingUpdate": { + "$ref": "#/definitions/io.k8s.api.extensions.v1beta1.RollingUpdateDeployment", + "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate." + }, + "type": { + "description": "Type of deployment. Can be \"Recreate\" or \"RollingUpdate\". Default is RollingUpdate.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.api.extensions.v1beta1.RollbackConfig": { + "description": "DEPRECATED.", + "properties": { + "revision": { + "description": "The revision to rollback to. If set to 0, rollback to the last revision.", + "format": "int64", + "type": "integer" + } + }, + "type": "object" + }, + "io.k8s.api.extensions.v1beta1.RollingUpdateDeployment": { + "description": "Spec to control the desired behavior of rolling update.", + "properties": { + "maxSurge": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. By default, a value of 1 is used. Example: when this is set to 30%, the new RC can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods." + }, + "maxUnavailable": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString", + "description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. By default, a fixed value of 1 is used. Example: when this is set to 30%, the old RC can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods." + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.api.resource.Quantity": { + "description": "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n ::= \n (Note that may be empty, from the \"\" case in .)\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n ::= m | \"\" | k | M | G | T | P | E\n (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n ::= \"e\" | \"E\" \n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n a. No precision is lost\n b. No fractional digits will be emitted\n c. The exponent (or suffix) is as large as possible.\nThe sign will be omitted unless the number is negative.\n\nExamples:\n 1.5 will be serialized as \"1500m\"\n 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", + "type": "string" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { + "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string", + "x-kubernetes-patch-merge-key": "key", + "x-kubernetes-patch-strategy": "merge" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { + "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + "properties": { + "continue": { + "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", + "type": "string" + }, + "remainingItemCount": { + "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + "format": "int64", + "type": "integer" + }, + "resourceVersion": { + "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" + }, + "selfLink": { + "description": "selfLink is a URL representing this object. Populated by the system. Read-only.\n\nDEPRECATED Kubernetes will stop propagating this field in 1.20 release and the field is planned to be removed in 1.21 release.", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { + "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type": "string" + }, + "fieldsType": { + "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + "type": "string" + }, + "fieldsV1": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1", + "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type." + }, + "manager": { + "description": "Manager is an identifier of the workflow managing these fields.", + "type": "string" + }, + "operation": { + "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type": "string" + }, + "time": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply'" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { + "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", + "type": "object" + }, + "clusterName": { + "description": "The name of the cluster which the object belongs to. This is used to distinguish resources with same name and namespace in different clusters. This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.", + "type": "string" + }, + "creationTimestamp": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "deletionGracePeriodSeconds": { + "description": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", + "format": "int64", + "type": "integer" + }, + "deletionTimestamp": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time", + "description": "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" + }, + "finalizers": { + "description": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-patch-strategy": "merge" + }, + "generateName": { + "description": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", + "type": "string" + }, + "generation": { + "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", + "format": "int64", + "type": "integer" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", + "type": "object" + }, + "managedFields": { + "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" + }, + "type": "array" + }, + "name": { + "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + "type": "string" + }, + "namespace": { + "description": "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", + "type": "string" + }, + "ownerReferences": { + "description": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", + "items": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "uid", + "x-kubernetes-patch-strategy": "merge" + }, + "resourceVersion": { + "description": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" + }, + "selfLink": { + "description": "SelfLink is a URL representing this object. Populated by the system. Read-only.\n\nDEPRECATED Kubernetes will stop propagating this field in 1.20 release and the field is planned to be removed in 1.21 release.", + "type": "string" + }, + "uid": { + "description": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { + "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", + "properties": { + "apiVersion": { + "description": "API version of the referent.", + "type": "string" + }, + "blockOwnerDeletion": { + "description": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", + "type": "boolean" + }, + "controller": { + "description": "If true, this reference points to the managing controller.", + "type": "boolean" + }, + "kind": { + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + "type": "string" + }, + "uid": { + "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + }, + "required": [ + "apiVersion", + "kind", + "name", + "uid" + ], + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "format": "date-time", + "type": "string" + }, + "io.k8s.apimachinery.pkg.util.intstr.IntOrString": { + "description": "IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number.", + "format": "int-or-string", + "type": "string" + } + }, + "info": { + "title": "Kubernetes", + "version": "test" + }, + "paths": { + }, + "swagger": "2.0" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/testing/testfieldmanager.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/testing/testfieldmanager.go new file mode 100644 index 0000000000..07558232ab --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/testing/testfieldmanager.go @@ -0,0 +1,167 @@ +/* +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 testing + +import ( + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields" + "k8s.io/apimachinery/pkg/util/managedfields/internal" +) + +// FakeObjectCreater implements ObjectCreater, it can create empty +// objects (unstructured) of the given GVK. +type FakeObjectCreater struct{} + +func (f *FakeObjectCreater) New(gvk schema.GroupVersionKind) (runtime.Object, error) { + u := unstructured.Unstructured{Object: map[string]interface{}{}} + u.SetAPIVersion(gvk.GroupVersion().String()) + u.SetKind(gvk.Kind) + return &u, nil +} + +// FakeObjectConvertor implements runtime.ObjectConvertor but it +// actually does nothing but return its input. +type FakeObjectConvertor struct{} + +//nolint:staticcheck,ineffassign // SA4009 backwards compatibility +func (c *FakeObjectConvertor) Convert(in, out, context interface{}) error { + out = in + return nil +} + +func (c *FakeObjectConvertor) ConvertToVersion(in runtime.Object, _ runtime.GroupVersioner) (runtime.Object, error) { + return in, nil +} + +func (c *FakeObjectConvertor) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) { + return "", "", errors.New("not implemented") +} + +// FakeObjectDefaulter implements runtime.Defaulter, but it actually +// does nothing. +type FakeObjectDefaulter struct{} + +func (d *FakeObjectDefaulter) Default(in runtime.Object) {} + +type TestFieldManagerImpl struct { + fieldManager *internal.FieldManager + apiVersion string + emptyObj runtime.Object + liveObj runtime.Object +} + +// APIVersion of the object that we're tracking. +func (f *TestFieldManagerImpl) APIVersion() string { + return f.apiVersion +} + +// Reset resets the state of the liveObject by resetting it to an empty object. +func (f *TestFieldManagerImpl) Reset() { + f.liveObj = f.emptyObj.DeepCopyObject() +} + +// Live returns a copy of the current liveObject. +func (f *TestFieldManagerImpl) Live() runtime.Object { + return f.liveObj.DeepCopyObject() +} + +// Apply applies the given object on top of the current liveObj, for the +// given manager and force flag. +func (f *TestFieldManagerImpl) Apply(obj runtime.Object, manager string, force bool) error { + out, err := f.fieldManager.Apply(f.liveObj, obj, manager, force) + if err == nil { + f.liveObj = out + } + return err +} + +// Update will updates the managed fields in the liveObj based on the +// changes performed by the update. +func (f *TestFieldManagerImpl) Update(obj runtime.Object, manager string) error { + out, err := f.fieldManager.Update(f.liveObj, obj, manager) + if err == nil { + f.liveObj = out + } + return err +} + +// UpdateNoErrors is the same as Update, but it will not return errors. +func (f *TestFieldManagerImpl) UpdateNoErrors(obj runtime.Object, manager string) { + f.liveObj = f.fieldManager.UpdateNoErrors(f.liveObj, obj, manager) +} + +// ManagedFields returns the list of existing managed fields for the +// liveObj. +func (f *TestFieldManagerImpl) ManagedFields() []metav1.ManagedFieldsEntry { + accessor, err := meta.Accessor(f.liveObj) + if err != nil { + panic(fmt.Errorf("couldn't get accessor: %v", err)) + } + + return accessor.GetManagedFields() +} + +// NewTestFieldManager creates a new manager for the given GVK. +func NewTestFieldManagerImpl(typeConverter managedfields.TypeConverter, gvk schema.GroupVersionKind, subresource string, chainFieldManager func(internal.Manager) internal.Manager) *TestFieldManagerImpl { + f, err := internal.NewStructuredMergeManager( + typeConverter, + &FakeObjectConvertor{}, + &FakeObjectDefaulter{}, + gvk.GroupVersion(), + gvk.GroupVersion(), + nil, + ) + if err != nil { + panic(err) + } + live := &unstructured.Unstructured{} + live.SetKind(gvk.Kind) + live.SetAPIVersion(gvk.GroupVersion().String()) + // This is different from `internal.NewDefaultFieldManager` because: + // 1. We don't want to create a `internal.FieldManager` + // 2. We don't want to use the CapManager that is tested separately with + // a smaller than the default cap. + f = internal.NewVersionCheckManager( + internal.NewLastAppliedUpdater( + internal.NewLastAppliedManager( + internal.NewProbabilisticSkipNonAppliedManager( + internal.NewBuildManagerInfoManager( + internal.NewManagedFieldsUpdater( + internal.NewStripMetaManager(f), + ), gvk.GroupVersion(), subresource, + ), &FakeObjectCreater{}, internal.DefaultTrackOnCreateProbability, + ), typeConverter, &FakeObjectConvertor{}, gvk.GroupVersion(), + ), + ), gvk, + ) + if chainFieldManager != nil { + f = chainFieldManager(f) + } + return &TestFieldManagerImpl{ + fieldManager: internal.NewFieldManager(f, subresource), + apiVersion: gvk.GroupVersion().String(), + emptyObj: live, + liveObj: live.DeepCopyObject(), + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/typeconverter.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/typeconverter.go new file mode 100644 index 0000000000..40cc90da78 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/typeconverter.go @@ -0,0 +1,211 @@ +/* +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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/schemaconv" + "k8s.io/kube-openapi/pkg/validation/spec" + smdschema "sigs.k8s.io/structured-merge-diff/v6/schema" + "sigs.k8s.io/structured-merge-diff/v6/typed" + "sigs.k8s.io/structured-merge-diff/v6/value" +) + +// TypeConverter allows you to convert from runtime.Object to +// typed.TypedValue and the other way around. +type TypeConverter interface { + ObjectToTyped(runtime.Object, ...typed.ValidationOptions) (*typed.TypedValue, error) + TypedToObject(*typed.TypedValue) (runtime.Object, error) +} + +type typeConverter struct { + parser map[schema.GroupVersionKind]*typed.ParseableType +} + +var _ TypeConverter = &typeConverter{} + +func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) { + typeSchema, err := schemaconv.ToSchemaFromOpenAPI(openapiSpec, preserveUnknownFields) + if err != nil { + return nil, fmt.Errorf("failed to convert models to schema: %v", err) + } + + typeParser := typed.Parser{Schema: smdschema.Schema{Types: typeSchema.Types}} + tr := indexModels(&typeParser, openapiSpec) + + return &typeConverter{parser: tr}, nil +} + +func (c *typeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) { + gvk := obj.GetObjectKind().GroupVersionKind() + t := c.parser[gvk] + if t == nil { + return nil, NewNoCorrespondingTypeError(gvk) + } + switch o := obj.(type) { + case *unstructured.Unstructured: + return t.FromUnstructured(o.UnstructuredContent(), opts...) + default: + return t.FromStructured(obj, opts...) + } +} + +func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { + return valueToObject(value.AsValue()) +} + +type deducedTypeConverter struct{} + +// DeducedTypeConverter is a TypeConverter for CRDs that don't have a +// schema. It does implement the same interface though (and create the +// same types of objects), so that everything can still work the same. +// CRDs are merged with all their fields being "atomic" (lists +// included). +func NewDeducedTypeConverter() TypeConverter { + return deducedTypeConverter{} +} + +// ObjectToTyped converts an object into a TypedValue with a "deduced type". +func (deducedTypeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) { + switch o := obj.(type) { + case *unstructured.Unstructured: + return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent(), opts...) + default: + return typed.DeducedParseableType.FromStructured(obj, opts...) + } +} + +// TypedToObject transforms the typed value into a runtime.Object. That +// is not specific to deduced type. +func (deducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { + return valueToObject(value.AsValue()) +} + +func valueToObject(val value.Value) (runtime.Object, error) { + vu := val.Unstructured() + switch o := vu.(type) { + case map[string]interface{}: + return &unstructured.Unstructured{Object: o}, nil + default: + return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu) + } +} + +// GroupVersionOfTypedValue returns the extracted GroupVersion from the TypeMeta +// fields of a TypedValue, or return false if no TypeMeta fields are found. +func GroupVersionOfTypedValue(object *typed.TypedValue) (schema.GroupVersion, bool) { + val := object.AsValue() + if val == nil || !val.IsMap() { + return schema.GroupVersion{}, false + } + apiVersion, ok := val.AsMap().Get("apiVersion") + if !ok || !apiVersion.IsString() { + return schema.GroupVersion{}, false + } + groupVersion, err := schema.ParseGroupVersion(apiVersion.AsString()) + if err != nil { + return schema.GroupVersion{}, false + } + return groupVersion, true +} + +func indexModels( + typeParser *typed.Parser, + openAPISchemas map[string]*spec.Schema, +) map[schema.GroupVersionKind]*typed.ParseableType { + tr := map[schema.GroupVersionKind]*typed.ParseableType{} + for modelName, model := range openAPISchemas { + gvkList := parseGroupVersionKind(model.Extensions) + if len(gvkList) == 0 { + continue + } + + parsedType := typeParser.Type(modelName) + for _, gvk := range gvkList { + if len(gvk.Kind) > 0 { + tr[schema.GroupVersionKind(gvk)] = &parsedType + } + } + } + return tr +} + +// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one. +func parseGroupVersionKind(extensions map[string]interface{}) []schema.GroupVersionKind { + gvkListResult := []schema.GroupVersionKind{} + + // Get the extensions + gvkExtension, ok := extensions["x-kubernetes-group-version-kind"] + if !ok { + return []schema.GroupVersionKind{} + } + + // gvk extension must be a list of at least 1 element. + gvkList, ok := gvkExtension.([]interface{}) + if !ok { + return []schema.GroupVersionKind{} + } + + for _, gvk := range gvkList { + var group, version, kind string + + // gvk extension list must be a map with group, version, and + // kind fields + if gvkMap, ok := gvk.(map[interface{}]interface{}); ok { + group, ok = gvkMap["group"].(string) + if !ok { + continue + } + version, ok = gvkMap["version"].(string) + if !ok { + continue + } + kind, ok = gvkMap["kind"].(string) + if !ok { + continue + } + + } else if gvkMap, ok := gvk.(map[string]interface{}); ok { + group, ok = gvkMap["group"].(string) + if !ok { + continue + } + version, ok = gvkMap["version"].(string) + if !ok { + continue + } + kind, ok = gvkMap["kind"].(string) + if !ok { + continue + } + } else { + continue + } + + gvkListResult = append(gvkListResult, schema.GroupVersionKind{ + Group: group, + Version: version, + Kind: kind, + }) + } + + return gvkListResult +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/typeconverter_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/typeconverter_test.go new file mode 100644 index 0000000000..e1094bf5f8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/typeconverter_test.go @@ -0,0 +1,317 @@ +/* +Copyright 2018 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 internal + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + smdschema "sigs.k8s.io/structured-merge-diff/v6/schema" + "sigs.k8s.io/structured-merge-diff/v6/typed" + "sigs.k8s.io/yaml" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +func TestTypeConverter(t *testing.T) { + dtc := NewDeducedTypeConverter() + + testCases := []struct { + name string + yaml string + }{ + { + name: "apps/v1.Deployment", + yaml: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.4 +`, + }, { + name: "extensions/v1beta1.Deployment", + yaml: ` +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.4 +`, + }, { + name: "v1.Pod", + yaml: ` +apiVersion: v1 +kind: Pod +metadata: + name: nginx-pod + labels: + app: nginx +spec: + containers: + - name: nginx + image: nginx:1.15.4 +`, + }, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("%v ObjectToTyped with TypeConverter", testCase.name), func(t *testing.T) { + testObjectToTyped(t, testTypeConverter, testCase.yaml) + }) + t.Run(fmt.Sprintf("%v ObjectToTyped with DeducedTypeConverter", testCase.name), func(t *testing.T) { + testObjectToTyped(t, dtc, testCase.yaml) + }) + } +} + +func testObjectToTyped(t *testing.T, tc TypeConverter, y string) { + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { + t.Fatalf("Failed to parse yaml object: %v", err) + } + typed, err := tc.ObjectToTyped(obj) + if err != nil { + t.Fatalf("Failed to convert object to typed: %v", err) + } + newObj, err := tc.TypedToObject(typed) + if err != nil { + t.Fatalf("Failed to convert typed to object: %v", err) + } + if !reflect.DeepEqual(obj, newObj) { + t.Errorf(`Round-trip failed: +Original object: +%#v +Final object: +%#v`, obj, newObj) + } +} + +var result typed.TypedValue + +func BenchmarkObjectToTyped(b *testing.B) { + y := ` +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.4 +` + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil { + b.Fatalf("Failed to parse yaml object: %v", err) + } + + b.ResetTimer() + b.ReportAllocs() + + var r *typed.TypedValue + for i := 0; i < b.N; i++ { + var err error + r, err = testTypeConverter.ObjectToTyped(obj) + if err != nil { + b.Fatalf("Failed to convert object to typed: %v", err) + } + } + result = *r +} + +func TestIndexModels(t *testing.T) { + myDefs := map[string]*spec.Schema{ + // Show empty GVK extension is ignored + "def0": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-group-version-kind": []interface{}{}, + }, + }, + }, + // Show nil GVK is ignored + "def0.0": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-group-version-kind": nil, + }, + }, + }, + // Show this is ignored + "def0.1": {}, + // Show allows binding a single GVK + "def1": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-group-version-kind": []interface{}{ + map[string]interface{}{ + "group": "mygroup", + "version": "v1", + "kind": "MyKind", + }, + }, + }, + }, + }, + // Show allows bindings with two versions + "def2": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-group-version-kind": []interface{}{ + map[string]interface{}{ + "group": "mygroup", + "version": "v1", + "kind": "MyOtherKind", + }, + map[string]interface{}{ + "group": "mygroup", + "version": "v2", + "kind": "MyOtherKind", + }, + }, + }, + }, + }, + // Show that we can mix and match GVKs from other definitions, and + // that both map[interface{}]interface{} and map[string]interface{} + // are allowed + "def3": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-group-version-kind": []interface{}{ + map[string]interface{}{ + "group": "mygroup", + "version": "v3", + "kind": "MyKind", + }, + map[interface{}]interface{}{ + "group": "mygroup", + "version": "v3", + "kind": "MyOtherKind", + }, + }, + }, + }, + }, + } + + myTypes := []smdschema.TypeDef{ + { + Name: "def0", + Atom: smdschema.Atom{}, + }, + { + Name: "def0.1", + Atom: smdschema.Atom{}, + }, + { + Name: "def0.2", + Atom: smdschema.Atom{}, + }, + { + Name: "def1", + Atom: smdschema.Atom{}, + }, + { + Name: "def2", + Atom: smdschema.Atom{}, + }, + { + Name: "def3", + Atom: smdschema.Atom{}, + }, + } + + parser := typed.Parser{Schema: smdschema.Schema{Types: myTypes}} + gvkIndex := indexModels(&parser, myDefs) + + require.Len(t, gvkIndex, 5) + + resultNames := map[schema.GroupVersionKind]string{} + for k, v := range gvkIndex { + require.NotNil(t, v.TypeRef.NamedType) + resultNames[k] = *v.TypeRef.NamedType + } + + require.Equal(t, map[schema.GroupVersionKind]string{ + { + Group: "mygroup", + Version: "v1", + Kind: "MyKind", + }: "def1", + { + Group: "mygroup", + Version: "v1", + Kind: "MyOtherKind", + }: "def2", + { + Group: "mygroup", + Version: "v2", + Kind: "MyOtherKind", + }: "def2", + { + Group: "mygroup", + Version: "v3", + Kind: "MyKind", + }: "def3", + { + Group: "mygroup", + Version: "v3", + Kind: "MyOtherKind", + }: "def3", + }, resultNames) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versioncheck.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versioncheck.go new file mode 100644 index 0000000000..ee1e2bca70 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versioncheck.go @@ -0,0 +1,52 @@ +/* +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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type versionCheckManager struct { + fieldManager Manager + gvk schema.GroupVersionKind +} + +var _ Manager = &versionCheckManager{} + +// NewVersionCheckManager creates a manager that makes sure that the +// applied object is in the proper version. +func NewVersionCheckManager(fieldManager Manager, gvk schema.GroupVersionKind) Manager { + return &versionCheckManager{fieldManager: fieldManager, gvk: gvk} +} + +// Update implements Manager. +func (f *versionCheckManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) { + // Nothing to do for updates, this is checked in many other places. + return f.fieldManager.Update(liveObj, newObj, managed, manager) +} + +// Apply implements Manager. +func (f *versionCheckManager) Apply(liveObj, appliedObj runtime.Object, managed Managed, fieldManager string, force bool) (runtime.Object, Managed, error) { + if gvk := appliedObj.GetObjectKind().GroupVersionKind(); gvk != f.gvk { + return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid object type: %v", gvk)) + } + return f.fieldManager.Apply(liveObj, appliedObj, managed, fieldManager, force) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter.go new file mode 100644 index 0000000000..917bdcd75f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter.go @@ -0,0 +1,122 @@ +/* +Copyright 2018 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 internal + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/merge" + "sigs.k8s.io/structured-merge-diff/v6/typed" +) + +// versionConverter is an implementation of +// sigs.k8s.io/structured-merge-diff/merge.Converter +type versionConverter struct { + typeConverter TypeConverter + objectConvertor runtime.ObjectConvertor + hubGetter func(from schema.GroupVersion) schema.GroupVersion +} + +var _ merge.Converter = &versionConverter{} + +// NewVersionConverter builds a VersionConverter from a TypeConverter and an ObjectConvertor. +func newVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter { + return &versionConverter{ + typeConverter: t, + objectConvertor: o, + hubGetter: func(from schema.GroupVersion) schema.GroupVersion { + return schema.GroupVersion{ + Group: from.Group, + Version: h.Version, + } + }, + } +} + +// NewCRDVersionConverter builds a VersionConverter for CRDs from a TypeConverter and an ObjectConvertor. +func newCRDVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter { + return &versionConverter{ + typeConverter: t, + objectConvertor: o, + hubGetter: func(from schema.GroupVersion) schema.GroupVersion { + return h + }, + } +} + +// Convert implements sigs.k8s.io/structured-merge-diff/merge.Converter +func (v *versionConverter) Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) { + groupVersion, err := schema.ParseGroupVersion(string(version)) + if err != nil { + return object, err + } + + // If attempting to convert to the same version as we already have, just return it. + if typedVersion, ok := GroupVersionOfTypedValue(object); ok && typedVersion == groupVersion { + return object, nil + } + + // Convert the smd typed value to a kubernetes object. + objectToConvert, err := v.typeConverter.TypedToObject(object) + if err != nil { + return object, err + } + fromVersion := objectToConvert.GetObjectKind().GroupVersionKind().GroupVersion() + + // Convert to internal + internalObject, err := v.objectConvertor.ConvertToVersion(objectToConvert, v.hubGetter(fromVersion)) + if err != nil { + return object, err + } + + // Convert the object into the target version + convertedObject, err := v.objectConvertor.ConvertToVersion(internalObject, groupVersion) + if err != nil { + return object, err + } + + // Convert the object back to a smd typed value and return it. + return v.typeConverter.ObjectToTyped(convertedObject) +} + +// IsMissingVersionError +func (v *versionConverter) IsMissingVersionError(err error) bool { + return runtime.IsNotRegisteredError(err) || isNoCorrespondingTypeError(err) +} + +type noCorrespondingTypeErr struct { + gvk schema.GroupVersionKind +} + +func NewNoCorrespondingTypeError(gvk schema.GroupVersionKind) error { + return &noCorrespondingTypeErr{gvk: gvk} +} + +func (k *noCorrespondingTypeErr) Error() string { + return fmt.Sprintf("no corresponding type for %v", k.gvk) +} + +func isNoCorrespondingTypeError(err error) bool { + if err == nil { + return false + } + _, ok := err.(*noCorrespondingTypeErr) + return ok +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter_bench_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter_bench_test.go new file mode 100644 index 0000000000..d1dd65fe0f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter_bench_test.go @@ -0,0 +1,197 @@ +/* +Copyright 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 internal + +import ( + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/merge" +) + +// benchDeployment is a structured (reflect-backed) object whose JSON shape is a +// subset of the apps Deployment schema. Built-in types reach the version +// converter as reflect-backed typed values, so the pre-fast-path Convert had to +// deep-copy the whole object via TypedToObject just to read its apiVersion; +// this type reproduces that cost. +type benchDeployment struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` +} + +func (d *benchDeployment) DeepCopyObject() runtime.Object { + out := &benchDeployment{TypeMeta: d.TypeMeta} + d.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + return out +} + +// structuredDeployment returns a reflect-backed Deployment with n labels and n +// annotations, exercising the built-in-type code path. +func structuredDeployment(apiVersion string, n int) runtime.Object { + labels, annotations := metaMaps(n) + return &benchDeployment{ + TypeMeta: metav1.TypeMeta{APIVersion: apiVersion, Kind: "Deployment"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-deployment", + Namespace: "default", + Labels: labels, + Annotations: annotations, + }, + } +} + +// unstructuredDeployment returns a value-backed Deployment, exercising the CRD +// code path (the typed value already holds a map, so TypedToObject is cheap). +func unstructuredDeployment(apiVersion string, n int) runtime.Object { + labels, annotations := metaMaps(n) + return &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": apiVersion, + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "nginx-deployment", + "namespace": "default", + "labels": toInterfaceMap(labels), + "annotations": toInterfaceMap(annotations), + }, + }} +} + +func metaMaps(n int) (labels, annotations map[string]string) { + labels = make(map[string]string, n) + annotations = make(map[string]string, n) + for i := range n { + labels[fmt.Sprintf("k8s.io/label-%d", i)] = fmt.Sprintf("value-%d", i) + annotations[fmt.Sprintf("k8s.io/annotation-%d", i)] = fmt.Sprintf("value-%d", i) + } + return labels, annotations +} + +func toInterfaceMap(m map[string]string) map[string]interface{} { + out := make(map[string]interface{}, len(m)) + for k, v := range m { + out[k] = v + } + return out +} + +func benchVersionConverter() merge.Converter { + oc := fakeObjectConvertorForTestSchema{ + gvkForVersion("v1beta1"): objForGroupVersion("apps/v1beta1"), + gvkForVersion("v1"): objForGroupVersion("apps/v1"), + } + return newVersionConverter(testTypeConverter, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal}) +} + +var benchBackings = []struct { + name string + build func(apiVersion string, n int) runtime.Object +}{ + {"structured", structuredDeployment}, + {"unstructured", unstructuredDeployment}, +} + +var benchConversions = []struct { + name string + version fieldpath.APIVersion +}{ + {"same-version", "apps/v1beta1"}, + {"cross-version", "apps/v1"}, +} + +var benchSizes = []int{0, 10, 100, 1000} + +// BenchmarkVersionConverter measures versionConverter.Convert across reflect-backed +// (built-in) and value-backed (CRD) inputs, for the same-version (fast path) and +// cross-version (fallback) cases, over a range of object sizes. +func BenchmarkVersionConverter(b *testing.B) { + vc := benchVersionConverter() + for _, backing := range benchBackings { + for _, conv := range benchConversions { + for _, n := range benchSizes { + input, err := testTypeConverter.ObjectToTyped(backing.build("apps/v1beta1", n)) + if err != nil { + b.Fatalf("ObjectToTyped(%s, fields=%d): %v", backing.name, n, err) + } + b.Run(fmt.Sprintf("%s/%s/fields=%d", backing.name, conv.name, n), func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := vc.Convert(input, conv.version); err != nil { + b.Fatalf("Convert: %v", err) + } + } + }) + } + } + } +} + +// TestBenchmarkFastPathEngages guards the benchmark: it confirms the same-version +// case takes the fast path (no TypedToObject / ConvertToVersion) for both +// reflect-backed and value-backed inputs, and that the cross-version case does +// not, so the before/after comparison measures the intended code paths. +func TestBenchmarkFastPathEngages(t *testing.T) { + for _, backing := range benchBackings { + t.Run(backing.name, func(t *testing.T) { + input, err := testTypeConverter.ObjectToTyped(backing.build("apps/v1beta1", 10)) + if err != nil { + t.Fatalf("ObjectToTyped: %v", err) + } + + // Same version: fast path, no materialization or conversion. + tc := &countingTypeConverter{TypeConverter: testTypeConverter} + oc := &countingObjectConvertor{ObjectConvertor: benchObjectConvertor()} + vc := newVersionConverter(tc, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal}) + out, err := vc.Convert(input, fieldpath.APIVersion("apps/v1beta1")) + if err != nil { + t.Fatalf("Convert same-version: %v", err) + } + if out != input { + t.Errorf("same-version Convert should return input unchanged") + } + if tc.typedToObjectCalls != 0 { + t.Errorf("same-version Convert called TypedToObject %d times, want 0", tc.typedToObjectCalls) + } + if oc.convertToVersionCalls != 0 { + t.Errorf("same-version Convert called ConvertToVersion %d times, want 0", oc.convertToVersionCalls) + } + + // Cross version: fallback path materializes the object. + tc = &countingTypeConverter{TypeConverter: testTypeConverter} + oc = &countingObjectConvertor{ObjectConvertor: benchObjectConvertor()} + vc = newVersionConverter(tc, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal}) + if _, err := vc.Convert(input, fieldpath.APIVersion("apps/v1")); err != nil { + t.Fatalf("Convert cross-version: %v", err) + } + if tc.typedToObjectCalls == 0 { + t.Errorf("cross-version Convert should call TypedToObject") + } + }) + } +} + +func benchObjectConvertor() fakeObjectConvertorForTestSchema { + return fakeObjectConvertorForTestSchema{ + gvkForVersion("v1beta1"): objForGroupVersion("apps/v1beta1"), + gvkForVersion("v1"): objForGroupVersion("apps/v1"), + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter_test.go new file mode 100644 index 0000000000..ced691d158 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/internal/versionconverter_test.go @@ -0,0 +1,164 @@ +/* +Copyright 2018 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 internal + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kube-openapi/pkg/validation/spec" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/typed" +) + +var testTypeConverter = func() TypeConverter { + data, err := os.ReadFile(filepath.Join("testdata", "swagger.json")) + if err != nil { + panic(err) + } + swag := spec.Swagger{} + if err := json.Unmarshal(data, &swag); err != nil { + panic(err) + } + + convertedDefs := map[string]*spec.Schema{} + for k, v := range swag.Definitions { + vCopy := v + convertedDefs[k] = &vCopy + } + typeConverter, err := NewTypeConverter(convertedDefs, false) + if err != nil { + panic(err) + } + return typeConverter +}() + +// TestVersionConverter tests the version converter +func TestVersionConverter(t *testing.T) { + oc := fakeObjectConvertorForTestSchema{ + gvkForVersion("v1beta1"): objForGroupVersion("apps/v1beta1"), + gvkForVersion("v1"): objForGroupVersion("apps/v1"), + } + vc := newVersionConverter(testTypeConverter, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal}) + + input, err := testTypeConverter.ObjectToTyped(objForGroupVersion("apps/v1beta1")) + if err != nil { + t.Fatalf("error creating converting input object to a typed value: %v", err) + } + expected := objForGroupVersion("apps/v1") + output, err := vc.Convert(input, fieldpath.APIVersion("apps/v1")) + if err != nil { + t.Fatalf("expected err to be nil but got %v", err) + } + actual, err := testTypeConverter.TypedToObject(output) + if err != nil { + t.Fatalf("error converting output typed value to an object %v", err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("expected to get %v but got %v", expected, actual) + } +} + +func TestVersionConverterSameVersion(t *testing.T) { + tc := &countingTypeConverter{TypeConverter: testTypeConverter} + oc := &countingObjectConvertor{ObjectConvertor: fakeObjectConvertorForTestSchema{}} + vc := newVersionConverter(tc, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal}) + + input, err := testTypeConverter.ObjectToTyped(objForGroupVersion("apps/v1beta1")) + if err != nil { + t.Fatalf("error converting input object to a typed value: %v", err) + } + output, err := vc.Convert(input, fieldpath.APIVersion("apps/v1beta1")) + if err != nil { + t.Fatalf("expected err to be nil but got %v", err) + } + if output != input { + t.Errorf("expected same-version conversion to return the input unchanged") + } + if tc.typedToObjectCalls != 0 { + t.Errorf("expected no TypedToObject calls for same-version conversion, got %d", tc.typedToObjectCalls) + } + if oc.convertToVersionCalls != 0 { + t.Errorf("expected no ConvertToVersion calls for same-version conversion, got %d", oc.convertToVersionCalls) + } +} + +type countingTypeConverter struct { + TypeConverter + typedToObjectCalls int +} + +func (c *countingTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { + c.typedToObjectCalls++ + return c.TypeConverter.TypedToObject(value) +} + +type countingObjectConvertor struct { + runtime.ObjectConvertor + convertToVersionCalls int +} + +func (c *countingObjectConvertor) ConvertToVersion(in runtime.Object, gv runtime.GroupVersioner) (runtime.Object, error) { + c.convertToVersionCalls++ + return c.ObjectConvertor.ConvertToVersion(in, gv) +} + +func gvkForVersion(v string) schema.GroupVersionKind { + return schema.GroupVersionKind{ + Group: "apps", + Version: v, + Kind: "Deployment", + } +} + +func objForGroupVersion(gv string) runtime.Object { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": gv, + "kind": "Deployment", + }, + } +} + +type fakeObjectConvertorForTestSchema map[schema.GroupVersionKind]runtime.Object + +var _ runtime.ObjectConvertor = fakeObjectConvertorForTestSchema{} + +func (c fakeObjectConvertorForTestSchema) ConvertToVersion(_ runtime.Object, gv runtime.GroupVersioner) (runtime.Object, error) { + allKinds := make([]schema.GroupVersionKind, 0) + for kind := range c { + allKinds = append(allKinds, kind) + } + gvk, _ := gv.KindForGroupVersionKinds(allKinds) + return c[gvk], nil +} + +func (fakeObjectConvertorForTestSchema) Convert(_, _, _ interface{}) error { + return fmt.Errorf("function not implemented") +} + +func (fakeObjectConvertorForTestSchema) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) { + return "", "", fmt.Errorf("function not implemented") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest/testfieldmanager.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest/testfieldmanager.go new file mode 100644 index 0000000000..afa7b06df9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/managedfieldstest/testfieldmanager.go @@ -0,0 +1,94 @@ +/* +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 managedfieldstest + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields" + "k8s.io/apimachinery/pkg/util/managedfields/internal/testing" +) + +// TestFieldManager is a FieldManager that can be used in test to +// simulate the behavior of Server-Side Apply and field tracking. This +// also has a few methods to get a sense of the state of the object. +// +// This TestFieldManager uses a series of "fake" objects to simulate +// some behavior which come with the limitation that you can only use +// one version since there is no version conversion logic. +// +// You can use this rather than NewDefaultTestFieldManager if you want +// to specify either a sub-resource, or a set of modified Manager to +// test them specifically. +type TestFieldManager interface { + // APIVersion of the object that we're tracking. + APIVersion() string + // Reset resets the state of the liveObject by resetting it to an empty object. + Reset() + // Live returns a copy of the current liveObject. + Live() runtime.Object + // Apply applies the given object on top of the current liveObj, for the + // given manager and force flag. + Apply(obj runtime.Object, manager string, force bool) error + // Update will updates the managed fields in the liveObj based on the + // changes performed by the update. + Update(obj runtime.Object, manager string) error + // ManagedFields returns the list of existing managed fields for the + // liveObj. + ManagedFields() []metav1.ManagedFieldsEntry +} + +// NewTestFieldManager returns a new TestFieldManager built for the +// given gvk, on the main resource. +func NewTestFieldManager(typeConverter managedfields.TypeConverter, gvk schema.GroupVersionKind) TestFieldManager { + return testing.NewTestFieldManagerImpl(typeConverter, gvk, "", nil) +} + +// NewTestFieldManagerSubresource returns a new TestFieldManager built +// for the given gvk, on the given sub-resource. +func NewTestFieldManagerSubresource(typeConverter managedfields.TypeConverter, gvk schema.GroupVersionKind, subresource string) TestFieldManager { + return testing.NewTestFieldManagerImpl(typeConverter, gvk, subresource, nil) + +} + +// NewFakeFieldManager creates an actual FieldManager but that doesn't +// perform any conversion. This is just a convenience for tests to +// create an actual manager that they can use but in very restricted +// ways. +// +// This is different from the TestFieldManager because it's not meant to +// assert values, or hold the state, this acts like a normal +// FieldManager. +// +// Also, this only operates on the main-resource, and sub-resource can't +// be configured. +func NewFakeFieldManager(typeConverter managedfields.TypeConverter, gvk schema.GroupVersionKind) *managedfields.FieldManager { + ffm, err := managedfields.NewDefaultFieldManager( + typeConverter, + &testing.FakeObjectConvertor{}, + &testing.FakeObjectDefaulter{}, + &testing.FakeObjectCreater{}, + gvk, + gvk.GroupVersion(), + "", + nil) + if err != nil { + panic(err) + } + return ffm +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/node.yaml b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/node.yaml new file mode 100644 index 0000000000..a7f2d54fdf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/node.yaml @@ -0,0 +1,261 @@ +apiVersion: v1 +kind: Node +metadata: + annotations: + container.googleapis.com/instance_id: "123456789321654789" + node.alpha.kubernetes.io/ttl: "0" + volumes.kubernetes.io/controller-managed-attach-detach: "true" + creationTimestamp: "2019-07-09T16:17:29Z" + labels: + kubernetes.io/arch: amd64 + beta.kubernetes.io/fluentd-ds-ready: "true" + beta.kubernetes.io/instance-type: n1-standard-4 + kubernetes.io/os: linux + cloud.google.com/gke-nodepool: default-pool + cloud.google.com/gke-os-distribution: cos + failure-domain.beta.kubernetes.io/region: us-central1 + failure-domain.beta.kubernetes.io/zone: us-central1-b + topology.kubernetes.io/region: us-central1 + topology.kubernetes.io/zone: us-central1-b + kubernetes.io/hostname: node-default-pool-something + name: node-default-pool-something + resourceVersion: "211582541" + selfLink: /api/v1/nodes/node-default-pool-something + uid: 0c24d0e1-a265-11e9-abe4-42010a80026b +spec: + podCIDR: 10.0.0.1/24 + providerID: some-provider-id-of-some-sort +status: + addresses: + - address: 10.0.0.1 + type: InternalIP + - address: 192.168.0.1 + type: ExternalIP + - address: node-default-pool-something + type: Hostname + allocatable: + cpu: 3920m + ephemeral-storage: "104638878617" + hugepages-2Mi: "0" + memory: 12700100Ki + pods: "110" + capacity: + cpu: "4" + ephemeral-storage: 202086868Ki + hugepages-2Mi: "0" + memory: 15399364Ki + pods: "110" + conditions: + - lastHeartbeatTime: "2019-09-20T19:32:08Z" + lastTransitionTime: "2019-07-09T16:22:08Z" + message: containerd is functioning properly + reason: FrequentContainerdRestart + status: "False" + type: FrequentContainerdRestart + - lastHeartbeatTime: "2019-09-20T19:32:08Z" + lastTransitionTime: "2019-07-09T16:22:06Z" + message: docker overlay2 is functioning properly + reason: CorruptDockerOverlay2 + status: "False" + type: CorruptDockerOverlay2 + - lastHeartbeatTime: "2019-09-20T19:32:08Z" + lastTransitionTime: "2019-07-09T16:22:06Z" + message: node is functioning properly + reason: UnregisterNetDevice + status: "False" + type: FrequentUnregisterNetDevice + - lastHeartbeatTime: "2019-09-20T19:32:08Z" + lastTransitionTime: "2019-07-09T16:17:04Z" + message: kernel has no deadlock + reason: KernelHasNoDeadlock + status: "False" + type: KernelDeadlock + - lastHeartbeatTime: "2019-09-20T19:32:08Z" + lastTransitionTime: "2019-07-09T16:17:04Z" + message: Filesystem is not read-only + reason: FilesystemIsNotReadOnly + status: "False" + type: ReadonlyFilesystem + - lastHeartbeatTime: "2019-09-20T19:32:08Z" + lastTransitionTime: "2019-07-09T16:22:05Z" + message: kubelet is functioning properly + reason: FrequentKubeletRestart + status: "False" + type: FrequentKubeletRestart + - lastHeartbeatTime: "2019-09-20T19:32:08Z" + lastTransitionTime: "2019-07-09T16:22:06Z" + message: docker is functioning properly + reason: FrequentDockerRestart + status: "False" + type: FrequentDockerRestart + - lastHeartbeatTime: "2019-07-09T16:17:47Z" + lastTransitionTime: "2019-07-09T16:17:47Z" + message: RouteController created a route + reason: RouteCreated + status: "False" + type: NetworkUnavailable + - lastHeartbeatTime: "2019-09-20T19:32:50Z" + lastTransitionTime: "2019-07-09T16:17:29Z" + message: kubelet has sufficient disk space available + reason: KubeletHasSufficientDisk + status: "False" + type: OutOfDisk + - lastHeartbeatTime: "2019-09-20T19:32:50Z" + lastTransitionTime: "2019-07-09T16:17:29Z" + message: kubelet has sufficient memory available + reason: KubeletHasSufficientMemory + status: "False" + type: MemoryPressure + - lastHeartbeatTime: "2019-09-20T19:32:50Z" + lastTransitionTime: "2019-07-09T16:17:29Z" + message: kubelet has no disk pressure + reason: KubeletHasNoDiskPressure + status: "False" + type: DiskPressure + - lastHeartbeatTime: "2019-09-20T19:32:50Z" + lastTransitionTime: "2019-07-09T16:17:29Z" + message: kubelet has sufficient PID available + reason: KubeletHasSufficientPID + status: "False" + type: PIDPressure + - lastHeartbeatTime: "2019-09-20T19:32:50Z" + lastTransitionTime: "2019-07-09T16:17:49Z" + message: kubelet is posting ready status + reason: KubeletReady + status: "True" + type: Ready + daemonEndpoints: + kubeletEndpoint: + Port: 10250 + images: + - names: + - grafana/grafana@sha256:80e5e113a984d74836aa16f5b4524012099436b1a50df293f00ac6377fb512c8 + - grafana/grafana:4.4.2 + sizeBytes: 287008013 + - names: + - registry.k8s.io/node-problem-detector@sha256:f95cab985c26b2f46e9bd43283e0bfa88860c14e0fb0649266babe8b65e9eb2b + - registry.k8s.io/node-problem-detector:v0.4.1 + sizeBytes: 286572743 + - names: + - grafana/grafana@sha256:7ff7f9b2501a5d55b55ce3f58d21771b1c5af1f2a4ab7dbf11bef7142aae7033 + - grafana/grafana:4.2.0 + sizeBytes: 277940263 + - names: + - influxdb@sha256:7dddf03376348876ed4bdf33d6dfa3326f45a2bae0930dbd80781a374eb519bc + - influxdb:1.2.2 + sizeBytes: 223948571 + - names: + - gcr.io/stackdriver-agents/stackdriver-logging-agent@sha256:f8d5231b67b9c53f60068b535a11811d29d1b3efd53d2b79f2a2591ea338e4f2 + - gcr.io/stackdriver-agents/stackdriver-logging-agent:0.6-1.6.0-1 + sizeBytes: 223242132 + - names: + - nginx@sha256:35779791c05d119df4fe476db8f47c0bee5943c83eba5656a15fc046db48178b + - nginx:1.10.1 + sizeBytes: 180708613 + - names: + - registry.k8s.io/fluentd-elasticsearch@sha256:b8c94527b489fb61d3d81ce5ad7f3ddbb7be71e9620a3a36e2bede2f2e487d73 + - registry.k8s.io/fluentd-elasticsearch:v2.0.4 + sizeBytes: 135716379 + - names: + - nginx@sha256:00be67d6ba53d5318cd91c57771530f5251cfbe028b7be2c4b70526f988cfc9f + - nginx:latest + sizeBytes: 109357355 + - names: + - registry.k8s.io/kubernetes-dashboard-amd64@sha256:dc4026c1b595435ef5527ca598e1e9c4343076926d7d62b365c44831395adbd0 + - registry.k8s.io/kubernetes-dashboard-amd64:v1.8.3 + sizeBytes: 102319441 + - names: + - gcr.io/google_containers/kube-proxy:v1.11.10-gke.5 + - registry.k8s.io/kube-proxy:v1.11.10-gke.5 + sizeBytes: 102279340 + - names: + - registry.k8s.io/event-exporter@sha256:7f9cd7cb04d6959b0aa960727d04fa86759008048c785397b7b0d9dff0007516 + - registry.k8s.io/event-exporter:v0.2.3 + sizeBytes: 94171943 + - names: + - registry.k8s.io/prometheus-to-sd@sha256:6c0c742475363d537ff059136e5d5e4ab1f512ee0fd9b7ca42ea48bc309d1662 + - registry.k8s.io/prometheus-to-sd:v0.3.1 + sizeBytes: 88077694 + - names: + - registry.k8s.io/fluentd-gcp-scaler@sha256:a5ace7506d393c4ed65eb2cbb6312c64ab357fcea16dff76b9055bc6e498e5ff + - registry.k8s.io/fluentd-gcp-scaler:0.5.1 + sizeBytes: 86637208 + - names: + - registry.k8s.io/heapster-amd64@sha256:9fae0af136ce0cf4f88393b3670f7139ffc464692060c374d2ae748e13144521 + - registry.k8s.io/heapster-amd64:v1.6.0-beta.1 + sizeBytes: 76016169 + - names: + - registry.k8s.io/ingress-glbc-amd64@sha256:31d36bbd9c44caffa135fc78cf0737266fcf25e3cf0cd1c2fcbfbc4f7309cc52 + - registry.k8s.io/ingress-glbc-amd64:v1.1.1 + sizeBytes: 67801919 + - names: + - registry.k8s.io/kube-addon-manager@sha256:d53486c3a0b49ebee019932878dc44232735d5622a51dbbdcec7124199020d09 + - registry.k8s.io/kube-addon-manager:v8.7 + sizeBytes: 63322109 + - names: + - nginx@sha256:4aacdcf186934dcb02f642579314075910f1855590fd3039d8fa4c9f96e48315 + - nginx:1.10-alpine + sizeBytes: 54042627 + - names: + - registry.k8s.io/cpvpa-amd64@sha256:cfe7b0a11c9c8e18c87b1eb34fef9a7cbb8480a8da11fc2657f78dbf4739f869 + - registry.k8s.io/cpvpa-amd64:v0.6.0 + sizeBytes: 51785854 + - names: + - registry.k8s.io/cluster-proportional-autoscaler-amd64@sha256:003f98d9f411ddfa6ff6d539196355e03ddd69fa4ed38c7ffb8fec6f729afe2d + - registry.k8s.io/cluster-proportional-autoscaler-amd64:1.1.2-r2 + sizeBytes: 49648481 + - names: + - registry.k8s.io/ip-masq-agent-amd64@sha256:1ffda57d87901bc01324c82ceb2145fe6a0448d3f0dd9cb65aa76a867cd62103 + - registry.k8s.io/ip-masq-agent-amd64:v2.1.1 + sizeBytes: 49612505 + - names: + - registry.k8s.io/k8s-dns-kube-dns-amd64@sha256:b99fc3eee2a9f052f7eb4cc00f15eb12fc405fa41019baa2d6b79847ae7284a8 + - registry.k8s.io/k8s-dns-kube-dns-amd64:1.14.10 + sizeBytes: 49549457 + - names: + - registry.k8s.io/rescheduler@sha256:156cfbfd05a5a815206fd2eeb6cbdaf1596d71ea4b415d3a6c43071dd7b99450 + - registry.k8s.io/rescheduler:v0.4.0 + sizeBytes: 48973149 + - names: + - registry.k8s.io/event-exporter@sha256:16ca66e2b5dc7a1ce6a5aafcb21d0885828b75cdfc08135430480f7ad2364adc + - registry.k8s.io/event-exporter:v0.2.4 + sizeBytes: 47261019 + - names: + - registry.k8s.io/coredns@sha256:db2bf53126ed1c761d5a41f24a1b82a461c85f736ff6e90542e9522be4757848 + - registry.k8s.io/coredns:1.1.3 + sizeBytes: 45587362 + - names: + - prom/prometheus@sha256:483f4c9d7733699ba79facca9f8bcce1cef1af43dfc3e7c5a1882aa85f53cb74 + - prom/prometheus:v1.1.3 + sizeBytes: 45493941 + nodeInfo: + architecture: amd64 + bootID: a32eca78-4ad4-4b76-9252-f143d6c2ae61 + containerRuntimeVersion: docker://17.3.2 + kernelVersion: 4.14.127+ + kubeProxyVersion: v1.11.10-gke.5 + kubeletVersion: v1.11.10-gke.5 + machineID: 1739555e5b231057f0f9a0b5fa29511b + operatingSystem: linux + osImage: Container-Optimized OS from Google + systemUUID: 1739555E-5B23-1057-F0F9-A0B5FA29511B + volumesAttached: + - devicePath: /dev/disk/by-id/b9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049 + name: kubernetes.io/pd/some-random-clusterb9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049 + - devicePath: /dev/disk/by-id/b9772-pvc-8895a852-fd42-11e6-94d4-42010a800049 + name: kubernetes.io/pd/some-random-clusterb9772-pvc-8895a852-fd42-11e6-94d4-42010a800049 + - devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049 + name: kubernetes.io/pd/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049 + - devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049 + name: kubernetes.io/pd/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049 + - devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049 + name: kubernetes.io/pd/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049 + - devicePath: /dev/disk/by-id/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283 + name: kubernetes.io/pd/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283 + volumesInUse: + - kubernetes.io/pd/some-random-clusterb9772-pvc-72e1c7f1-fd41-11e6-94d4-42010a800049 + - kubernetes.io/pd/some-random-clusterb9772-pvc-8895a852-fd42-11e6-94d4-42010a800049 + - kubernetes.io/pd/some-random-clusterb9772-pvc-8bf50554-fd42-11e6-94d4-42010a800049 + - kubernetes.io/pd/some-random-clusterb9772-pvc-8fb5e386-4641-11e7-a490-42010a800283 + - kubernetes.io/pd/some-random-clusterb9772-pvc-c2435a06-14d7-11e7-9baf-42010a800049 + - kubernetes.io/pd/some-random-clusterb9772-pvc-c787c67d-14d7-11e7-9baf-42010a800049 diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/pod.yaml b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/pod.yaml new file mode 100644 index 0000000000..3fb0877d67 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/pod.yaml @@ -0,0 +1,121 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: some-app + plugin1: some-value + plugin2: some-value + plugin3: some-value + plugin4: some-value + name: some-name + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: ReplicaSet + name: some-name + uid: 0a9d2b9e-779e-11e7-b422-42010a8001be +spec: + containers: + - args: + - one + - two + - three + - four + - five + - six + - seven + - eight + - nine + env: + - name: VAR_3 + valueFrom: + secretKeyRef: + key: some-other-key + name: some-oher-name + - name: VAR_2 + valueFrom: + secretKeyRef: + key: other-key + name: other-name + - name: VAR_1 + valueFrom: + secretKeyRef: + key: some-key + name: some-name + image: some-image-name + imagePullPolicy: IfNotPresent + name: some-name + resources: + requests: + cpu: '0' + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + name: default-token-hu5jz + readOnly: true + dnsPolicy: ClusterFirst + nodeName: node-name + priority: 0 + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + serviceAccount: default + serviceAccountName: default + terminationGracePeriodSeconds: 30 + tolerations: + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 300 + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 300 + volumes: + - name: default-token-hu5jz + secret: + defaultMode: 420 + secretName: default-token-hu5jz +status: + conditions: + - lastProbeTime: null + lastTransitionTime: '2019-07-08T09:31:18Z' + status: 'True' + type: Initialized + - lastProbeTime: null + lastTransitionTime: '2019-07-08T09:41:59Z' + status: 'True' + type: Ready + - lastProbeTime: null + lastTransitionTime: null + status: 'True' + type: ContainersReady + - lastProbeTime: null + lastTransitionTime: '2019-07-08T09:31:18Z' + status: 'True' + type: PodScheduled + containerStatuses: + - containerID: docker://885e82a1ed0b7356541bb410a0126921ac42439607c09875cd8097dd5d7b5376 + image: some-image-name + imageID: docker-pullable://some-image-id + lastState: + terminated: + containerID: docker://d57290f9e00fad626b20d2dd87a3cf69bbc22edae07985374f86a8b2b4e39565 + exitCode: 255 + finishedAt: '2019-07-08T09:39:09Z' + reason: Error + startedAt: '2019-07-08T09:38:54Z' + name: name + ready: true + restartCount: 6 + state: + running: + startedAt: '2019-07-08T09:41:59Z' + hostIP: 10.0.0.1 + phase: Running + podIP: 10.0.0.1 + qosClass: BestEffort + startTime: '2019-07-08T09:31:18Z' diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/scalehandler.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/scalehandler.go new file mode 100644 index 0000000000..ca96ca9834 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/scalehandler.go @@ -0,0 +1,174 @@ +/* +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 managedfields + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +var ( + scaleGroupVersion = schema.GroupVersion{Group: "autoscaling", Version: "v1"} + replicasPathInScale = fieldpath.MakePathOrDie("spec", "replicas") +) + +// ResourcePathMappings maps a group/version to its replicas path. The +// assumption is that all the paths correspond to leaf fields. +type ResourcePathMappings map[string]fieldpath.Path + +// ScaleHandler manages the conversion of managed fields between a main +// resource and the scale subresource +type ScaleHandler struct { + parentEntries []metav1.ManagedFieldsEntry + groupVersion schema.GroupVersion + mappings ResourcePathMappings +} + +// NewScaleHandler creates a new ScaleHandler +func NewScaleHandler(parentEntries []metav1.ManagedFieldsEntry, groupVersion schema.GroupVersion, mappings ResourcePathMappings) *ScaleHandler { + return &ScaleHandler{ + parentEntries: parentEntries, + groupVersion: groupVersion, + mappings: mappings, + } +} + +// ToSubresource filter the managed fields of the main resource and convert +// them so that they can be handled by scale. +// For the managed fields that have a replicas path it performs two changes: +// 1. APIVersion is changed to the APIVersion of the scale subresource +// 2. Replicas path of the main resource is transformed to the replicas path of +// the scale subresource +func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) { + managed, err := internal.DecodeManagedFields(h.parentEntries) + if err != nil { + return nil, err + } + + f := fieldpath.ManagedFields{} + t := map[string]*metav1.Time{} + for manager, versionedSet := range managed.Fields() { + path, ok := h.mappings[string(versionedSet.APIVersion())] + // Skip the entry if the APIVersion is unknown + if !ok || path == nil { + continue + } + + if versionedSet.Set().Has(path) { + newVersionedSet := fieldpath.NewVersionedSet( + fieldpath.NewSet(replicasPathInScale), + fieldpath.APIVersion(scaleGroupVersion.String()), + versionedSet.Applied(), + ) + + f[manager] = newVersionedSet + t[manager] = managed.Times()[manager] + } + } + + return managedFieldsEntries(internal.NewManaged(f, t)) +} + +// ToParent merges `scaleEntries` with the entries of the main resource and +// transforms them accordingly +func (h *ScaleHandler) ToParent(scaleEntries []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) { + decodedParentEntries, err := internal.DecodeManagedFields(h.parentEntries) + if err != nil { + return nil, err + } + parentFields := decodedParentEntries.Fields() + + decodedScaleEntries, err := internal.DecodeManagedFields(scaleEntries) + if err != nil { + return nil, err + } + scaleFields := decodedScaleEntries.Fields() + + f := fieldpath.ManagedFields{} + t := map[string]*metav1.Time{} + + for manager, versionedSet := range parentFields { + // Get the main resource "replicas" path + path, ok := h.mappings[string(versionedSet.APIVersion())] + // Drop the entry if the APIVersion is unknown. + if !ok { + continue + } + + // If the parent entry does not have the replicas path or it is nil, just + // keep it as it is. The path is nil for Custom Resources without scale + // subresource. + if path == nil || !versionedSet.Set().Has(path) { + f[manager] = versionedSet + t[manager] = decodedParentEntries.Times()[manager] + continue + } + + if _, ok := scaleFields[manager]; !ok { + // "Steal" the replicas path from the main resource entry + newSet := versionedSet.Set().Difference(fieldpath.NewSet(path)) + + if !newSet.Empty() { + newVersionedSet := fieldpath.NewVersionedSet( + newSet, + versionedSet.APIVersion(), + versionedSet.Applied(), + ) + f[manager] = newVersionedSet + t[manager] = decodedParentEntries.Times()[manager] + } + } else { + // Field wasn't stolen, let's keep the entry as it is. + f[manager] = versionedSet + t[manager] = decodedParentEntries.Times()[manager] + delete(scaleFields, manager) + } + } + + for manager, versionedSet := range scaleFields { + if !versionedSet.Set().Has(replicasPathInScale) { + continue + } + newVersionedSet := fieldpath.NewVersionedSet( + fieldpath.NewSet(h.mappings[h.groupVersion.String()]), + fieldpath.APIVersion(h.groupVersion.String()), + versionedSet.Applied(), + ) + f[manager] = newVersionedSet + t[manager] = decodedParentEntries.Times()[manager] + } + + return managedFieldsEntries(internal.NewManaged(f, t)) +} + +func managedFieldsEntries(entries internal.ManagedInterface) ([]metav1.ManagedFieldsEntry, error) { + obj := &unstructured.Unstructured{Object: map[string]interface{}{}} + if err := internal.EncodeObjectManagedFields(obj, entries); err != nil { + return nil, err + } + accessor, err := meta.Accessor(obj) + if err != nil { + panic(fmt.Sprintf("couldn't get accessor: %v", err)) + } + return accessor.GetManagedFields(), nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/scalehandler_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/scalehandler_test.go new file mode 100644 index 0000000000..8b35974ed6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/scalehandler_test.go @@ -0,0 +1,785 @@ +/* +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 managedfields + +import ( + "reflect" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" +) + +func TestTransformManagedFieldsToSubresource(t *testing.T) { + testTime, _ := time.ParseInLocation("2006-Jan-02", "2013-Feb-03", time.Local) + managedFieldTime := metav1.NewTime(testTime) + + tests := []struct { + desc string + input []metav1.ManagedFieldsEntry + expected []metav1.ManagedFieldsEntry + }{ + { + desc: "filter one entry and transform it into a subresource entry", + input: []metav1.ManagedFieldsEntry{ + { + Manager: "manager-1", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:another-field":{}}}`), + }, + { + Manager: "manager-2", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Time: &managedFieldTime, + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "manager-2", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Time: &managedFieldTime, + }, + }, + }, + { + desc: "transform all entries", + input: []metav1.ManagedFieldsEntry{ + { + Manager: "manager-1", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + { + Manager: "manager-2", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + { + Manager: "manager-3", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "manager-1", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + { + Manager: "manager-2", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + { + Manager: "manager-3", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + { + desc: "drops fields if the api version is unknown", + input: []metav1.ManagedFieldsEntry{ + { + Manager: "manager-1", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v10", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + }, + expected: nil, + }, + } + + for _, test := range tests { + handler := NewScaleHandler( + test.input, + schema.GroupVersion{Group: "apps", Version: "v1"}, + defaultMappings(), + ) + subresourceEntries, err := handler.ToSubresource() + if err != nil { + t.Fatalf("test %q - expected no error but got %v", test.desc, err) + } + + if !reflect.DeepEqual(subresourceEntries, test.expected) { + t.Fatalf("test %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, subresourceEntries) + } + } +} + +func TestTransformingManagedFieldsToParent(t *testing.T) { + tests := []struct { + desc string + parent []metav1.ManagedFieldsEntry + subresource []metav1.ManagedFieldsEntry + expected []metav1.ManagedFieldsEntry + }{ + { + desc: "different-managers: apply -> update", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + { + desc: "different-managers: apply -> apply", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + }, + }, + { + desc: "different-managers: update -> update", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + }, + }, + { + desc: "different-managers: update -> apply", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + }, + }, + { + desc: "same manager: apply -> apply", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + { + desc: "same manager: update -> update", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + { + desc: "same manager: update -> apply", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + }, + }, + { + desc: "same manager: apply -> update", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + { + desc: "subresource doesn't own the path anymore", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:status":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + }, + }, + { + desc: "Subresource steals all the fields of the parent resource", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + { + desc: "apply without stealing", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{},"f:selector":{}}}`), + }, + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + { + desc: "drops the entry if the api version is unknown", + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + { + Manager: "another-manager", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v10", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + handler := NewScaleHandler( + test.parent, + schema.GroupVersion{Group: "apps", Version: "v1"}, + defaultMappings(), + ) + parentEntries, err := handler.ToParent(test.subresource) + if err != nil { + t.Fatalf("test: %q - expected no error but got %v", test.desc, err) + } + if !reflect.DeepEqual(parentEntries, test.expected) { + t.Fatalf("test: %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, parentEntries) + } + }) + } +} + +func TestTransformingManagedFieldsToParentMultiVersion(t *testing.T) { + tests := []struct { + desc string + groupVersion schema.GroupVersion + mappings ResourcePathMappings + parent []metav1.ManagedFieldsEntry + subresource []metav1.ManagedFieldsEntry + expected []metav1.ManagedFieldsEntry + }{ + { + desc: "multi-version", + groupVersion: schema.GroupVersion{Group: "apps", Version: "v1"}, + mappings: ResourcePathMappings{ + "apps/v1": fieldpath.MakePathOrDie("spec", "the-replicas"), + "apps/v2": fieldpath.MakePathOrDie("spec", "not-the-replicas"), + }, + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`), + }, + { + Manager: "test-other", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v2", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:not-the-replicas":{},"f:selector":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + { + Manager: "test-other", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "apps/v2", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "apps/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:the-replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + { + desc: "Custom resource without scale subresource, scaling a version with `scale`", + groupVersion: schema.GroupVersion{Group: "mygroup", Version: "v1"}, + mappings: ResourcePathMappings{ + "mygroup/v1": fieldpath.MakePathOrDie("spec", "the-replicas"), + "mygroup/v2": nil, + }, + parent: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "mygroup/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:the-replicas":{},"f:selector":{}}}`), + }, + { + Manager: "test-other", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "mygroup/v2", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:test-other":{}}}`), + }, + }, + subresource: []metav1.ManagedFieldsEntry{ + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "autoscaling/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:replicas":{}}}`), + Subresource: "scale", + }, + }, + expected: []metav1.ManagedFieldsEntry{ + { + Manager: "test", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "mygroup/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:selector":{}}}`), + }, + { + Manager: "test-other", + Operation: metav1.ManagedFieldsOperationApply, + APIVersion: "mygroup/v2", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:test-other":{}}}`), + }, + { + Manager: "scale", + Operation: metav1.ManagedFieldsOperationUpdate, + APIVersion: "mygroup/v1", + FieldsType: "FieldsV1", + FieldsV1: metav1.NewFieldsV1(`{"f:spec":{"f:the-replicas":{}}}`), + Subresource: "scale", + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + handler := NewScaleHandler( + test.parent, + test.groupVersion, + test.mappings, + ) + parentEntries, err := handler.ToParent(test.subresource) + if err != nil { + t.Fatalf("test: %q - expected no error but got %v", test.desc, err) + } + if !reflect.DeepEqual(parentEntries, test.expected) { + t.Fatalf("test: %q - expected output to be:\n%v\n\nbut got:\n%v", test.desc, test.expected, parentEntries) + } + }) + } +} + +func defaultMappings() ResourcePathMappings { + return ResourcePathMappings{ + "apps/v1": fieldpath.MakePathOrDie("spec", "replicas"), + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/typeconverter.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/typeconverter.go new file mode 100644 index 0000000000..e706ac8221 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/managedfields/typeconverter.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 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 managedfields + +import ( + "sigs.k8s.io/structured-merge-diff/v6/typed" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/managedfields/internal" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +// TypeConverter allows you to convert from runtime.Object to +// typed.TypedValue and the other way around. +type TypeConverter = internal.TypeConverter + +// NewDeducedTypeConverter creates a TypeConverter for CRDs that don't +// have a schema. It does implement the same interface though (and +// create the same types of objects), so that everything can still work +// the same. CRDs are merged with all their fields being "atomic" (lists +// included). +func NewDeducedTypeConverter() TypeConverter { + return internal.NewDeducedTypeConverter() +} + +// NewTypeConverter builds a TypeConverter from a map of OpenAPIV3 schemas. +// This will automatically find the proper version of the object, and the +// corresponding schema information. +// The keys to the map must be consistent with the names +// used by Refs within the schemas. +// The schemas should conform to the Kubernetes Structural Schema OpenAPI +// restrictions found in docs: +// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema +func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) { + return internal.NewTypeConverter(openapiSpec, preserveUnknownFields) +} + +// NewSchemeTypeConverter creates a TypeConverter that uses the provided scheme to +// convert between runtime.Objects and TypedValues. +func NewSchemeTypeConverter(scheme *runtime.Scheme, parser *typed.Parser) TypeConverter { + return internal.NewSchemeTypeConverter(scheme, parser) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/OWNERS new file mode 100644 index 0000000000..349bc69d65 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/OWNERS @@ -0,0 +1,6 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - pwittrock +reviewers: + - apelisse diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/errors.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/errors.go new file mode 100644 index 0000000000..16501d5afe --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/errors.go @@ -0,0 +1,102 @@ +/* +Copyright 2017 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 mergepatch + +import ( + "errors" + "fmt" + "reflect" +) + +var ( + ErrBadJSONDoc = errors.New("invalid JSON document") + ErrNoListOfLists = errors.New("lists of lists are not supported") + ErrBadPatchFormatForPrimitiveList = errors.New("invalid patch format of primitive list") + ErrBadPatchFormatForRetainKeys = errors.New("invalid patch format of retainKeys") + ErrBadPatchFormatForSetElementOrderList = errors.New("invalid patch format of setElementOrder list") + ErrPatchContentNotMatchRetainKeys = errors.New("patch content doesn't match retainKeys list") + ErrUnsupportedStrategicMergePatchFormat = errors.New("strategic merge patch format is not supported") +) + +func ErrNoMergeKey(m map[string]interface{}, k string) error { + return fmt.Errorf("map: %v does not contain declared merge key: %s", m, k) +} + +func ErrBadArgType(expected, actual interface{}) error { + return fmt.Errorf("expected a %s, but received a %s", + reflect.TypeOf(expected), + reflect.TypeOf(actual)) +} + +func ErrBadArgKind(expected, actual interface{}) error { + var expectedKindString, actualKindString string + if expected == nil { + expectedKindString = "nil" + } else { + expectedKindString = reflect.TypeOf(expected).Kind().String() + } + if actual == nil { + actualKindString = "nil" + } else { + actualKindString = reflect.TypeOf(actual).Kind().String() + } + return fmt.Errorf("expected a %s, but received a %s", expectedKindString, actualKindString) +} + +func ErrBadPatchType(t interface{}, m map[string]interface{}) error { + return fmt.Errorf("unknown patch type: %s in map: %v", t, m) +} + +// IsPreconditionFailed returns true if the provided error indicates +// a precondition failed. +func IsPreconditionFailed(err error) bool { + _, ok := err.(ErrPreconditionFailed) + return ok +} + +type ErrPreconditionFailed struct { + message string +} + +func NewErrPreconditionFailed(target map[string]interface{}) ErrPreconditionFailed { + s := fmt.Sprintf("precondition failed for: %v", target) + return ErrPreconditionFailed{s} +} + +func (err ErrPreconditionFailed) Error() string { + return err.message +} + +type ErrConflict struct { + message string +} + +func NewErrConflict(patch, current string) ErrConflict { + s := fmt.Sprintf("patch:\n%s\nconflicts with changes made from original to current:\n%s\n", patch, current) + return ErrConflict{s} +} + +func (err ErrConflict) Error() string { + return err.message +} + +// IsConflict returns true if the provided error indicates +// a conflict between the patch and the current configuration. +func IsConflict(err error) bool { + _, ok := err.(ErrConflict) + return ok +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/util.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/util.go new file mode 100644 index 0000000000..46e3bb75dd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/util.go @@ -0,0 +1,133 @@ +/* +Copyright 2017 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 mergepatch + +import ( + "fmt" + "reflect" + + "k8s.io/utils/dump" + "sigs.k8s.io/yaml" +) + +// PreconditionFunc asserts that an incompatible change is not present within a patch. +type PreconditionFunc func(interface{}) bool + +// RequireKeyUnchanged returns a precondition function that fails if the provided key +// is present in the patch (indicating that its value has changed). +func RequireKeyUnchanged(key string) PreconditionFunc { + return func(patch interface{}) bool { + patchMap, ok := patch.(map[string]interface{}) + if !ok { + return true + } + + // The presence of key means that its value has been changed, so the test fails. + _, ok = patchMap[key] + return !ok + } +} + +// RequireMetadataKeyUnchanged creates a precondition function that fails +// if the metadata.key is present in the patch (indicating its value +// has changed). +func RequireMetadataKeyUnchanged(key string) PreconditionFunc { + return func(patch interface{}) bool { + patchMap, ok := patch.(map[string]interface{}) + if !ok { + return true + } + patchMap1, ok := patchMap["metadata"] + if !ok { + return true + } + patchMap2, ok := patchMap1.(map[string]interface{}) + if !ok { + return true + } + _, ok = patchMap2[key] + return !ok + } +} + +func ToYAMLOrError(v interface{}) string { + y, err := toYAML(v) + if err != nil { + return err.Error() + } + + return y +} + +func toYAML(v interface{}) (string, error) { + y, err := yaml.Marshal(v) + if err != nil { + return "", fmt.Errorf("yaml marshal failed:%v\n%v\n", err, dump.Pretty(v)) + } + + return string(y), nil +} + +// HasConflicts returns true if the left and right JSON interface objects overlap with +// different values in any key. All keys are required to be strings. Since patches of the +// same Type have congruent keys, this is valid for multiple patch types. This method +// supports JSON merge patch semantics. +// +// NOTE: Numbers with different types (e.g. int(0) vs int64(0)) will be detected as conflicts. +// Make sure the unmarshaling of left and right are consistent (e.g. use the same library). +func HasConflicts(left, right interface{}) (bool, error) { + switch typedLeft := left.(type) { + case map[string]interface{}: + switch typedRight := right.(type) { + case map[string]interface{}: + for key, leftValue := range typedLeft { + rightValue, ok := typedRight[key] + if !ok { + continue + } + if conflict, err := HasConflicts(leftValue, rightValue); err != nil || conflict { + return conflict, err + } + } + + return false, nil + default: + return true, nil + } + case []interface{}: + switch typedRight := right.(type) { + case []interface{}: + if len(typedLeft) != len(typedRight) { + return true, nil + } + + for i := range typedLeft { + if conflict, err := HasConflicts(typedLeft[i], typedRight[i]); err != nil || conflict { + return conflict, err + } + } + + return false, nil + default: + return true, nil + } + case string, float64, bool, int64, nil: + return !reflect.DeepEqual(left, right), nil + default: + return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left)) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/util_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/util_test.go new file mode 100644 index 0000000000..e74dfabd4f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/mergepatch/util_test.go @@ -0,0 +1,136 @@ +/* +Copyright 2017 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 mergepatch + +import ( + "fmt" + "testing" +) + +func TestHasConflicts(t *testing.T) { + testCases := []struct { + A interface{} + B interface{} + Ret bool + }{ + {A: "hello", B: "hello", Ret: false}, + {A: "hello", B: "hell", Ret: true}, + {A: "hello", B: nil, Ret: true}, + {A: "hello", B: int64(1), Ret: true}, + {A: "hello", B: float64(1.0), Ret: true}, + {A: "hello", B: false, Ret: true}, + {A: int64(1), B: int64(1), Ret: false}, + {A: nil, B: nil, Ret: false}, + {A: false, B: false, Ret: false}, + {A: float64(3), B: float64(3), Ret: false}, + + {A: "hello", B: []interface{}{}, Ret: true}, + {A: []interface{}{int64(1)}, B: []interface{}{}, Ret: true}, + {A: []interface{}{}, B: []interface{}{}, Ret: false}, + {A: []interface{}{int64(1)}, B: []interface{}{int64(1)}, Ret: false}, + {A: map[string]interface{}{}, B: []interface{}{int64(1)}, Ret: true}, + + {A: map[string]interface{}{}, B: map[string]interface{}{"a": int64(1)}, Ret: false}, + {A: map[string]interface{}{"a": int64(1)}, B: map[string]interface{}{"a": int64(1)}, Ret: false}, + {A: map[string]interface{}{"a": int64(1)}, B: map[string]interface{}{"a": int64(2)}, Ret: true}, + {A: map[string]interface{}{"a": int64(1)}, B: map[string]interface{}{"b": int64(2)}, Ret: false}, + + { + A: map[string]interface{}{"a": []interface{}{int64(1)}}, + B: map[string]interface{}{"a": []interface{}{int64(1)}}, + Ret: false, + }, + { + A: map[string]interface{}{"a": []interface{}{int64(1)}}, + B: map[string]interface{}{"a": []interface{}{}}, + Ret: true, + }, + { + A: map[string]interface{}{"a": []interface{}{int64(1)}}, + B: map[string]interface{}{"a": int64(1)}, + Ret: true, + }, + + // Maps and lists with multiple entries. + { + A: map[string]interface{}{"a": int64(1), "b": int64(2)}, + B: map[string]interface{}{"a": int64(1), "b": int64(0)}, + Ret: true, + }, + { + A: map[string]interface{}{"a": int64(1), "b": int64(2)}, + B: map[string]interface{}{"a": int64(1), "b": int64(2)}, + Ret: false, + }, + { + A: map[string]interface{}{"a": int64(1), "b": int64(2)}, + B: map[string]interface{}{"a": int64(1), "b": int64(0), "c": int64(3)}, + Ret: true, + }, + { + A: map[string]interface{}{"a": int64(1), "b": int64(2)}, + B: map[string]interface{}{"a": int64(1), "b": int64(2), "c": int64(3)}, + Ret: false, + }, + { + A: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}, + B: map[string]interface{}{"a": []interface{}{int64(1), int64(0)}}, + Ret: true, + }, + { + A: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}, + B: map[string]interface{}{"a": []interface{}{int64(1), int64(2)}}, + Ret: false, + }, + + // Numeric types are not interchangeable. + // Callers are expected to ensure numeric types are consistent in 'left' and 'right'. + {A: int64(0), B: float64(0), Ret: true}, + // Other types are not interchangeable. + {A: int64(0), B: "0", Ret: true}, + {A: int64(0), B: nil, Ret: true}, + {A: int64(0), B: false, Ret: true}, + {A: "true", B: true, Ret: true}, + {A: "null", B: nil, Ret: true}, + } + + for _, testCase := range testCases { + testStr := fmt.Sprintf("A = %#v, B = %#v", testCase.A, testCase.B) + // Run each test case multiple times if it passes because HasConflicts() + // uses map iteration, which returns keys in nondeterministic order. + for try := 0; try < 10; try++ { + out, err := HasConflicts(testCase.A, testCase.B) + if err != nil { + t.Errorf("%v: unexpected error: %v", testStr, err) + break + } + if out != testCase.Ret { + t.Errorf("%v: expected %t got %t", testStr, testCase.Ret, out) + break + } + out, err = HasConflicts(testCase.B, testCase.A) + if err != nil { + t.Errorf("%v: unexpected error: %v", testStr, err) + break + } + if out != testCase.Ret { + t.Errorf("%v: expected reversed %t got %t", testStr, testCase.Ret, out) + break + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/naming/from_stack.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/naming/from_stack.go new file mode 100644 index 0000000000..d69bf32caa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/naming/from_stack.go @@ -0,0 +1,93 @@ +/* +Copyright 2018 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 naming + +import ( + "fmt" + "regexp" + goruntime "runtime" + "runtime/debug" + "strconv" + "strings" +) + +// GetNameFromCallsite walks back through the call stack until we find a caller from outside of the ignoredPackages +// it returns back a shortpath/filename:line to aid in identification of this reflector when it starts logging +func GetNameFromCallsite(ignoredPackages ...string) string { + name := "????" + const maxStack = 10 + for i := 1; i < maxStack; i++ { + _, file, line, ok := goruntime.Caller(i) + if !ok { + file, line, ok = extractStackCreator() + if !ok { + break + } + i += maxStack + } + if hasPackage(file, append(ignoredPackages, "/runtime/asm_")) { + continue + } + + file = trimPackagePrefix(file) + name = fmt.Sprintf("%s:%d", file, line) + break + } + return name +} + +// hasPackage returns true if the file is in one of the ignored packages. +func hasPackage(file string, ignoredPackages []string) bool { + for _, ignoredPackage := range ignoredPackages { + if strings.Contains(file, ignoredPackage) { + return true + } + } + return false +} + +// trimPackagePrefix reduces duplicate values off the front of a package name. +func trimPackagePrefix(file string) string { + if l := strings.LastIndex(file, "/vendor/"); l >= 0 { + return file[l+len("/vendor/"):] + } + if l := strings.LastIndex(file, "/src/"); l >= 0 { + return file[l+5:] + } + if l := strings.LastIndex(file, "/pkg/"); l >= 0 { + return file[l+1:] + } + return file +} + +var stackCreator = regexp.MustCompile(`(?m)^created by (.*)\n\s+(.*):(\d+) \+0x[[:xdigit:]]+$`) + +// extractStackCreator retrieves the goroutine file and line that launched this stack. Returns false +// if the creator cannot be located. +// TODO: Go does not expose this via runtime https://github.com/golang/go/issues/11440 +func extractStackCreator() (string, int, bool) { + stack := debug.Stack() + matches := stackCreator.FindStringSubmatch(string(stack)) + if len(matches) != 4 { + return "", 0, false + } + line, err := strconv.Atoi(matches[3]) + if err != nil { + return "", 0, false + } + return matches[2], line, true +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/naming/from_stack_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/naming/from_stack_test.go new file mode 100644 index 0000000000..0eaafae577 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/naming/from_stack_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 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 naming + +import ( + "strings" + "testing" +) + +func TestGetNameFromCallsite(t *testing.T) { + tests := []struct { + name string + ignoredPackages []string + expected string + }{ + { + name: "simple", + expected: "k8s.io/apimachinery/pkg/util/naming/from_stack_test.go:", + }, + { + name: "ignore-package", + ignoredPackages: []string{"k8s.io/apimachinery/pkg/util/naming"}, + expected: "testing/testing.go:", + }, + { + name: "ignore-file", + ignoredPackages: []string{"k8s.io/apimachinery/pkg/util/naming/from_stack_test.go"}, + expected: "testing/testing.go:", + }, + { + name: "ignore-multiple", + ignoredPackages: []string{"k8s.io/apimachinery/pkg/util/naming/from_stack_test.go", "testing/testing.go"}, + expected: "????", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actual := GetNameFromCallsite(tc.ignoredPackages...) + if !strings.HasPrefix(actual, tc.expected) { + t.Fatalf("expected string with prefix %q, got %q", tc.expected, actual) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/http.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/http.go new file mode 100644 index 0000000000..8912804c50 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/http.go @@ -0,0 +1,704 @@ +/* +Copyright 2016 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 net + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "mime" + "net" + "net/http" + "net/url" + "os" + "path" + "regexp" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf8" + + "golang.org/x/net/http2" + "k8s.io/klog/v2" + netutils "k8s.io/utils/net" +) + +// JoinPreservingTrailingSlash does a path.Join of the specified elements, +// preserving any trailing slash on the last non-empty segment +func JoinPreservingTrailingSlash(elem ...string) string { + // do the basic path join + result := path.Join(elem...) + + // find the last non-empty segment + for i := len(elem) - 1; i >= 0; i-- { + if len(elem[i]) > 0 { + // if the last segment ended in a slash, ensure our result does as well + if strings.HasSuffix(elem[i], "/") && !strings.HasSuffix(result, "/") { + result += "/" + } + break + } + } + + return result +} + +// IsTimeout returns true if the given error is a network timeout error +func IsTimeout(err error) bool { + var neterr net.Error + if errors.As(err, &neterr) { + return neterr != nil && neterr.Timeout() + } + return false +} + +// IsProbableEOF returns true if the given error resembles a connection termination +// scenario that would justify assuming that the watch is empty. +// These errors are what the Go http stack returns back to us which are general +// connection closure errors (strongly correlated) and callers that need to +// differentiate probable errors in connection behavior between normal "this is +// disconnected" should use the method. +func IsProbableEOF(err error) bool { + if err == nil { + return false + } + var uerr *url.Error + if errors.As(err, &uerr) { + err = uerr.Err + } + msg := err.Error() + switch { + case err == io.EOF: + return true + case err == io.ErrUnexpectedEOF: + return true + case msg == "http: can't write HTTP request on broken connection": + return true + case strings.Contains(msg, "http2: server sent GOAWAY and closed the connection"): + return true + case strings.Contains(msg, "connection reset by peer"): + return true + case strings.Contains(strings.ToLower(msg), "use of closed network connection"): + return true + } + return false +} + +var defaultTransport = http.DefaultTransport.(*http.Transport) + +// SetOldTransportDefaults applies the defaults from http.DefaultTransport +// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset +func SetOldTransportDefaults(t *http.Transport) *http.Transport { + if t.Proxy == nil || isDefault(t.Proxy) { + // http.ProxyFromEnvironment doesn't respect CIDRs and that makes it impossible to exclude things like pod and service IPs from proxy settings + // ProxierWithNoProxyCIDR allows CIDR rules in NO_PROXY + t.Proxy = NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment) + } + // If no custom dialer is set, use the default context dialer + //lint:file-ignore SA1019 Keep supporting deprecated Dial method of custom transports + if t.DialContext == nil && t.Dial == nil { + t.DialContext = defaultTransport.DialContext + } + if t.TLSHandshakeTimeout == 0 { + t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout + } + if t.IdleConnTimeout == 0 { + t.IdleConnTimeout = defaultTransport.IdleConnTimeout + } + return t +} + +// SetTransportDefaults applies the defaults from http.DefaultTransport +// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset +func SetTransportDefaults(t *http.Transport) *http.Transport { + t = SetOldTransportDefaults(t) + // Allow clients to disable http2 if needed. + if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 { + //nolint:logcheck // Should be rare, not worth converting. + klog.Info("HTTP2 has been explicitly disabled") + } else if allowsHTTP2(t) { + if err := configureHTTP2Transport(t); err != nil { + //nolint:logcheck // Should be rare, not worth converting. + klog.Warningf("Transport failed http2 configuration: %v", err) + } + } + return t +} + +func readIdleTimeoutSeconds() int { + ret := 30 + // User can set the readIdleTimeout to 0 to disable the HTTP/2 + // connection health check. + if s := os.Getenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS"); len(s) > 0 { + i, err := strconv.Atoi(s) + if err != nil { + //nolint:logcheck // Should be rare, not worth converting. + klog.Warningf("Illegal HTTP2_READ_IDLE_TIMEOUT_SECONDS(%q): %v."+ + " Default value %d is used", s, err, ret) + return ret + } + ret = i + } + return ret +} + +func pingTimeoutSeconds() int { + ret := 15 + if s := os.Getenv("HTTP2_PING_TIMEOUT_SECONDS"); len(s) > 0 { + i, err := strconv.Atoi(s) + if err != nil { + //nolint:logcheck // Should be rare, not worth converting. + klog.Warningf("Illegal HTTP2_PING_TIMEOUT_SECONDS(%q): %v."+ + " Default value %d is used", s, err, ret) + return ret + } + ret = i + } + return ret +} + +func configureHTTP2Transport(t *http.Transport) error { + t2, err := http2.ConfigureTransports(t) + if err != nil { + return err + } + // The following enables the HTTP/2 connection health check added in + // https://github.com/golang/net/pull/55. The health check detects and + // closes broken transport layer connections. Without the health check, + // a broken connection can linger too long, e.g., a broken TCP + // connection will be closed by the Linux kernel after 13 to 30 minutes + // by default, which caused + // https://github.com/kubernetes/client-go/issues/374 and + // https://github.com/kubernetes/kubernetes/issues/87615. + t2.ReadIdleTimeout = time.Duration(readIdleTimeoutSeconds()) * time.Second + t2.PingTimeout = time.Duration(pingTimeoutSeconds()) * time.Second + return nil +} + +func allowsHTTP2(t *http.Transport) bool { + if t.TLSClientConfig == nil || len(t.TLSClientConfig.NextProtos) == 0 { + // the transport expressed no NextProto preference, allow + return true + } + for _, p := range t.TLSClientConfig.NextProtos { + if p == http2.NextProtoTLS { + // the transport explicitly allowed http/2 + return true + } + } + // the transport explicitly set NextProtos and excluded http/2 + return false +} + +type RoundTripperWrapper interface { + http.RoundTripper + WrappedRoundTripper() http.RoundTripper +} + +type DialFunc func(ctx context.Context, net, addr string) (net.Conn, error) + +func DialerFor(transport http.RoundTripper) (DialFunc, error) { + if transport == nil { + return nil, nil + } + + switch transport := transport.(type) { + case *http.Transport: + // transport.DialContext takes precedence over transport.Dial + if transport.DialContext != nil { + return transport.DialContext, nil + } + // adapt transport.Dial to the DialWithContext signature + if transport.Dial != nil { + return func(ctx context.Context, net, addr string) (net.Conn, error) { + return transport.Dial(net, addr) + }, nil + } + // otherwise return nil + return nil, nil + case RoundTripperWrapper: + return DialerFor(transport.WrappedRoundTripper()) + default: + return nil, fmt.Errorf("unknown transport type: %T", transport) + } +} + +// CloseIdleConnectionsFor close idles connections for the Transport. +// If the Transport is wrapped it iterates over the wrapped round trippers +// until it finds one that implements the CloseIdleConnections method. +// If the Transport does not have a CloseIdleConnections method +// then this function does nothing. +func CloseIdleConnectionsFor(transport http.RoundTripper) { + if transport == nil { + return + } + type closeIdler interface { + CloseIdleConnections() + } + + switch transport := transport.(type) { + case closeIdler: + transport.CloseIdleConnections() + case RoundTripperWrapper: + CloseIdleConnectionsFor(transport.WrappedRoundTripper()) + default: + //nolint:logcheck // Should be rare, not worth converting. + klog.Warningf("unknown transport type: %T", transport) + } +} + +type TLSClientConfigHolder interface { + TLSClientConfig() *tls.Config +} + +func TLSClientConfig(transport http.RoundTripper) (*tls.Config, error) { + if transport == nil { + return nil, nil + } + + switch transport := transport.(type) { + case *http.Transport: + return transport.TLSClientConfig, nil + case TLSClientConfigHolder: + return transport.TLSClientConfig(), nil + case RoundTripperWrapper: + return TLSClientConfig(transport.WrappedRoundTripper()) + default: + return nil, fmt.Errorf("unknown transport type: %T", transport) + } +} + +func FormatURL(scheme string, host string, port int, path string) *url.URL { + return &url.URL{ + Scheme: scheme, + Host: net.JoinHostPort(host, strconv.Itoa(port)), + Path: path, + } +} + +func GetHTTPClient(req *http.Request) string { + if ua := req.UserAgent(); len(ua) != 0 { + return ua + } + return "unknown" +} + +// SourceIPs splits the comma separated X-Forwarded-For header and joins it with +// the X-Real-Ip header and/or req.RemoteAddr, ignoring invalid IPs. +// The X-Real-Ip is omitted if it's already present in the X-Forwarded-For chain. +// The req.RemoteAddr is always the last IP in the returned list. +// It returns nil if all of these are empty or invalid. +func SourceIPs(req *http.Request) []net.IP { + var srcIPs []net.IP + + hdr := req.Header + // First check the X-Forwarded-For header for requests via proxy. + hdrForwardedFor := hdr.Get("X-Forwarded-For") + if hdrForwardedFor != "" { + // X-Forwarded-For can be a csv of IPs in case of multiple proxies. + // Use the first valid one. + parts := strings.Split(hdrForwardedFor, ",") + for _, part := range parts { + ip := netutils.ParseIPSloppy(strings.TrimSpace(part)) + if ip != nil { + srcIPs = append(srcIPs, ip) + } + } + } + + // Try the X-Real-Ip header. + hdrRealIp := hdr.Get("X-Real-Ip") + if hdrRealIp != "" { + ip := netutils.ParseIPSloppy(hdrRealIp) + // Only append the X-Real-Ip if it's not already contained in the X-Forwarded-For chain. + if ip != nil && !containsIP(srcIPs, ip) { + srcIPs = append(srcIPs, ip) + } + } + + // Always include the request Remote Address as it cannot be easily spoofed. + var remoteIP net.IP + // Remote Address in Go's HTTP server is in the form host:port so we need to split that first. + host, _, err := net.SplitHostPort(req.RemoteAddr) + if err == nil { + remoteIP = netutils.ParseIPSloppy(host) + } + // Fallback if Remote Address was just IP. + if remoteIP == nil { + remoteIP = netutils.ParseIPSloppy(req.RemoteAddr) + } + + // Don't duplicate remote IP if it's already the last address in the chain. + if remoteIP != nil && (len(srcIPs) == 0 || !remoteIP.Equal(srcIPs[len(srcIPs)-1])) { + srcIPs = append(srcIPs, remoteIP) + } + + return srcIPs +} + +// Checks whether the given IP address is contained in the list of IPs. +func containsIP(ips []net.IP, ip net.IP) bool { + for _, v := range ips { + if v.Equal(ip) { + return true + } + } + return false +} + +// Extracts and returns the clients IP from the given request. +// Looks at X-Forwarded-For header, X-Real-Ip header and request.RemoteAddr in that order. +// Returns nil if none of them are set or is set to an invalid value. +func GetClientIP(req *http.Request) net.IP { + ips := SourceIPs(req) + if len(ips) == 0 { + return nil + } + return ips[0] +} + +// Prepares the X-Forwarded-For header for another forwarding hop by appending the previous sender's +// IP address to the X-Forwarded-For chain. +func AppendForwardedForHeader(req *http.Request) { + // Copied from net/http/httputil/reverseproxy.go: + if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { + // If we aren't the first proxy retain prior + // X-Forwarded-For information as a comma+space + // separated list and fold multiple headers into one. + if prior, ok := req.Header["X-Forwarded-For"]; ok { + clientIP = strings.Join(prior, ", ") + ", " + clientIP + } + req.Header.Set("X-Forwarded-For", clientIP) + } +} + +var defaultProxyFuncPointer = fmt.Sprintf("%p", http.ProxyFromEnvironment) + +// isDefault checks to see if the transportProxierFunc is pointing to the default one +func isDefault(transportProxier func(*http.Request) (*url.URL, error)) bool { + transportProxierPointer := fmt.Sprintf("%p", transportProxier) + return transportProxierPointer == defaultProxyFuncPointer +} + +// NewProxierWithNoProxyCIDR constructs a Proxier function that respects CIDRs in NO_PROXY and delegates if +// no matching CIDRs are found +func NewProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error)) func(req *http.Request) (*url.URL, error) { + // we wrap the default method, so we only need to perform our check if the NO_PROXY (or no_proxy) envvar has a CIDR in it + noProxyEnv := os.Getenv("NO_PROXY") + if noProxyEnv == "" { + noProxyEnv = os.Getenv("no_proxy") + } + noProxyRules := strings.Split(noProxyEnv, ",") + + cidrs := []*net.IPNet{} + for _, noProxyRule := range noProxyRules { + _, cidr, _ := netutils.ParseCIDRSloppy(noProxyRule) + if cidr != nil { + cidrs = append(cidrs, cidr) + } + } + + if len(cidrs) == 0 { + return delegate + } + + return func(req *http.Request) (*url.URL, error) { + ip := netutils.ParseIPSloppy(req.URL.Hostname()) + if ip == nil { + return delegate(req) + } + + for _, cidr := range cidrs { + if cidr.Contains(ip) { + return nil, nil + } + } + + return delegate(req) + } +} + +// DialerFunc implements Dialer for the provided function. +type DialerFunc func(req *http.Request) (net.Conn, error) + +func (fn DialerFunc) Dial(req *http.Request) (net.Conn, error) { + return fn(req) +} + +// Dialer dials a host and writes a request to it. +type Dialer interface { + // Dial connects to the host specified by req's URL, writes the request to the connection, and + // returns the opened net.Conn. + Dial(req *http.Request) (net.Conn, error) +} + +// CloneRequest creates a shallow copy of the request along with a deep copy of the Headers. +func CloneRequest(req *http.Request) *http.Request { + r := new(http.Request) + + // shallow clone + *r = *req + + // deep copy headers + r.Header = CloneHeader(req.Header) + + return r +} + +// CloneHeader creates a deep copy of an http.Header. +func CloneHeader(in http.Header) http.Header { + out := make(http.Header, len(in)) + for key, values := range in { + newValues := make([]string, len(values)) + copy(newValues, values) + out[key] = newValues + } + return out +} + +// WarningHeader contains a single RFC2616 14.46 warnings header +type WarningHeader struct { + // Codeindicates the type of warning. 299 is a miscellaneous persistent warning + Code int + // Agent contains the name or pseudonym of the server adding the Warning header. + // A single "-" is recommended when agent is unknown. + Agent string + // Warning text + Text string +} + +// ParseWarningHeaders extract RFC2616 14.46 warnings headers from the specified set of header values. +// Multiple comma-separated warnings per header are supported. +// If errors are encountered on a header, the remainder of that header are skipped and subsequent headers are parsed. +// Returns successfully parsed warnings and any errors encountered. +func ParseWarningHeaders(headers []string) ([]WarningHeader, []error) { + var ( + results []WarningHeader + errs []error + ) + for _, header := range headers { + for len(header) > 0 { + result, remainder, err := ParseWarningHeader(header) + if err != nil { + errs = append(errs, err) + break + } + results = append(results, result) + header = remainder + } + } + return results, errs +} + +var ( + codeMatcher = regexp.MustCompile(`^[0-9]{3}$`) + wordDecoder = &mime.WordDecoder{} +) + +// ParseWarningHeader extracts one RFC2616 14.46 warning from the specified header, +// returning an error if the header does not contain a correctly formatted warning. +// Any remaining content in the header is returned. +func ParseWarningHeader(header string) (result WarningHeader, remainder string, err error) { + // https://tools.ietf.org/html/rfc2616#section-14.46 + // updated by + // https://tools.ietf.org/html/rfc7234#section-5.5 + // https://tools.ietf.org/html/rfc7234#appendix-A + // Some requirements regarding production and processing of the Warning + // header fields have been relaxed, as it is not widely implemented. + // Furthermore, the Warning header field no longer uses RFC 2047 + // encoding, nor does it allow multiple languages, as these aspects were + // not implemented. + // + // Format is one of: + // warn-code warn-agent "warn-text" + // warn-code warn-agent "warn-text" "warn-date" + // + // warn-code is a three digit number + // warn-agent is unquoted and contains no spaces + // warn-text is quoted with backslash escaping (RFC2047-encoded according to RFC2616, not encoded according to RFC7234) + // warn-date is optional, quoted, and in HTTP-date format (no embedded or escaped quotes) + // + // additional warnings can optionally be included in the same header by comma-separating them: + // warn-code warn-agent "warn-text" "warn-date"[, warn-code warn-agent "warn-text" "warn-date", ...] + + // tolerate leading whitespace + header = strings.TrimSpace(header) + + parts := strings.SplitN(header, " ", 3) + if len(parts) != 3 { + return WarningHeader{}, "", errors.New("invalid warning header: fewer than 3 segments") + } + code, agent, textDateRemainder := parts[0], parts[1], parts[2] + + // verify code format + if !codeMatcher.Match([]byte(code)) { + return WarningHeader{}, "", errors.New("invalid warning header: code segment is not 3 digits between 100-299") + } + codeInt, _ := strconv.ParseInt(code, 10, 64) + + // verify agent presence + if len(agent) == 0 { + return WarningHeader{}, "", errors.New("invalid warning header: empty agent segment") + } + if !utf8.ValidString(agent) || hasAnyRunes(agent, unicode.IsControl) { + return WarningHeader{}, "", errors.New("invalid warning header: invalid agent") + } + + // verify textDateRemainder presence + if len(textDateRemainder) == 0 { + return WarningHeader{}, "", errors.New("invalid warning header: empty text segment") + } + + // extract text + text, dateAndRemainder, err := parseQuotedString(textDateRemainder) + if err != nil { + return WarningHeader{}, "", fmt.Errorf("invalid warning header: %v", err) + } + // tolerate RFC2047-encoded text from warnings produced according to RFC2616 + if decodedText, err := wordDecoder.DecodeHeader(text); err == nil { + text = decodedText + } + if !utf8.ValidString(text) || hasAnyRunes(text, unicode.IsControl) { + return WarningHeader{}, "", errors.New("invalid warning header: invalid text") + } + result = WarningHeader{Code: int(codeInt), Agent: agent, Text: text} + + if len(dateAndRemainder) > 0 { + if dateAndRemainder[0] == '"' { + // consume date + foundEndQuote := false + for i := 1; i < len(dateAndRemainder); i++ { + if dateAndRemainder[i] == '"' { + foundEndQuote = true + remainder = strings.TrimSpace(dateAndRemainder[i+1:]) + break + } + } + if !foundEndQuote { + return WarningHeader{}, "", errors.New("invalid warning header: unterminated date segment") + } + } else { + remainder = dateAndRemainder + } + } + if len(remainder) > 0 { + if remainder[0] == ',' { + // consume comma if present + remainder = strings.TrimSpace(remainder[1:]) + } else { + return WarningHeader{}, "", errors.New("invalid warning header: unexpected token after warn-date") + } + } + + return result, remainder, nil +} + +func parseQuotedString(quotedString string) (string, string, error) { + if len(quotedString) == 0 { + return "", "", errors.New("invalid quoted string: 0-length") + } + + if quotedString[0] != '"' { + return "", "", errors.New("invalid quoted string: missing initial quote") + } + + quotedString = quotedString[1:] + var remainder string + escaping := false + closedQuote := false + result := &strings.Builder{} +loop: + for i := 0; i < len(quotedString); i++ { + b := quotedString[i] + switch b { + case '"': + if escaping { + result.WriteByte(b) + escaping = false + } else { + closedQuote = true + remainder = strings.TrimSpace(quotedString[i+1:]) + break loop + } + case '\\': + if escaping { + result.WriteByte(b) + escaping = false + } else { + escaping = true + } + default: + result.WriteByte(b) + escaping = false + } + } + + if !closedQuote { + return "", "", errors.New("invalid quoted string: missing closing quote") + } + return result.String(), remainder, nil +} + +func NewWarningHeader(code int, agent, text string) (string, error) { + if code < 0 || code > 999 { + return "", errors.New("code must be between 0 and 999") + } + if len(agent) == 0 { + agent = "-" + } else if !utf8.ValidString(agent) || strings.ContainsAny(agent, `\"`) || hasAnyRunes(agent, unicode.IsSpace, unicode.IsControl) { + return "", errors.New("agent must be valid UTF-8 and must not contain spaces, quotes, backslashes, or control characters") + } + if !utf8.ValidString(text) || hasAnyRunes(text, unicode.IsControl) { + return "", errors.New("text must be valid UTF-8 and must not contain control characters") + } + return fmt.Sprintf("%03d %s %s", code, agent, makeQuotedString(text)), nil +} + +func hasAnyRunes(s string, runeCheckers ...func(rune) bool) bool { + for _, r := range s { + for _, checker := range runeCheckers { + if checker(r) { + return true + } + } + } + return false +} + +func makeQuotedString(s string) string { + result := &bytes.Buffer{} + // opening quote + result.WriteRune('"') + for _, c := range s { + switch c { + case '"', '\\': + // escape " and \ + result.WriteRune('\\') + result.WriteRune(c) + default: + // write everything else as-is + result.WriteRune(c) + } + } + // closing quote + result.WriteRune('"') + return result.String() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/http_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/http_test.go new file mode 100644 index 0000000000..8cced587d2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/http_test.go @@ -0,0 +1,958 @@ +//go:build go1.8 + +/* +Copyright 2016 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 net + +import ( + "crypto/tls" + "fmt" + "io" + "net" + "net/http" + "net/url" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + netutils "k8s.io/utils/net" +) + +func TestGetClientIP(t *testing.T) { + ipString := "10.0.0.1" + ip := netutils.ParseIPSloppy(ipString) + invalidIPString := "invalidIPString" + testCases := []struct { + Request http.Request + ExpectedIP net.IP + }{ + { + Request: http.Request{}, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Real-Ip": {ipString}, + }, + }, + ExpectedIP: ip, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Real-Ip": {invalidIPString}, + }, + }, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Forwarded-For": {ipString}, + }, + }, + ExpectedIP: ip, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Forwarded-For": {invalidIPString}, + }, + }, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Forwarded-For": {invalidIPString + "," + ipString}, + }, + }, + ExpectedIP: ip, + }, + { + Request: http.Request{ + // RemoteAddr is in the form host:port + RemoteAddr: ipString + ":1234", + }, + ExpectedIP: ip, + }, + { + Request: http.Request{ + RemoteAddr: invalidIPString, + }, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Forwarded-For": {invalidIPString}, + }, + // RemoteAddr is in the form host:port + RemoteAddr: ipString, + }, + ExpectedIP: ip, + }, + } + + for i, test := range testCases { + if a, e := GetClientIP(&test.Request), test.ExpectedIP; reflect.DeepEqual(e, a) != true { + t.Fatalf("test case %d failed. expected: %v, actual: %v", i, e, a) + } + } +} + +func TestAppendForwardedForHeader(t *testing.T) { + testCases := []struct { + addr, forwarded, expected string + }{ + {"1.2.3.4:8000", "", "1.2.3.4"}, + {"1.2.3.4:8000", "8.8.8.8", "8.8.8.8, 1.2.3.4"}, + {"1.2.3.4:8000", "8.8.8.8, 1.2.3.4", "8.8.8.8, 1.2.3.4, 1.2.3.4"}, + {"1.2.3.4:8000", "foo,bar", "foo,bar, 1.2.3.4"}, + } + for i, test := range testCases { + req := &http.Request{ + RemoteAddr: test.addr, + Header: make(http.Header), + } + if test.forwarded != "" { + req.Header.Set("X-Forwarded-For", test.forwarded) + } + + AppendForwardedForHeader(req) + actual := req.Header.Get("X-Forwarded-For") + if actual != test.expected { + t.Errorf("[%d] Expected %q, Got %q", i, test.expected, actual) + } + } +} + +func TestProxierWithNoProxyCIDR(t *testing.T) { + testCases := []struct { + name string + noProxy string + url string + + expectedDelegated bool + }{ + { + name: "no env", + url: "https://192.168.143.1/api", + expectedDelegated: true, + }, + { + name: "no cidr", + noProxy: "192.168.63.1", + url: "https://192.168.143.1/api", + expectedDelegated: true, + }, + { + name: "hostname", + noProxy: "192.168.63.0/24,192.168.143.0/24", + url: "https://my-hostname/api", + expectedDelegated: true, + }, + { + name: "match second cidr", + noProxy: "192.168.63.0/24,192.168.143.0/24", + url: "https://192.168.143.1/api", + expectedDelegated: false, + }, + { + name: "match second cidr with host:port", + noProxy: "192.168.63.0/24,192.168.143.0/24", + url: "https://192.168.143.1:8443/api", + expectedDelegated: false, + }, + { + name: "IPv6 cidr", + noProxy: "2001:db8::/48", + url: "https://[2001:db8::1]/api", + expectedDelegated: false, + }, + { + name: "IPv6+port cidr", + noProxy: "2001:db8::/48", + url: "https://[2001:db8::1]:8443/api", + expectedDelegated: false, + }, + { + name: "IPv6, not matching cidr", + noProxy: "2001:db8::/48", + url: "https://[2001:db8:1::1]/api", + expectedDelegated: true, + }, + { + name: "IPv6+port, not matching cidr", + noProxy: "2001:db8::/48", + url: "https://[2001:db8:1::1]:8443/api", + expectedDelegated: true, + }, + } + + for _, test := range testCases { + t.Setenv("NO_PROXY", test.noProxy) + actualDelegated := false + proxyFunc := NewProxierWithNoProxyCIDR(func(req *http.Request) (*url.URL, error) { + actualDelegated = true + return nil, nil + }) + + req, err := http.NewRequest(http.MethodGet, test.url, nil) + if err != nil { + t.Errorf("%s: unexpected err: %v", test.name, err) + continue + } + if _, err := proxyFunc(req); err != nil { + t.Errorf("%s: unexpected err: %v", test.name, err) + continue + } + + if test.expectedDelegated != actualDelegated { + t.Errorf("%s: expected %v, got %v", test.name, test.expectedDelegated, actualDelegated) + continue + } + } +} + +type fakeTLSClientConfigHolder struct { + called bool +} + +func (f *fakeTLSClientConfigHolder) TLSClientConfig() *tls.Config { + f.called = true + return nil +} +func (f *fakeTLSClientConfigHolder) RoundTrip(*http.Request) (*http.Response, error) { + return nil, nil +} + +func TestTLSClientConfigHolder(t *testing.T) { + rt := &fakeTLSClientConfigHolder{} + TLSClientConfig(rt) + + if !rt.called { + t.Errorf("didn't find tls config") + } +} + +func TestJoinPreservingTrailingSlash(t *testing.T) { + tests := []struct { + a string + b string + want string + }{ + // All empty + {"", "", ""}, + + // Empty a + {"", "/", "/"}, + {"", "foo", "foo"}, + {"", "/foo", "/foo"}, + {"", "/foo/", "/foo/"}, + + // Empty b + {"/", "", "/"}, + {"foo", "", "foo"}, + {"/foo", "", "/foo"}, + {"/foo/", "", "/foo/"}, + + // Both populated + {"/", "/", "/"}, + {"foo", "foo", "foo/foo"}, + {"/foo", "/foo", "/foo/foo"}, + {"/foo/", "/foo/", "/foo/foo/"}, + } + for _, tt := range tests { + name := fmt.Sprintf("%q+%q=%q", tt.a, tt.b, tt.want) + t.Run(name, func(t *testing.T) { + if got := JoinPreservingTrailingSlash(tt.a, tt.b); got != tt.want { + t.Errorf("JoinPreservingTrailingSlash() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAllowsHTTP2(t *testing.T) { + testcases := []struct { + Name string + Transport *http.Transport + ExpectAllows bool + }{ + { + Name: "empty", + Transport: &http.Transport{}, + ExpectAllows: true, + }, + { + Name: "empty tlsconfig", + Transport: &http.Transport{TLSClientConfig: &tls.Config{}}, + ExpectAllows: true, + }, + { + Name: "zero-length NextProtos", + Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{}}}, + ExpectAllows: true, + }, + { + Name: "includes h2 in NextProtos after", + Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1", "h2"}}}, + ExpectAllows: true, + }, + { + Name: "includes h2 in NextProtos before", + Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"h2", "http/1.1"}}}, + ExpectAllows: true, + }, + { + Name: "includes h2 in NextProtos between", + Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1", "h2", "h3"}}}, + ExpectAllows: true, + }, + { + Name: "excludes h2 in NextProtos", + Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1"}}}, + ExpectAllows: false, + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + allows := allowsHTTP2(tc.Transport) + if allows != tc.ExpectAllows { + t.Errorf("expected %v, got %v", tc.ExpectAllows, allows) + } + }) + } +} + +func TestSourceIPs(t *testing.T) { + tests := []struct { + name string + realIP string + forwardedFor string + remoteAddr string + expected []string + }{{ + name: "no headers, missing remoteAddr", + expected: []string{}, + }, { + name: "no headers, just remoteAddr host:port", + remoteAddr: "1.2.3.4:555", + expected: []string{"1.2.3.4"}, + }, { + name: "no headers, just remoteAddr host", + remoteAddr: "1.2.3.4", + expected: []string{"1.2.3.4"}, + }, { + name: "empty forwarded-for chain", + forwardedFor: " ", + remoteAddr: "1.2.3.4", + expected: []string{"1.2.3.4"}, + }, { + name: "invalid forwarded-for chain", + forwardedFor: "garbage garbage values!", + remoteAddr: "1.2.3.4", + expected: []string{"1.2.3.4"}, + }, { + name: "partially invalid forwarded-for chain", + forwardedFor: "garbage garbage values!,4.5.6.7", + remoteAddr: "1.2.3.4", + expected: []string{"4.5.6.7", "1.2.3.4"}, + }, { + name: "valid forwarded-for chain", + forwardedFor: "120.120.120.126,2.2.2.2,4.5.6.7", + remoteAddr: "1.2.3.4", + expected: []string{"120.120.120.126", "2.2.2.2", "4.5.6.7", "1.2.3.4"}, + }, { + name: "valid forwarded-for chain with redundant remoteAddr", + forwardedFor: "2.2.2.2,1.2.3.4", + remoteAddr: "1.2.3.4", + expected: []string{"2.2.2.2", "1.2.3.4"}, + }, { + name: "invalid Real-Ip", + realIP: "garbage, just garbage!", + remoteAddr: "1.2.3.4", + expected: []string{"1.2.3.4"}, + }, { + name: "invalid Real-Ip with forwarded-for", + realIP: "garbage, just garbage!", + forwardedFor: "2.2.2.2", + remoteAddr: "1.2.3.4", + expected: []string{"2.2.2.2", "1.2.3.4"}, + }, { + name: "valid Real-Ip", + realIP: "2.2.2.2", + remoteAddr: "1.2.3.4", + expected: []string{"2.2.2.2", "1.2.3.4"}, + }, { + name: "redundant Real-Ip", + realIP: "1.2.3.4", + remoteAddr: "1.2.3.4", + expected: []string{"1.2.3.4"}, + }, { + name: "valid Real-Ip with forwarded-for", + realIP: "2.2.2.2", + forwardedFor: "120.120.120.126,4.5.6.7", + remoteAddr: "1.2.3.4", + expected: []string{"120.120.120.126", "4.5.6.7", "2.2.2.2", "1.2.3.4"}, + }, { + name: "redundant Real-Ip with forwarded-for", + realIP: "2.2.2.2", + forwardedFor: "120.120.120.126,2.2.2.2,4.5.6.7", + remoteAddr: "1.2.3.4", + expected: []string{"120.120.120.126", "2.2.2.2", "4.5.6.7", "1.2.3.4"}, + }, { + name: "full redundancy", + realIP: "1.2.3.4", + forwardedFor: "1.2.3.4", + remoteAddr: "1.2.3.4", + expected: []string{"1.2.3.4"}, + }, { + name: "full ipv6", + realIP: "abcd:ef01:2345:6789:abcd:ef01:2345:6789", + forwardedFor: "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111,0:1111:2222:3333:4444:5555:6666:7777", + remoteAddr: "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa", + expected: []string{ + "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111", + "0:1111:2222:3333:4444:5555:6666:7777", + "abcd:ef01:2345:6789:abcd:ef01:2345:6789", + "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa", + }, + }, { + name: "mixed ipv4 ipv6", + forwardedFor: "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111,1.2.3.4", + remoteAddr: "0:0:0:0:0:ffff:102:304", // ipv6 equivalent to 1.2.3.4 + expected: []string{ + "aaaa:bbbb:cccc:dddd:eeee:ffff:0:1111", + "1.2.3.4", + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req, _ := http.NewRequest(http.MethodGet, "https://cluster.k8s.io/apis/foobars/v1/foo/bar", nil) + req.RemoteAddr = test.remoteAddr + if test.forwardedFor != "" { + req.Header.Set("X-Forwarded-For", test.forwardedFor) + } + if test.realIP != "" { + req.Header.Set("X-Real-Ip", test.realIP) + } + + actualIPs := SourceIPs(req) + actual := make([]string, len(actualIPs)) + for i, ip := range actualIPs { + actual[i] = ip.String() + } + + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestParseWarningHeader(t *testing.T) { + tests := []struct { + name string + + header string + + wantResult WarningHeader + wantRemainder string + wantErr string + }{ + // invalid cases + { + name: "empty", + header: ``, + wantErr: "fewer than 3 segments", + }, + { + name: "bad code", + header: `A B`, + wantErr: "fewer than 3 segments", + }, + { + name: "short code", + header: `1 - "text"`, + wantErr: "not 3 digits", + }, + { + name: "bad code", + header: `A - "text"`, + wantErr: "not 3 digits", + }, + { + name: "invalid date quoting", + header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT `, + wantErr: "unterminated date segment", + }, + { + name: "invalid post-date", + header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" other`, + wantErr: "unexpected token after warn-date", + }, + { + name: "agent control character", + header: " 299 agent\u0000name \"text\"", + wantErr: "invalid agent", + }, + { + name: "agent non-utf8 character", + header: " 299 agent\xc5name \"text\"", + wantErr: "invalid agent", + }, + { + name: "text control character", + header: " 299 - \"text\u0000\"content", + wantErr: "invalid text", + }, + { + name: "text non-utf8 character", + header: " 299 - \"text\xc5\"content", + wantErr: "invalid text", + }, + + // valid cases + { + name: "ok", + header: `299 - "text"`, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text`}, + }, + { + name: "ok", + header: `299 - "text\"\\\a\b\c"`, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, + }, + // big code + { + name: "big code", + header: `321 - "text"`, + wantResult: WarningHeader{Code: 321, Agent: "-", Text: "text"}, + }, + // RFC 2047 decoding + { + name: "ok, rfc 2047, iso-8859-1, q", + header: `299 - "=?iso-8859-1?q?this=20is=20some=20text?="`, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `this is some text`}, + }, + { + name: "ok, rfc 2047, utf-8, b", + header: `299 - "=?UTF-8?B?VGhpcyBpcyBhIGhvcnNleTog8J+Qjg==?= And =?UTF-8?B?VGhpcyBpcyBhIGhvcnNleTog8J+Qjg==?="`, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `This is a horsey: 🐎 And This is a horsey: 🐎`}, + }, + { + name: "ok, rfc 2047, utf-8, q", + header: `299 - "=?UTF-8?Q?This is a \"horsey\": =F0=9F=90=8E?="`, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `This is a "horsey": 🐎`}, + }, + { + name: "ok, rfc 2047, unknown charset", + header: `299 - "=?UTF-9?Q?This is a horsey: =F0=9F=90=8E?="`, + wantResult: WarningHeader{Code: 299, Agent: "-", Text: `=?UTF-9?Q?This is a horsey: =F0=9F=90=8E?=`}, + }, + { + name: "ok with spaces", + header: ` 299 - "text\"\\\a\b\c" `, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, + }, + { + name: "ok with date", + header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" `, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, + }, + { + name: "ok with date and comma", + header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" , `, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, + }, + { + name: "ok with comma", + header: ` 299 - "text\"\\\a\b\c" , `, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, + }, + { + name: "ok with date and comma and remainder", + header: ` 299 - "text\"\\\a\b\c" "Tue, 15 Nov 1994 08:12:31 GMT" , remainder `, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, + wantRemainder: "remainder", + }, + { + name: "ok with comma and remainder", + header: ` 299 - "text\"\\\a\b\c" ,remainder text,second remainder`, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `text"\abc`}, + wantRemainder: "remainder text,second remainder", + }, + { + name: "ok with utf-8 content directly in warn-text", + header: ` 299 - "Test of Iñtërnâtiônàlizætiøn,💝🐹🌇⛔" `, + wantResult: WarningHeader{Code: 299, Agent: `-`, Text: `Test of Iñtërnâtiônàlizætiøn,💝🐹🌇⛔`}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotResult, gotRemainder, err := ParseWarningHeader(tt.header) + switch { + case err == nil && len(tt.wantErr) > 0: + t.Errorf("ParseWarningHeader() no error, expected error %q", tt.wantErr) + return + case err != nil && len(tt.wantErr) == 0: + t.Errorf("ParseWarningHeader() error %q, expected no error", err) + return + case err != nil && len(tt.wantErr) > 0 && !strings.Contains(err.Error(), tt.wantErr): + t.Errorf("ParseWarningHeader() error %q, expected error %q", err, tt.wantErr) + return + } + if err != nil { + return + } + if !reflect.DeepEqual(gotResult, tt.wantResult) { + t.Errorf("ParseWarningHeader() gotResult = %#v, want %#v", gotResult, tt.wantResult) + } + if gotRemainder != tt.wantRemainder { + t.Errorf("ParseWarningHeader() gotRemainder = %v, want %v", gotRemainder, tt.wantRemainder) + } + }) + } +} + +func TestNewWarningHeader(t *testing.T) { + tests := []struct { + name string + + code int + agent string + text string + + want string + wantErr string + }{ + // invalid cases + { + name: "code too low", + code: -1, + agent: `-`, + text: `example warning`, + wantErr: "between 0 and 999", + }, + { + name: "code too high", + code: 1000, + agent: `-`, + text: `example warning`, + wantErr: "between 0 and 999", + }, + { + name: "agent with space", + code: 299, + agent: `test agent`, + text: `example warning`, + wantErr: `agent must be valid`, + }, + { + name: "agent with newline", + code: 299, + agent: "test\nagent", + text: `example warning`, + wantErr: `agent must be valid`, + }, + { + name: "agent with backslash", + code: 299, + agent: `test\agent`, + text: `example warning`, + wantErr: `agent must be valid`, + }, + { + name: "agent with quote", + code: 299, + agent: `test"agent"`, + text: `example warning`, + wantErr: `agent must be valid`, + }, + { + name: "agent with control character", + code: 299, + agent: "test\u0000agent", + text: `example warning`, + wantErr: `agent must be valid`, + }, + { + name: "agent with non-UTF8", + code: 299, + agent: "test\xc5agent", + text: `example warning`, + wantErr: `agent must be valid`, + }, + { + name: "text with newline", + code: 299, + agent: `-`, + text: "Test of new\nline", + wantErr: "text must be valid", + }, + { + name: "text with control character", + code: 299, + agent: `-`, + text: "Test of control\u0000character", + wantErr: "text must be valid", + }, + { + name: "text with non-UTF8", + code: 299, + agent: `-`, + text: "Test of control\xc5character", + wantErr: "text must be valid", + }, + + { + name: "valid empty text", + code: 299, + agent: `-`, + text: ``, + want: `299 - ""`, + }, + { + name: "valid empty agent", + code: 299, + agent: ``, + text: `example warning`, + want: `299 - "example warning"`, + }, + { + name: "valid low code", + code: 1, + agent: `-`, + text: `example warning`, + want: `001 - "example warning"`, + }, + { + name: "valid high code", + code: 999, + agent: `-`, + text: `example warning`, + want: `999 - "example warning"`, + }, + { + name: "valid utf-8", + code: 299, + agent: `-`, + text: `Test of "Iñtërnâtiônàlizætiøn,💝🐹🌇⛔"`, + want: `299 - "Test of \"Iñtërnâtiônàlizætiøn,💝🐹🌇⛔\""`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewWarningHeader(tt.code, tt.agent, tt.text) + + switch { + case err == nil && len(tt.wantErr) > 0: + t.Fatalf("ParseWarningHeader() no error, expected error %q", tt.wantErr) + case err != nil && len(tt.wantErr) == 0: + t.Fatalf("ParseWarningHeader() error %q, expected no error", err) + case err != nil && len(tt.wantErr) > 0 && !strings.Contains(err.Error(), tt.wantErr): + t.Fatalf("ParseWarningHeader() error %q, expected error %q", err, tt.wantErr) + } + if err != nil { + return + } + + if got != tt.want { + t.Fatalf("NewWarningHeader() = %v, want %v", got, tt.want) + } + + roundTrip, remaining, err := ParseWarningHeader(got) + if err != nil { + t.Fatalf("error roundtripping: %v", err) + } + if len(remaining) > 0 { + t.Fatalf("unexpected remainder roundtripping: %s", remaining) + } + agent := tt.agent + if len(agent) == 0 { + agent = "-" + } + expect := WarningHeader{Code: tt.code, Agent: agent, Text: tt.text} + if roundTrip != expect { + t.Fatalf("after round trip, want:\n%#v\ngot\n%#v", expect, roundTrip) + } + }) + } +} + +func TestParseWarningHeaders(t *testing.T) { + tests := []struct { + name string + + headers []string + + want []WarningHeader + wantErrs []string + }{ + { + name: "empty", + headers: []string{}, + want: nil, + wantErrs: []string{}, + }, + { + name: "multi-header with error", + headers: []string{ + `299 - "warning 1.1",299 - "warning 1.2"`, + `299 - "warning 2", 299 - "warning unquoted`, + ` 299 - "warning 3.1" , 299 - "warning 3.2" `, + }, + want: []WarningHeader{ + {Code: 299, Agent: "-", Text: "warning 1.1"}, + {Code: 299, Agent: "-", Text: "warning 1.2"}, + {Code: 299, Agent: "-", Text: "warning 2"}, + {Code: 299, Agent: "-", Text: "warning 3.1"}, + {Code: 299, Agent: "-", Text: "warning 3.2"}, + }, + wantErrs: []string{"invalid warning header: invalid quoted string: missing closing quote"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotErrs := ParseWarningHeaders(tt.headers) + + switch { + case len(gotErrs) != len(tt.wantErrs): + t.Fatalf("ParseWarningHeader() got %v, expected %v", gotErrs, tt.wantErrs) + case len(gotErrs) == len(tt.wantErrs) && len(gotErrs) > 0: + gotErrStrings := []string{} + for _, err := range gotErrs { + gotErrStrings = append(gotErrStrings, err.Error()) + } + if !reflect.DeepEqual(gotErrStrings, tt.wantErrs) { + t.Fatalf("ParseWarningHeader() got %v, expected %v", gotErrs, tt.wantErrs) + } + } + if len(gotErrs) > 0 { + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseWarningHeaders() got %#v, want %#v", got, tt.want) + } + }) + } +} + +func TestIsProbableEOF(t *testing.T) { + tests := []struct { + name string + err error + expected bool + }{ + { + name: "with no error", + expected: false, + }, + { + name: "with EOF error", + err: io.EOF, + expected: true, + }, + { + name: "with unexpected EOF error", + err: io.ErrUnexpectedEOF, + expected: true, + }, + { + name: "with broken connection error", + err: fmt.Errorf("http: can't write HTTP request on broken connection"), + expected: true, + }, + { + name: "with server sent GOAWAY error", + err: fmt.Errorf("error foo - http2: server sent GOAWAY and closed the connection - error bar"), + expected: true, + }, + { + name: "with connection reset by peer error", + err: fmt.Errorf("error foo - connection reset by peer - error bar"), + expected: true, + }, + { + name: "with use of closed network connection error", + err: fmt.Errorf("error foo - Use of closed network connection - error bar"), + expected: true, + }, + { + name: "with url error", + err: &url.Error{ + Err: io.ErrUnexpectedEOF, + }, + expected: true, + }, + { + name: "with unrecognized error", + err: fmt.Errorf("error foo"), + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := IsProbableEOF(test.err) + assert.Equal(t, test.expected, actual) + }) + } +} + +func TestReadIdleTimeoutSeconds(t *testing.T) { + t.Setenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", "60") + if e, a := 60, readIdleTimeoutSeconds(); e != a { + t.Errorf("expected %d, got %d", e, a) + } + + t.Setenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS", "illegal value") + if e, a := 30, readIdleTimeoutSeconds(); e != a { + t.Errorf("expected %d, got %d", e, a) + } +} + +func TestPingTimeoutSeconds(t *testing.T) { + t.Setenv("HTTP2_PING_TIMEOUT_SECONDS", "60") + if e, a := 60, pingTimeoutSeconds(); e != a { + t.Errorf("expected %d, got %d", e, a) + } + + t.Setenv("HTTP2_PING_TIMEOUT_SECONDS", "illegal value") + if e, a := 15, pingTimeoutSeconds(); e != a { + t.Errorf("expected %d, got %d", e, a) + } +} + +func Benchmark_ParseQuotedString(b *testing.B) { + str := `"The quick brown" fox jumps over the lazy dog` + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + quoted, remainder, err := parseQuotedString(str) + if err != nil { + b.Errorf("Unexpected error %s", err) + } + if quoted != "The quick brown" { + b.Errorf("Unexpected quoted string %s", quoted) + } + if remainder != "fox jumps over the lazy dog" { + b.Errorf("Unexpected remainder string %s", quoted) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/interface.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/interface.go new file mode 100644 index 0000000000..3ccf227af0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/interface.go @@ -0,0 +1,532 @@ +/* +Copyright 2016 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 net + +import ( + "bufio" + "encoding/hex" + "fmt" + "io" + "net" + "os" + + "strings" + + "k8s.io/klog/v2" + netutils "k8s.io/utils/net" +) + +type AddressFamily uint + +const ( + familyIPv4 AddressFamily = 4 + familyIPv6 AddressFamily = 6 +) + +type AddressFamilyPreference []AddressFamily + +var ( + preferIPv4 = AddressFamilyPreference{familyIPv4, familyIPv6} + preferIPv6 = AddressFamilyPreference{familyIPv6, familyIPv4} +) + +const ( + // LoopbackInterfaceName is the default name of the loopback interface + LoopbackInterfaceName = "lo" +) + +const ( + ipv4RouteFile = "/proc/net/route" + ipv6RouteFile = "/proc/net/ipv6_route" +) + +type Route struct { + Interface string + Destination net.IP + Gateway net.IP + Family AddressFamily +} + +type RouteFile struct { + name string + parse func(input io.Reader) ([]Route, error) +} + +// noRoutesError can be returned in case of no routes +type noRoutesError struct { + message string +} + +func (e noRoutesError) Error() string { + return e.message +} + +// IsNoRoutesError checks if an error is of type noRoutesError +func IsNoRoutesError(err error) bool { + if err == nil { + return false + } + switch err.(type) { + case noRoutesError: + return true + default: + return false + } +} + +var ( + v4File = RouteFile{name: ipv4RouteFile, parse: getIPv4DefaultRoutes} + v6File = RouteFile{name: ipv6RouteFile, parse: getIPv6DefaultRoutes} +) + +func (rf RouteFile) extract() ([]Route, error) { + file, err := os.Open(rf.name) + if err != nil { + return nil, err + } + defer file.Close() + return rf.parse(file) +} + +// getIPv4DefaultRoutes obtains the IPv4 routes, and filters out non-default routes. +func getIPv4DefaultRoutes(input io.Reader) ([]Route, error) { + routes := []Route{} + scanner := bufio.NewReader(input) + for { + line, err := scanner.ReadString('\n') + if err == io.EOF { + break + } + //ignore the headers in the route info + if strings.HasPrefix(line, "Iface") { + continue + } + fields := strings.Fields(line) + // Interested in fields: + // 0 - interface name + // 1 - destination address + // 2 - gateway + dest, err := parseIP(fields[1], familyIPv4) + if err != nil { + return nil, err + } + gw, err := parseIP(fields[2], familyIPv4) + if err != nil { + return nil, err + } + if !dest.Equal(net.IPv4zero) { + continue + } + routes = append(routes, Route{ + Interface: fields[0], + Destination: dest, + Gateway: gw, + Family: familyIPv4, + }) + } + return routes, nil +} + +func getIPv6DefaultRoutes(input io.Reader) ([]Route, error) { + routes := []Route{} + scanner := bufio.NewReader(input) + for { + line, err := scanner.ReadString('\n') + if err == io.EOF { + break + } + fields := strings.Fields(line) + // Interested in fields: + // 0 - destination address + // 4 - gateway + // 9 - interface name + dest, err := parseIP(fields[0], familyIPv6) + if err != nil { + return nil, err + } + gw, err := parseIP(fields[4], familyIPv6) + if err != nil { + return nil, err + } + if !dest.Equal(net.IPv6zero) { + continue + } + if gw.Equal(net.IPv6zero) { + continue // loopback + } + routes = append(routes, Route{ + Interface: fields[9], + Destination: dest, + Gateway: gw, + Family: familyIPv6, + }) + } + return routes, nil +} + +// parseIP takes the hex IP address string from route file and converts it +// to a net.IP address. For IPv4, the value must be converted to big endian. +func parseIP(str string, family AddressFamily) (net.IP, error) { + if str == "" { + return nil, fmt.Errorf("input is nil") + } + bytes, err := hex.DecodeString(str) + if err != nil { + return nil, err + } + if family == familyIPv4 { + if len(bytes) != net.IPv4len { + return nil, fmt.Errorf("invalid IPv4 address in route") + } + return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil + } + // Must be IPv6 + if len(bytes) != net.IPv6len { + return nil, fmt.Errorf("invalid IPv6 address in route") + } + return net.IP(bytes), nil +} + +func isInterfaceUp(logger klog.Logger, intf *net.Interface) bool { + if intf == nil { + return false + } + if intf.Flags&net.FlagUp != 0 { + logger.V(4).Info("Interface is up", "interface", intf.Name) + return true + } + return false +} + +func isLoopbackOrPointToPoint(intf *net.Interface) bool { + return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0 +} + +// getMatchingGlobalIP returns the first valid global unicast address of the given +// 'family' from the list of 'addrs'. +func getMatchingGlobalIP(logger klog.Logger, addrs []net.Addr, family AddressFamily) (net.IP, error) { + if len(addrs) > 0 { + for i := range addrs { + logger.V(4).Info("Checking for matching global IP", "address", addrs[i]) + ip, _, err := netutils.ParseCIDRSloppy(addrs[i].String()) + if err != nil { + return nil, err + } + if memberOf(ip, family) { + if ip.IsGlobalUnicast() { + logger.V(4).Info("IP found", "IP", ip) + return ip, nil + } else { + logger.V(4).Info("Non-global unicast address found", "IP", ip) + } + } else { + logger.V(4).Info("IP address has wrong version", "IP", ip, "IPVersion", int(family)) + } + + } + } + return nil, nil +} + +// getIPFromInterface gets the IPs on an interface and returns a global unicast address, if any. The +// interface must be up, the IP must in the family requested, and the IP must be a global unicast address. +func getIPFromInterface(logger klog.Logger, intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { + intf, err := nw.InterfaceByName(intfName) + if err != nil { + return nil, err + } + if isInterfaceUp(logger, intf) { + addrs, err := nw.Addrs(intf) + if err != nil { + return nil, err + } + logger.V(4).Info("Found addresses for interface", "interface", intfName, "numAddresses", len(addrs), "addresses", addrs) + matchingIP, err := getMatchingGlobalIP(logger, addrs, forFamily) + if err != nil { + return nil, err + } + if matchingIP != nil { + logger.V(4).Info("Found valid address", "IPVersion", int(forFamily), "IP", matchingIP, "interface", intfName) + return matchingIP, nil + } + } + return nil, nil +} + +// getIPFromLoopbackInterface gets the IPs on a loopback interface and returns a global unicast address, if any. +// The loopback interface must be up, the IP must in the family requested, and the IP must be a global unicast address. +func getIPFromLoopbackInterface(logger klog.Logger, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) { + intfs, err := nw.Interfaces() + if err != nil { + return nil, err + } + for _, intf := range intfs { + if !isInterfaceUp(logger, &intf) { + continue + } + if intf.Flags&(net.FlagLoopback) != 0 { + addrs, err := nw.Addrs(&intf) + if err != nil { + return nil, err + } + logger.V(4).Info("Found addresses for interface", "interface", intf.Name, "numAddresses", len(addrs), "addresses", addrs) + matchingIP, err := getMatchingGlobalIP(logger, addrs, forFamily) + if err != nil { + return nil, err + } + if matchingIP != nil { + logger.V(4).Info("Found valid address", "IPVersion", int(forFamily), "IP", matchingIP, "interface", intf.Name) + return matchingIP, nil + } + } + } + return nil, nil +} + +// memberOf tells if the IP is of the desired family. Used for checking interface addresses. +func memberOf(ip net.IP, family AddressFamily) bool { + if ip.To4() != nil { + return family == familyIPv4 + } else { + return family == familyIPv6 + } +} + +// chooseIPFromHostInterfaces looks at all system interfaces, trying to find one that is up that +// has a global unicast address (non-loopback, non-link local, non-point2point), and returns the IP. +// addressFamilies determines whether it prefers IPv4 or IPv6 +func chooseIPFromHostInterfaces(logger klog.Logger, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) { + intfs, err := nw.Interfaces() + if err != nil { + return nil, err + } + if len(intfs) == 0 { + return nil, fmt.Errorf("no interfaces found on host.") + } + for _, family := range addressFamilies { + logger.V(4).Info("Looking for system interface with a global address", "IPVersion", uint(family)) + for _, intf := range intfs { + if !isInterfaceUp(logger, &intf) { + logger.V(4).Info("Skipping: interface is down", "interface", intf.Name) + continue + } + if isLoopbackOrPointToPoint(&intf) { + logger.V(4).Info("Skipping: is LB or P2P", "interface", intf.Name) + continue + } + addrs, err := nw.Addrs(&intf) + if err != nil { + return nil, err + } + if len(addrs) == 0 { + logger.V(4).Info("Skipping: no addresses", "interface", intf.Name) + continue + } + for _, addr := range addrs { + ip, _, err := netutils.ParseCIDRSloppy(addr.String()) + if err != nil { + return nil, fmt.Errorf("unable to parse CIDR for interface %q: %s", intf.Name, err) + } + if !memberOf(ip, family) { + logger.V(4).Info("Skipping: no address family match", "IP", ip, "interface", intf.Name) + continue + } + // TODO: Decide if should open up to allow IPv6 LLAs in future. + if !ip.IsGlobalUnicast() { + logger.V(4).Info("Skipping: non-global address", "IP", ip, "interface", intf.Name) + continue + } + logger.V(4).Info("Found global unicast address", "IP", ip, "interface", intf.Name) + return ip, nil + } + } + } + return nil, fmt.Errorf("no acceptable interface with global unicast address found on host") +} + +// ChooseHostInterface is a method used fetch an IP for a daemon. +// If there is no routing info file, it will choose a global IP from the system +// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the +// IP of the interface with a gateway on it (with priority given to IPv4). For a node +// with no internet connection, it returns error. +// +//logcheck:context // [ChooseHostInterfaceWithLogger] should be used instead of ChooseHostInterface in code which supports contextual logging. +func ChooseHostInterface() (net.IP, error) { + return ChooseHostInterfaceWithLogger(klog.Background()) +} + +// ChooseHostInterfaceWithLogger is a method used fetch an IP for a daemon. +// If there is no routing info file, it will choose a global IP from the system +// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the +// IP of the interface with a gateway on it (with priority given to IPv4). For a node +// with no internet connection, it returns error. +func ChooseHostInterfaceWithLogger(logger klog.Logger) (net.IP, error) { + return chooseHostInterface(logger, preferIPv4) +} + +func chooseHostInterface(logger klog.Logger, addressFamilies AddressFamilyPreference) (net.IP, error) { + var nw networkInterfacer = networkInterface{} + if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) { + return chooseIPFromHostInterfaces(logger, nw, addressFamilies) + } + routes, err := getAllDefaultRoutes() + if err != nil { + return nil, err + } + return chooseHostInterfaceFromRoute(logger, routes, nw, addressFamilies) +} + +// networkInterfacer defines an interface for several net library functions. Production +// code will forward to net library functions, and unit tests will override the methods +// for testing purposes. +type networkInterfacer interface { + InterfaceByName(intfName string) (*net.Interface, error) + Addrs(intf *net.Interface) ([]net.Addr, error) + Interfaces() ([]net.Interface, error) +} + +// networkInterface implements the networkInterfacer interface for production code, just +// wrapping the underlying net library function calls. +type networkInterface struct{} + +func (_ networkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return net.InterfaceByName(intfName) +} + +func (_ networkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + return intf.Addrs() +} + +func (_ networkInterface) Interfaces() ([]net.Interface, error) { + return net.Interfaces() +} + +// getAllDefaultRoutes obtains IPv4 and IPv6 default routes on the node. If unable +// to read the IPv4 routing info file, we return an error. If unable to read the IPv6 +// routing info file (which is optional), we'll just use the IPv4 route information. +// Using all the routing info, if no default routes are found, an error is returned. +func getAllDefaultRoutes() ([]Route, error) { + routes, err := v4File.extract() + if err != nil { + return nil, err + } + v6Routes, _ := v6File.extract() + routes = append(routes, v6Routes...) + if len(routes) == 0 { + return nil, noRoutesError{ + message: fmt.Sprintf("no default routes found in %q or %q", v4File.name, v6File.name), + } + } + return routes, nil +} + +// chooseHostInterfaceFromRoute cycles through each default route provided, looking for a +// global IP address from the interface for the route. If there are routes but no global +// address is obtained from the interfaces, it checks if the loopback interface has a global address. +// addressFamilies determines whether it prefers IPv4 or IPv6 +func chooseHostInterfaceFromRoute(logger klog.Logger, routes []Route, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) { + for _, family := range addressFamilies { + logger.V(4).Info("Looking for default routes with IP addresses", "IPVersion", uint(family)) + for _, route := range routes { + if route.Family != family { + continue + } + logger.V(4).Info("Default route transits interface", "interface", route.Interface) + finalIP, err := getIPFromInterface(logger, route.Interface, family, nw) + if err != nil { + return nil, err + } + if finalIP != nil { + logger.V(4).Info("Found active IP", "IP", finalIP) + return finalIP, nil + } + // In case of network setups where default routes are present, but network + // interfaces use only link-local addresses (e.g. as described in RFC5549). + // the global IP is assigned to the loopback interface, and we should use it + loopbackIP, err := getIPFromLoopbackInterface(logger, family, nw) + if err != nil { + return nil, err + } + if loopbackIP != nil { + logger.V(4).Info("Found active IP on Loopback interface", "IP", loopbackIP) + return loopbackIP, nil + } + } + } + logger.V(4).Info("No active IP found by looking at default routes") + return nil, fmt.Errorf("unable to select an IP from default routes.") +} + +// ResolveBindAddress returns the IP address of a daemon, based on the given bindAddress: +// If bindAddress is unset, it returns the host's default IP, as with ChooseHostInterface(). +// If bindAddress is unspecified or loopback, it returns the default IP of the same +// address family as bindAddress. +// Otherwise, it just returns bindAddress. +// +//logcheck:context // [ResolveBindAddressWithLogger] should be used instead of ResolveBindAddress in code which supports contextual logging. +func ResolveBindAddress(bindAddress net.IP) (net.IP, error) { + return ResolveBindAddressWithLogger(klog.Background(), bindAddress) +} + +// ResolveBindAddressWithLogger returns the IP address of a daemon, based on the given bindAddress: +// If bindAddress is unset, it returns the host's default IP, as with ChooseHostInterface(). +// If bindAddress is unspecified or loopback, it returns the default IP of the same +// address family as bindAddress. +// Otherwise, it just returns bindAddress. +func ResolveBindAddressWithLogger(logger klog.Logger, bindAddress net.IP) (net.IP, error) { + addressFamilies := preferIPv4 + if bindAddress != nil && memberOf(bindAddress, familyIPv6) { + addressFamilies = preferIPv6 + } + + if bindAddress == nil || bindAddress.IsUnspecified() || bindAddress.IsLoopback() { + hostIP, err := chooseHostInterface(logger, addressFamilies) + if err != nil { + return nil, err + } + bindAddress = hostIP + } + return bindAddress, nil +} + +// ChooseBindAddressForInterface choose a global IP for a specific interface, with priority given to IPv4. +// This is required in case of network setups where default routes are present, but network +// interfaces use only link-local addresses (e.g. as described in RFC5549). +// e.g when using BGP to announce a host IP over link-local ip addresses and this ip address is attached to the lo interface. +// +//logcheck:context // [ChooseBindAddressForInterfaceWithLogger] should be used instead of ChooseBindAddressForInterface in code which supports contextual logging. +func ChooseBindAddressForInterface(intfName string) (net.IP, error) { + return ChooseBindAddressForInterfaceWithLogger(klog.Background(), intfName) +} + +// ChooseBindAddressForInterfaceWithLogger choose a global IP for a specific interface, with priority given to IPv4. +// This is required in case of network setups where default routes are present, but network +// interfaces use only link-local addresses (e.g. as described in RFC5549). +// e.g when using BGP to announce a host IP over link-local ip addresses and this ip address is attached to the lo interface. +func ChooseBindAddressForInterfaceWithLogger(logger klog.Logger, intfName string) (net.IP, error) { + var nw networkInterfacer = networkInterface{} + for _, family := range preferIPv4 { + ip, err := getIPFromInterface(logger, intfName, family, nw) + if err != nil { + return nil, err + } + if ip != nil { + return ip, nil + } + } + return nil, fmt.Errorf("unable to select an IP from %s network interface", intfName) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/interface_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/interface_test.go new file mode 100644 index 0000000000..b791aa1090 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/interface_test.go @@ -0,0 +1,828 @@ +/* +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 net + +import ( + "fmt" + "net" + "os" + "strings" + "testing" + + "k8s.io/klog/v2/ktesting" + netutils "k8s.io/utils/net" +) + +const gatewayfirst = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 +eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 +docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 +virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +` +const gatewaylast = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 +virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 +eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 +` +const gatewaymiddle = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 +docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 +eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 +virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +` +const noInternetConnection = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 +virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +` +const nothing = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +` +const badDestination = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +eth3 00000000 0100FE0A 0003 0 0 1024 00000000 0 0 0 +eth3 0000FE0AA1 00000000 0001 0 0 0 0080FFFF 0 0 0 +docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 +virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +` +const badGateway = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +eth3 00000000 0100FE0AA1 0003 0 0 1024 00000000 0 0 0 +eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 +docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 +virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +` +const route_Invalidhex = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +eth3 00000000 0100FE0AA 0003 0 0 1024 00000000 0 0 0 +eth3 0000FE0A 00000000 0001 0 0 0 0080FFFF 0 0 0 +docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0 +virbr0 007AA8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +` + +const v6gatewayfirst = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3 +20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 +00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo +` +const v6gatewaylast = `20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 +00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo +00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3 +` +const v6gatewaymiddle = `20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 +00000000000000000000000000000000 00 00000000000000000000000000000000 00 20010001000000000000000000000001 00000064 00000000 00000000 00000003 eth3 +00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo +` +const v6noDefaultRoutes = `00000000000000000000000000000000 60 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo +20010001000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00000001 docker0 +20010002000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 +fe800000000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 eth3 +` +const v6nothing = `` +const v6badDestination = `2001000200000000 7a 00000000000000000000000000000000 00 00000000000000000000000000000000 00000400 00000000 00000000 00200200 lo +` +const v6badGateway = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 200100010000000000000000000000000012 00000064 00000000 00000000 00000003 eth3 +` +const v6route_Invalidhex = `000000000000000000000000000000000 00 00000000000000000000000000000000 00 fe80000000000000021fcafffea0ec00 00000064 00000000 00000000 00000003 enp1s0f0 + +` + +const ( + flagUp = net.FlagUp | net.FlagBroadcast | net.FlagMulticast + flagDown = net.FlagBroadcast | net.FlagMulticast + flagLoopback = net.FlagUp | net.FlagLoopback + flagP2P = net.FlagUp | net.FlagPointToPoint +) + +func makeIntf(index int, name string, flags net.Flags) net.Interface { + mac := net.HardwareAddr{0, 0x32, 0x7d, 0x69, 0xf7, byte(0x30 + index)} + return net.Interface{ + Index: index, + MTU: 1500, + Name: name, + HardwareAddr: mac, + Flags: flags} +} + +var ( + downIntf = makeIntf(1, "eth3", flagDown) + loopbackIntf = makeIntf(1, "lo", flagLoopback) + p2pIntf = makeIntf(1, "lo", flagP2P) + upIntf = makeIntf(1, "eth3", flagUp) +) + +var ( + ipv4Route = Route{Interface: "eth3", Destination: netutils.ParseIPSloppy("0.0.0.0"), Gateway: netutils.ParseIPSloppy("10.254.0.1"), Family: familyIPv4} + ipv6Route = Route{Interface: "eth3", Destination: netutils.ParseIPSloppy("::"), Gateway: netutils.ParseIPSloppy("2001:1::1"), Family: familyIPv6} +) + +var ( + noRoutes = []Route{} + routeV4 = []Route{ipv4Route} + routeV6 = []Route{ipv6Route} + bothRoutes = []Route{ipv4Route, ipv6Route} +) + +func TestGetIPv4Routes(t *testing.T) { + testCases := []struct { + tcase string + route string + count int + expected *Route + errStrFrag string + }{ + {"gatewayfirst", gatewayfirst, 1, &ipv4Route, ""}, + {"gatewaymiddle", gatewaymiddle, 1, &ipv4Route, ""}, + {"gatewaylast", gatewaylast, 1, &ipv4Route, ""}, + {"no routes", nothing, 0, nil, ""}, + {"badDestination", badDestination, 0, nil, "invalid IPv4"}, + {"badGateway", badGateway, 0, nil, "invalid IPv4"}, + {"route_Invalidhex", route_Invalidhex, 0, nil, "odd length hex string"}, + {"no default routes", noInternetConnection, 0, nil, ""}, + } + for _, tc := range testCases { + r := strings.NewReader(tc.route) + routes, err := getIPv4DefaultRoutes(r) + if err != nil { + if !strings.Contains(err.Error(), tc.errStrFrag) { + t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) + } + } else if tc.errStrFrag != "" { + t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) + } else { + if tc.count != len(routes) { + t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes) + } else if tc.count == 1 { + if !tc.expected.Gateway.Equal(routes[0].Gateway) { + t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err) + } + if !routes[0].Destination.Equal(net.IPv4zero) { + t.Errorf("case[%s}: destination is not for default route (not zero)", tc.tcase) + } + + } + } + } +} + +func TestGetIPv6Routes(t *testing.T) { + testCases := []struct { + tcase string + route string + count int + expected *Route + errStrFrag string + }{ + {"v6 gatewayfirst", v6gatewayfirst, 1, &ipv6Route, ""}, + {"v6 gatewaymiddle", v6gatewaymiddle, 1, &ipv6Route, ""}, + {"v6 gatewaylast", v6gatewaylast, 1, &ipv6Route, ""}, + {"v6 no routes", v6nothing, 0, nil, ""}, + {"v6 badDestination", v6badDestination, 0, nil, "invalid IPv6"}, + {"v6 badGateway", v6badGateway, 0, nil, "invalid IPv6"}, + {"v6 route_Invalidhex", v6route_Invalidhex, 0, nil, "odd length hex string"}, + {"v6 no default routes", v6noDefaultRoutes, 0, nil, ""}, + } + for _, tc := range testCases { + r := strings.NewReader(tc.route) + routes, err := getIPv6DefaultRoutes(r) + if err != nil { + if !strings.Contains(err.Error(), tc.errStrFrag) { + t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) + } + } else if tc.errStrFrag != "" { + t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) + } else { + if tc.count != len(routes) { + t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes) + } else if tc.count == 1 { + if !tc.expected.Gateway.Equal(routes[0].Gateway) { + t.Errorf("case[%s]: expected %v, got %v .err : %v", tc.tcase, tc.expected, routes, err) + } + if !routes[0].Destination.Equal(net.IPv6zero) { + t.Errorf("case[%s}: destination is not for default route (not zero)", tc.tcase) + } + } + } + } +} + +func TestParseIP(t *testing.T) { + testCases := []struct { + tcase string + ip string + family AddressFamily + success bool + expected net.IP + }{ + {"empty", "", familyIPv4, false, nil}, + {"too short", "AA", familyIPv4, false, nil}, + {"too long", "0011223344", familyIPv4, false, nil}, + {"invalid", "invalid!", familyIPv4, false, nil}, + {"zero", "00000000", familyIPv4, true, net.IP{0, 0, 0, 0}}, + {"ffff", "FFFFFFFF", familyIPv4, true, net.IP{0xff, 0xff, 0xff, 0xff}}, + {"valid v4", "12345678", familyIPv4, true, net.IP{120, 86, 52, 18}}, + {"valid v6", "fe800000000000000000000000000000", familyIPv6, true, net.IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {"v6 too short", "fe80000000000000021fcafffea0ec0", familyIPv6, false, nil}, + {"v6 too long", "fe80000000000000021fcafffea0ec002", familyIPv6, false, nil}, + } + for _, tc := range testCases { + ip, err := parseIP(tc.ip, tc.family) + if !ip.Equal(tc.expected) { + t.Errorf("case[%v]: expected %q, got %q . err : %v", tc.tcase, tc.expected, ip, err) + } + } +} + +func TestIsInterfaceUp(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + testCases := []struct { + tcase string + intf *net.Interface + expected bool + }{ + {"up", &net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: net.FlagUp}, true}, + {"down", &net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: 0}, false}, + {"no interface", nil, false}, + } + for _, tc := range testCases { + it := isInterfaceUp(logger, tc.intf) + if it != tc.expected { + t.Errorf("case[%v]: expected %v, got %v .", tc.tcase, tc.expected, it) + } + } +} + +type addrStruct struct{ val string } + +func (a addrStruct) Network() string { + return a.val +} +func (a addrStruct) String() string { + return a.val +} + +func TestFinalIP(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + testCases := []struct { + tcase string + addr []net.Addr + family AddressFamily + expected net.IP + }{ + {"no ipv4", []net.Addr{addrStruct{val: "2001::5/64"}}, familyIPv4, nil}, + {"no ipv6", []net.Addr{addrStruct{val: "10.128.0.4/32"}}, familyIPv6, nil}, + {"invalidV4CIDR", []net.Addr{addrStruct{val: "10.20.30.40.50/24"}}, familyIPv4, nil}, + {"invalidV6CIDR", []net.Addr{addrStruct{val: "fe80::2f7:67fff:fe6e:2956/64"}}, familyIPv6, nil}, + {"loopback", []net.Addr{addrStruct{val: "127.0.0.1/24"}}, familyIPv4, nil}, + {"loopbackv6", []net.Addr{addrStruct{val: "::1/128"}}, familyIPv6, nil}, + {"link local v4", []net.Addr{addrStruct{val: "169.254.1.10/16"}}, familyIPv4, nil}, + {"link local v6", []net.Addr{addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}}, familyIPv6, nil}, + {"ip4", []net.Addr{addrStruct{val: "10.254.12.132/17"}}, familyIPv4, netutils.ParseIPSloppy("10.254.12.132")}, + {"ip6", []net.Addr{addrStruct{val: "2001::5/64"}}, familyIPv6, netutils.ParseIPSloppy("2001::5")}, + + {"no addresses", []net.Addr{}, familyIPv4, nil}, + } + for _, tc := range testCases { + ip, err := getMatchingGlobalIP(logger, tc.addr, tc.family) + if !ip.Equal(tc.expected) { + t.Errorf("case[%v]: expected %v, got %v .err : %v", tc.tcase, tc.expected, ip, err) + } + } +} + +func TestAddrs(t *testing.T) { + var nw networkInterfacer = validNetworkInterface{} + intf := net.Interface{Index: 0, MTU: 0, Name: "eth3", HardwareAddr: nil, Flags: 0} + addrs, err := nw.Addrs(&intf) + if err != nil { + t.Errorf("expected no error got : %v", err) + } + if len(addrs) != 2 { + t.Errorf("expected addrs: 2 got null") + } +} + +// Has a valid IPv4 address (IPv6 is LLA) +type validNetworkInterface struct { +} + +func (_ validNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return &upIntf, nil +} +func (_ validNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{ + addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}, addrStruct{val: "10.254.71.145/17"}} + return ifat, nil +} +func (_ validNetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf}, nil +} + +// Both IPv4 and IPv6 addresses (expecting IPv4 to be used) +type v4v6NetworkInterface struct { +} + +func (_ v4v6NetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return &upIntf, nil +} +func (_ v4v6NetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{ + addrStruct{val: "2001::10/64"}, addrStruct{val: "10.254.71.145/17"}} + return ifat, nil +} +func (_ v4v6NetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf}, nil +} + +// Interface with only IPv6 address +type ipv6NetworkInterface struct { +} + +func (_ ipv6NetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return &upIntf, nil +} +func (_ ipv6NetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{addrStruct{val: "2001::200/64"}} + return ifat, nil +} + +func (_ ipv6NetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf}, nil +} + +// Only with link local addresses +type networkInterfaceWithOnlyLinkLocals struct { +} + +func (_ networkInterfaceWithOnlyLinkLocals) InterfaceByName(intfName string) (*net.Interface, error) { + return &upIntf, nil +} +func (_ networkInterfaceWithOnlyLinkLocals) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"}} + return ifat, nil +} +func (_ networkInterfaceWithOnlyLinkLocals) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf}, nil +} + +// Unable to get interface(s) +type failGettingNetworkInterface struct { +} + +func (_ failGettingNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return nil, fmt.Errorf("unable get Interface") +} +func (_ failGettingNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + return nil, nil +} +func (_ failGettingNetworkInterface) Interfaces() ([]net.Interface, error) { + return nil, fmt.Errorf("mock failed getting all interfaces") +} + +// No interfaces +type noNetworkInterface struct { +} + +func (_ noNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return nil, fmt.Errorf("no such network interface") +} +func (_ noNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + return nil, nil +} +func (_ noNetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{}, nil +} + +// Interface is down +type downNetworkInterface struct { +} + +func (_ downNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return &downIntf, nil +} +func (_ downNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{ + addrStruct{val: "fe80::2f7:6fff:fe6e:2956/64"}, addrStruct{val: "10.254.71.145/17"}} + return ifat, nil +} +func (_ downNetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{downIntf}, nil +} + +// Loopback interface +type loopbackNetworkInterface struct { +} + +func (_ loopbackNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return &loopbackIntf, nil +} +func (_ loopbackNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{ + addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}} + return ifat, nil +} +func (_ loopbackNetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{loopbackIntf}, nil +} + +// Point to point interface +type p2pNetworkInterface struct { +} + +func (_ p2pNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + return &p2pIntf, nil +} +func (_ p2pNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{ + addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}} + return ifat, nil +} +func (_ p2pNetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{p2pIntf}, nil +} + +// Interface with link locals and loopback interface with global addresses +type linkLocalLoopbackNetworkInterface struct { +} + +func (_ linkLocalLoopbackNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + if intfName == LoopbackInterfaceName { + return &loopbackIntf, nil + } + return &upIntf, nil +} +func (_ linkLocalLoopbackNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"}} + if intf.Name == LoopbackInterfaceName { + ifat = []net.Addr{addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}, + // global addresses on loopback interface + addrStruct{val: "10.1.1.1/32"}, addrStruct{val: "fd00:1:1::1/128"}} + } + return ifat, nil +} +func (_ linkLocalLoopbackNetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf, loopbackIntf}, nil +} + +// Interface and loopback interface with global addresses +type globalsNetworkInterface struct { +} + +func (_ globalsNetworkInterface) InterfaceByName(intfName string) (*net.Interface, error) { + if intfName == LoopbackInterfaceName { + return &loopbackIntf, nil + } + return &upIntf, nil +} +func (_ globalsNetworkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{addrStruct{val: "169.254.162.166/16"}, addrStruct{val: "fe80::200/10"}, + addrStruct{val: "192.168.1.1/31"}, addrStruct{val: "fd00::200/127"}} + if intf.Name == LoopbackInterfaceName { + ifat = []net.Addr{addrStruct{val: "::1/128"}, addrStruct{val: "127.0.0.1/8"}, + // global addresses on loopback interface + addrStruct{val: "10.1.1.1/32"}, addrStruct{val: "fd00:1:1::1/128"}} + } + return ifat, nil +} +func (_ globalsNetworkInterface) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf, loopbackIntf}, nil +} + +// Unable to get IP addresses for interface +type networkInterfaceFailGetAddrs struct { +} + +func (_ networkInterfaceFailGetAddrs) InterfaceByName(intfName string) (*net.Interface, error) { + return &upIntf, nil +} +func (_ networkInterfaceFailGetAddrs) Addrs(intf *net.Interface) ([]net.Addr, error) { + return nil, fmt.Errorf("unable to get Addrs") +} +func (_ networkInterfaceFailGetAddrs) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf}, nil +} + +// No addresses for interface +type networkInterfaceWithNoAddrs struct { +} + +func (_ networkInterfaceWithNoAddrs) InterfaceByName(intfName string) (*net.Interface, error) { + return &upIntf, nil +} +func (_ networkInterfaceWithNoAddrs) Addrs(intf *net.Interface) ([]net.Addr, error) { + ifat := []net.Addr{} + return ifat, nil +} +func (_ networkInterfaceWithNoAddrs) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf}, nil +} + +// Invalid addresses for interface +type networkInterfaceWithInvalidAddr struct { +} + +func (_ networkInterfaceWithInvalidAddr) InterfaceByName(intfName string) (*net.Interface, error) { + return &upIntf, nil +} +func (_ networkInterfaceWithInvalidAddr) Addrs(intf *net.Interface) ([]net.Addr, error) { + var ifat []net.Addr + ifat = []net.Addr{addrStruct{val: "10.20.30.40.50/24"}} + return ifat, nil +} +func (_ networkInterfaceWithInvalidAddr) Interfaces() ([]net.Interface, error) { + return []net.Interface{upIntf}, nil +} + +func TestGetIPFromInterface(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + testCases := []struct { + tcase string + nwname string + family AddressFamily + nw networkInterfacer + expected net.IP + errStrFrag string + }{ + {"ipv4", "eth3", familyIPv4, validNetworkInterface{}, netutils.ParseIPSloppy("10.254.71.145"), ""}, + {"ipv6", "eth3", familyIPv6, ipv6NetworkInterface{}, netutils.ParseIPSloppy("2001::200"), ""}, + {"no ipv4", "eth3", familyIPv4, ipv6NetworkInterface{}, nil, ""}, + {"no ipv6", "eth3", familyIPv6, validNetworkInterface{}, nil, ""}, + {"I/F down", "eth3", familyIPv4, downNetworkInterface{}, nil, ""}, + {"I/F get fail", "eth3", familyIPv4, noNetworkInterface{}, nil, "no such network interface"}, + {"fail get addr", "eth3", familyIPv4, networkInterfaceFailGetAddrs{}, nil, "unable to get Addrs"}, + {"bad addr", "eth3", familyIPv4, networkInterfaceWithInvalidAddr{}, nil, "invalid CIDR"}, + } + for _, tc := range testCases { + ip, err := getIPFromInterface(logger, tc.nwname, tc.family, tc.nw) + if err != nil { + if !strings.Contains(err.Error(), tc.errStrFrag) { + t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) + } + } else if tc.errStrFrag != "" { + t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) + } else if !ip.Equal(tc.expected) { + t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err) + } + } +} + +func TestGetIPFromLoopbackInterface(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + testCases := []struct { + tcase string + family AddressFamily + nw networkInterfacer + expected net.IP + errStrFrag string + }{ + {"ipv4", familyIPv4, linkLocalLoopbackNetworkInterface{}, netutils.ParseIPSloppy("10.1.1.1"), ""}, + {"ipv6", familyIPv6, linkLocalLoopbackNetworkInterface{}, netutils.ParseIPSloppy("fd00:1:1::1"), ""}, + {"no global ipv4", familyIPv4, loopbackNetworkInterface{}, nil, ""}, + {"no global ipv6", familyIPv6, loopbackNetworkInterface{}, nil, ""}, + } + for _, tc := range testCases { + ip, err := getIPFromLoopbackInterface(logger, tc.family, tc.nw) + if err != nil { + if !strings.Contains(err.Error(), tc.errStrFrag) { + t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) + } + } else if tc.errStrFrag != "" { + t.Errorf("case[%s]: Error %q expected, but seen %v", tc.tcase, tc.errStrFrag, err) + } else if !ip.Equal(tc.expected) { + t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err) + } + } +} + +func TestChooseHostInterfaceFromRoute(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + testCases := []struct { + tcase string + routes []Route + nw networkInterfacer + order AddressFamilyPreference + expected net.IP + }{ + {"single-stack ipv4", routeV4, validNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.254.71.145")}, + {"single-stack ipv4, prefer v6", routeV4, validNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("10.254.71.145")}, + {"single-stack ipv6", routeV6, ipv6NetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("2001::200")}, + {"single-stack ipv6, prefer v6", routeV6, ipv6NetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("2001::200")}, + {"dual stack", bothRoutes, v4v6NetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.254.71.145")}, + {"dual stack, prefer v6", bothRoutes, v4v6NetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("2001::10")}, + {"LLA and loopback with global, IPv4", routeV4, linkLocalLoopbackNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.1.1.1")}, + {"LLA and loopback with global, IPv6", routeV6, linkLocalLoopbackNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("fd00:1:1::1")}, + {"LLA and loopback with global, dual stack prefer IPv4", bothRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.1.1.1")}, + {"LLA and loopback with global, dual stack prefer IPv6", bothRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("fd00:1:1::1")}, + {"LLA and loopback with global, no routes", noRoutes, linkLocalLoopbackNetworkInterface{}, preferIPv6, nil}, + {"interface and loopback with global, IPv4", routeV4, globalsNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("192.168.1.1")}, + {"interface and loopback with global, IPv6", routeV6, globalsNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("fd00::200")}, + {"interface and loopback with global, dual stack prefer IPv4", bothRoutes, globalsNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("192.168.1.1")}, + {"interface and loopback with global, dual stack prefer IPv6", bothRoutes, globalsNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("fd00::200")}, + {"interface and loopback with global, no routes", noRoutes, globalsNetworkInterface{}, preferIPv6, nil}, + {"all LLA", routeV4, networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil}, + {"no routes", noRoutes, validNetworkInterface{}, preferIPv4, nil}, + {"fail get IP", routeV4, networkInterfaceFailGetAddrs{}, preferIPv4, nil}, + } + for _, tc := range testCases { + ip, err := chooseHostInterfaceFromRoute(logger, tc.routes, tc.nw, tc.order) + if !ip.Equal(tc.expected) { + t.Errorf("case[%v]: expected %v, got %+v .err : %v", tc.tcase, tc.expected, ip, err) + } + } +} + +func TestMemberOf(t *testing.T) { + testCases := []struct { + tcase string + ip net.IP + family AddressFamily + expected bool + }{ + {"ipv4 is 4", netutils.ParseIPSloppy("10.20.30.40"), familyIPv4, true}, + {"ipv4 is 6", netutils.ParseIPSloppy("10.10.10.10"), familyIPv6, false}, + {"ipv6 is 4", netutils.ParseIPSloppy("2001::100"), familyIPv4, false}, + {"ipv6 is 6", netutils.ParseIPSloppy("2001::100"), familyIPv6, true}, + } + for _, tc := range testCases { + if memberOf(tc.ip, tc.family) != tc.expected { + t.Errorf("case[%s]: expected %+v", tc.tcase, tc.expected) + } + } +} + +func TestGetIPFromHostInterfaces(t *testing.T) { + logger, _ := ktesting.NewTestContext(t) + testCases := []struct { + tcase string + nw networkInterfacer + order AddressFamilyPreference + expected net.IP + errStrFrag string + }{ + {"fail get I/Fs", failGettingNetworkInterface{}, preferIPv4, nil, "failed getting all interfaces"}, + {"no interfaces", noNetworkInterface{}, preferIPv4, nil, "no interfaces"}, + {"I/F not up", downNetworkInterface{}, preferIPv4, nil, "no acceptable"}, + {"loopback only", loopbackNetworkInterface{}, preferIPv4, nil, "no acceptable"}, + {"P2P I/F only", p2pNetworkInterface{}, preferIPv4, nil, "no acceptable"}, + {"fail get addrs", networkInterfaceFailGetAddrs{}, preferIPv4, nil, "unable to get Addrs"}, + {"no addresses", networkInterfaceWithNoAddrs{}, preferIPv4, nil, "no acceptable"}, + {"invalid addr", networkInterfaceWithInvalidAddr{}, preferIPv4, nil, "invalid CIDR"}, + {"no matches", networkInterfaceWithOnlyLinkLocals{}, preferIPv4, nil, "no acceptable"}, + {"single-stack ipv4", validNetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.254.71.145"), ""}, + {"single-stack ipv4, prefer ipv6", validNetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("10.254.71.145"), ""}, + {"single-stack ipv6", ipv6NetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("2001::200"), ""}, + {"single-stack ipv6, prefer ipv6", ipv6NetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("2001::200"), ""}, + {"dual stack", v4v6NetworkInterface{}, preferIPv4, netutils.ParseIPSloppy("10.254.71.145"), ""}, + {"dual stack, prefer ipv6", v4v6NetworkInterface{}, preferIPv6, netutils.ParseIPSloppy("2001::10"), ""}, + } + + for _, tc := range testCases { + ip, err := chooseIPFromHostInterfaces(logger, tc.nw, tc.order) + if !ip.Equal(tc.expected) { + t.Errorf("case[%s]: expected %+v, got %+v with err : %v", tc.tcase, tc.expected, ip, err) + } + if err != nil && !strings.Contains(err.Error(), tc.errStrFrag) { + t.Errorf("case[%s]: unable to find %q in error string %q", tc.tcase, tc.errStrFrag, err.Error()) + } + } +} + +func makeRouteFile(content string, t *testing.T) (*os.File, error) { + routeFile, err := os.CreateTemp("", "route") + if err != nil { + return nil, err + } + + if _, err := routeFile.Write([]byte(content)); err != nil { + return routeFile, err + } + err = routeFile.Close() + return routeFile, err +} + +func TestFailGettingIPv4Routes(t *testing.T) { + defer func() { v4File.name = ipv4RouteFile }() + + // Try failure to open file (should not occur, as caller ensures we have IPv4 route file, but being thorough) + v4File.name = "no-such-file" + errStrFrag := "no such file" + _, err := v4File.extract() + if err == nil { + t.Errorf("Expected error trying to read non-existent v4 route file") + } + if !strings.Contains(err.Error(), errStrFrag) { + t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error()) + } +} + +func TestFailGettingIPv6Routes(t *testing.T) { + defer func() { v6File.name = ipv6RouteFile }() + + // Try failure to open file (this would be ignored by caller) + v6File.name = "no-such-file" + errStrFrag := "no such file" + _, err := v6File.extract() + if err == nil { + t.Errorf("Expected error trying to read non-existent v6 route file") + } + if !strings.Contains(err.Error(), errStrFrag) { + t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error()) + } +} + +func TestGetAllDefaultRoutesFailNoV4RouteFile(t *testing.T) { + defer func() { v4File.name = ipv4RouteFile }() + + // Should not occur, as caller ensures we have IPv4 route file, but being thorough + v4File.name = "no-such-file" + errStrFrag := "no such file" + _, err := getAllDefaultRoutes() + if err == nil { + t.Errorf("Expected error trying to read non-existent v4 route file") + } + if !strings.Contains(err.Error(), errStrFrag) { + t.Errorf("Unable to find %q in error string %q", errStrFrag, err.Error()) + } +} + +func TestGetAllDefaultRoutes(t *testing.T) { + testCases := []struct { + tcase string + v4Info string + v6Info string + count int + expected []Route + errStrFrag string + }{ + {"no routes", noInternetConnection, v6noDefaultRoutes, 0, nil, "no default routes"}, + {"only v4 route", gatewayfirst, v6noDefaultRoutes, 1, routeV4, ""}, + {"only v6 route", noInternetConnection, v6gatewayfirst, 1, routeV6, ""}, + {"v4 and v6 routes", gatewayfirst, v6gatewayfirst, 2, bothRoutes, ""}, + } + defer func() { + v4File.name = ipv4RouteFile + v6File.name = ipv6RouteFile + }() + + for _, tc := range testCases { + routeFile, err := makeRouteFile(tc.v4Info, t) + if routeFile != nil { + defer os.Remove(routeFile.Name()) + } + if err != nil { + t.Errorf("case[%s]: test setup failure for IPv4 route file: %v", tc.tcase, err) + } + v4File.name = routeFile.Name() + v6routeFile, err := makeRouteFile(tc.v6Info, t) + if v6routeFile != nil { + defer os.Remove(v6routeFile.Name()) + } + if err != nil { + t.Errorf("case[%s]: test setup failure for IPv6 route file: %v", tc.tcase, err) + } + v6File.name = v6routeFile.Name() + + routes, err := getAllDefaultRoutes() + if err != nil { + if !strings.Contains(err.Error(), tc.errStrFrag) { + t.Errorf("case[%s]: Error string %q does not contain %q", tc.tcase, err, tc.errStrFrag) + } + } else if tc.errStrFrag != "" { + t.Errorf("case[%s]: Error %q expected, but not seen", tc.tcase, tc.errStrFrag) + } else { + if tc.count != len(routes) { + t.Errorf("case[%s]: expected %d routes, have %v", tc.tcase, tc.count, routes) + } + for i, expected := range tc.expected { + if !expected.Gateway.Equal(routes[i].Gateway) { + t.Errorf("case[%s]: at %d expected %v, got %v .err : %v", tc.tcase, i, tc.expected, routes, err) + } + zeroIP := net.IPv4zero + if expected.Family == familyIPv6 { + zeroIP = net.IPv6zero + } + if !routes[i].Destination.Equal(zeroIP) { + t.Errorf("case[%s}: at %d destination is not for default route (not %v)", tc.tcase, i, zeroIP) + } + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_range.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_range.go new file mode 100644 index 0000000000..42ecffcca0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_range.go @@ -0,0 +1,149 @@ +/* +Copyright 2015 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 net + +import ( + "fmt" + "strconv" + "strings" +) + +// PortRange represents a range of TCP/UDP ports. To represent a single port, +// set Size to 1. +type PortRange struct { + Base int + Size int +} + +// Contains tests whether a given port falls within the PortRange. +func (pr *PortRange) Contains(p int) bool { + return (p >= pr.Base) && ((p - pr.Base) < pr.Size) +} + +// String converts the PortRange to a string representation, which can be +// parsed by PortRange.Set or ParsePortRange. +func (pr PortRange) String() string { + if pr.Size == 0 { + return "" + } + return fmt.Sprintf("%d-%d", pr.Base, pr.Base+pr.Size-1) +} + +// Set parses a string of the form "value", "min-max", or "min+offset", inclusive at both ends, and +// sets the PortRange from it. This is part of the flag.Value and pflag.Value +// interfaces. +func (pr *PortRange) Set(value string) error { + const ( + SinglePortNotation = 1 << iota + HyphenNotation + PlusNotation + ) + + value = strings.TrimSpace(value) + hyphenIndex := strings.Index(value, "-") + plusIndex := strings.Index(value, "+") + + if value == "" { + pr.Base = 0 + pr.Size = 0 + return nil + } + + var err error + var low, high int + var notation int + + if plusIndex == -1 && hyphenIndex == -1 { + notation |= SinglePortNotation + } + if hyphenIndex != -1 { + notation |= HyphenNotation + } + if plusIndex != -1 { + notation |= PlusNotation + } + + switch notation { + case SinglePortNotation: + var port int + port, err = strconv.Atoi(value) + if err != nil { + return err + } + low = port + high = port + case HyphenNotation: + low, err = strconv.Atoi(value[:hyphenIndex]) + if err != nil { + return err + } + high, err = strconv.Atoi(value[hyphenIndex+1:]) + if err != nil { + return err + } + case PlusNotation: + var offset int + low, err = strconv.Atoi(value[:plusIndex]) + if err != nil { + return err + } + offset, err = strconv.Atoi(value[plusIndex+1:]) + if err != nil { + return err + } + high = low + offset + default: + return fmt.Errorf("unable to parse port range: %s", value) + } + + if low > 65535 || high > 65535 { + return fmt.Errorf("the port range cannot be greater than 65535: %s", value) + } + + if high < low { + return fmt.Errorf("end port cannot be less than start port: %s", value) + } + + pr.Base = low + pr.Size = 1 + high - low + return nil +} + +// Type returns a descriptive string about this type. This is part of the +// pflag.Value interface. +func (*PortRange) Type() string { + return "portRange" +} + +// ParsePortRange parses a string of the form "min-max", inclusive at both +// ends, and initializes a new PortRange from it. +func ParsePortRange(value string) (*PortRange, error) { + pr := &PortRange{} + err := pr.Set(value) + if err != nil { + return nil, err + } + return pr, nil +} + +func ParsePortRangeOrDie(value string) *PortRange { + pr, err := ParsePortRange(value) + if err != nil { + panic(fmt.Sprintf("couldn't parse port range %q: %v", value, err)) + } + return pr +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_range_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_range_test.go new file mode 100644 index 0000000000..94a1b7f881 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_range_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2015 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 net + +import ( + "testing" + + flag "github.com/spf13/pflag" +) + +func TestPortRange(t *testing.T) { + testCases := []struct { + input string + success bool + expected string + included int + excluded int + }{ + {"100-200", true, "100-200", 200, 201}, + {" 100-200 ", true, "100-200", 200, 201}, + {"0-0", true, "0-0", 0, 1}, + {"", true, "", -1, 0}, + {"100", true, "100-100", 100, 101}, + {"100 - 200", false, "", -1, -1}, + {"-100", false, "", -1, -1}, + {"100-", false, "", -1, -1}, + {"200-100", false, "", -1, -1}, + {"60000-70000", false, "", -1, -1}, + {"70000-80000", false, "", -1, -1}, + {"70000+80000", false, "", -1, -1}, + {"1+0", true, "1-1", 1, 2}, + {"0+0", true, "0-0", 0, 1}, + {"1+-1", false, "", -1, -1}, + {"1-+1", false, "", -1, -1}, + {"100+200", true, "100-300", 300, 301}, + {"1+65535", false, "", -1, -1}, + {"0+65535", true, "0-65535", 65535, 65536}, + } + + for i := range testCases { + tc := &testCases[i] + pr := &PortRange{} + var f flag.Value = pr + err := f.Set(tc.input) + if err != nil && tc.success { + t.Errorf("expected success, got %q", err) + continue + } else if err == nil && !tc.success { + t.Errorf("expected failure %#v", testCases[i]) + continue + } else if tc.success { + if f.String() != tc.expected { + t.Errorf("expected %q, got %q", tc.expected, f.String()) + } + if tc.included >= 0 && !pr.Contains(tc.included) { + t.Errorf("expected %q to include %d", f.String(), tc.included) + } + if tc.excluded >= 0 && pr.Contains(tc.excluded) { + t.Errorf("expected %q to exclude %d", f.String(), tc.excluded) + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_split.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_split.go new file mode 100644 index 0000000000..f54bb1e71c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_split.go @@ -0,0 +1,78 @@ +/* +Copyright 2015 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 net + +import ( + "strings" + + "k8s.io/apimachinery/pkg/util/sets" +) + +var validSchemes = sets.NewString("http", "https", "") + +// SplitSchemeNamePort takes a string of the following forms: +// - "", returns "", "","", true +// - ":", returns "", "","",true +// - "::", returns "","","",true +// +// Name must be non-empty or valid will be returned false. +// Scheme must be "http" or "https" if specified +// Port is returned as a string, and it is not required to be numeric (could be +// used for a named port, for example). +func SplitSchemeNamePort(id string) (scheme, name, port string, valid bool) { + parts := strings.Split(id, ":") + switch len(parts) { + case 1: + name = parts[0] + case 2: + name = parts[0] + port = parts[1] + case 3: + scheme = parts[0] + name = parts[1] + port = parts[2] + default: + return "", "", "", false + } + + if len(name) > 0 && validSchemes.Has(scheme) { + return scheme, name, port, true + } else { + return "", "", "", false + } +} + +// JoinSchemeNamePort returns a string that specifies the scheme, name, and port: +// - "" +// - ":" +// - "::" +// +// None of the parameters may contain a ':' character +// Name is required +// Scheme must be "", "http", or "https" +func JoinSchemeNamePort(scheme, name, port string) string { + if len(scheme) > 0 { + // Must include three segments to specify scheme + return scheme + ":" + name + ":" + port + } + if len(port) > 0 { + // Must include two segments to specify port + return name + ":" + port + } + // Return name alone + return name +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_split_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_split_test.go new file mode 100644 index 0000000000..e801bdbea0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/port_split_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2015 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 net + +import ( + "testing" +) + +func TestSplitSchemeNamePort(t *testing.T) { + table := []struct { + in string + name, port, scheme string + valid bool + normalized bool + }{ + { + in: "aoeu:asdf", + name: "aoeu", + port: "asdf", + valid: true, + normalized: true, + }, { + in: "http:aoeu:asdf", + scheme: "http", + name: "aoeu", + port: "asdf", + valid: true, + normalized: true, + }, { + in: "https:aoeu:", + scheme: "https", + name: "aoeu", + port: "", + valid: true, + normalized: false, + }, { + in: "https:aoeu:asdf", + scheme: "https", + name: "aoeu", + port: "asdf", + valid: true, + normalized: true, + }, { + in: "aoeu:", + name: "aoeu", + valid: true, + normalized: false, + }, { + in: "aoeu", + name: "aoeu", + valid: true, + normalized: true, + }, { + in: ":asdf", + valid: false, + }, { + in: "aoeu:asdf:htns", + valid: false, + }, { + in: "http::asdf", + valid: false, + }, { + in: "http::", + valid: false, + }, { + in: "", + valid: false, + }, + } + + for _, item := range table { + scheme, name, port, valid := SplitSchemeNamePort(item.in) + if e, a := item.scheme, scheme; e != a { + t.Errorf("%q: Wanted %q, got %q", item.in, e, a) + } + if e, a := item.name, name; e != a { + t.Errorf("%q: Wanted %q, got %q", item.in, e, a) + } + if e, a := item.port, port; e != a { + t.Errorf("%q: Wanted %q, got %q", item.in, e, a) + } + if e, a := item.valid, valid; e != a { + t.Errorf("%q: Wanted %t, got %t", item.in, e, a) + } + + // Make sure valid items round trip through JoinSchemeNamePort + if item.valid { + out := JoinSchemeNamePort(scheme, name, port) + if item.normalized && out != item.in { + t.Errorf("%q: Wanted %s, got %s", item.in, item.in, out) + } + scheme, name, port, valid := SplitSchemeNamePort(out) + if e, a := item.scheme, scheme; e != a { + t.Errorf("%q: Wanted %q, got %q", item.in, e, a) + } + if e, a := item.name, name; e != a { + t.Errorf("%q: Wanted %q, got %q", item.in, e, a) + } + if e, a := item.port, port; e != a { + t.Errorf("%q: Wanted %q, got %q", item.in, e, a) + } + if e, a := item.valid, valid; e != a { + t.Errorf("%q: Wanted %t, got %t", item.in, e, a) + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/testing/http.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/testing/http.go new file mode 100644 index 0000000000..439bb38140 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/testing/http.go @@ -0,0 +1,139 @@ +/* +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 nettesting contains utilities for testing networking functionality. +// Don't use these utilities in production code. They have not been security +// reviewed. +package nettesting + +import ( + "io" + "net" + "net/http" + "net/http/httputil" + "sync" + "testing" +) + +// NewHTTPProxyHandler returns a new HTTPProxyHandler. It accepts an optional +// hook which is called early in the handler to export request state. If the +// hook returns false, the handler returns immediately with a server error. +// Ensure that this is only used in tests. This code has not been security +// reviewed. +func NewHTTPProxyHandler(t testing.TB, hook func(req *http.Request) bool) *HTTPProxyHandler { + h := &HTTPProxyHandler{ + hook: hook, + httpProxy: httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = req.Host + }, + }, + t: t, + } + return h +} + +// HTTPProxyHandler implements a simple handler for http_proxy and https_proxy +// requests for use in testing. +type HTTPProxyHandler struct { + handlerDone sync.WaitGroup + hook func(r *http.Request) bool + // httpProxy is the reverse proxy we use for standard http proxy requests. + httpProxy httputil.ReverseProxy + t testing.TB +} + +// ServeHTTP handles an HTTP proxy request. +func (h *HTTPProxyHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + h.handlerDone.Add(1) + defer h.handlerDone.Done() + + if h.hook != nil { + if ok := h.hook(req); !ok { + rw.WriteHeader(http.StatusInternalServerError) + return + } + } + + b, err := httputil.DumpRequest(req, false) + if err != nil { + h.t.Logf("Failed to dump request, host=%s: %v", req.Host, err) + } else { + h.t.Logf("Proxy Request: %s", string(b)) + } + + if req.Method != http.MethodConnect { + h.httpProxy.ServeHTTP(rw, req) + return + } + + // CONNECT proxy + + sconn, err := net.Dial("tcp", req.Host) + if err != nil { + h.t.Logf("Failed to dial proxy backend, host=%s: %v", req.Host, err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + defer sconn.Close() + + hj, ok := rw.(http.Hijacker) + if !ok { + h.t.Logf("Can't switch protocols using non-Hijacker ResponseWriter: type=%T, host=%s", rw, req.Host) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + rw.WriteHeader(http.StatusOK) + + conn, brw, err := hj.Hijack() + if err != nil { + h.t.Logf("Failed to hijack client connection, host=%s: %v", req.Host, err) + return + } + defer conn.Close() + + if err := brw.Flush(); err != nil { + h.t.Logf("Failed to flush pending writes to client, host=%s: %v", req.Host, err) + return + } + if _, err := io.Copy(sconn, io.LimitReader(brw, int64(brw.Reader.Buffered()))); err != nil { + h.t.Logf("Failed to flush buffered reads to server, host=%s: %v", req.Host, err) + return + } + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + defer h.t.Logf("Server read close, host=%s", req.Host) + io.Copy(conn, sconn) + }() + go func() { + defer wg.Done() + defer h.t.Logf("Server write close, host=%s", req.Host) + io.Copy(sconn, conn) + }() + + wg.Wait() + h.t.Logf("Done handling CONNECT request, host=%s", req.Host) +} + +func (h *HTTPProxyHandler) Wait() { + h.handlerDone.Wait() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/testing/socket.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/testing/socket.go new file mode 100644 index 0000000000..4bf868aac8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/testing/socket.go @@ -0,0 +1,41 @@ +/* +Copyright 2025 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 nettesting contains utilities for testing networking functionality. +// Don't use these utilities in production code. They have not been security +// reviewed. +package nettesting + +import ( + "os" + goruntime "runtime" + "testing" +) + +// MakeSocketNameForTest returns a socket name to use for the duration of a test. +// On Operating systems that support abstract sockets, it the name is prefixed with `@` to make it an abstract socket. +// On Operating systems that do not support abstract sockets, the name is treated as a filename and a cleanup hook is +// registered to delete the socket at the end of the test. +func MakeSocketNameForTest(t testing.TB, name string) string { + var sockname = name + switch goruntime.GOOS { + case "darwin", "windows": + t.Cleanup(func() { _ = os.Remove(sockname) }) + default: + sockname = "@" + name + } + return sockname +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/util.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/util.go new file mode 100644 index 0000000000..1635e69a5c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/util.go @@ -0,0 +1,63 @@ +/* +Copyright 2016 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 net + +import ( + "errors" + "net" + "reflect" + "strings" + "syscall" +) + +// IPNetEqual checks if the two input IPNets are representing the same subnet. +// For example, +// +// 10.0.0.1/24 and 10.0.0.0/24 are the same subnet. +// 10.0.0.1/24 and 10.0.0.0/25 are not the same subnet. +func IPNetEqual(ipnet1, ipnet2 *net.IPNet) bool { + if ipnet1 == nil || ipnet2 == nil { + return false + } + if reflect.DeepEqual(ipnet1.Mask, ipnet2.Mask) && ipnet1.Contains(ipnet2.IP) && ipnet2.Contains(ipnet1.IP) { + return true + } + return false +} + +// Returns if the given err is "connection reset by peer" error. +func IsConnectionReset(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + return errno == syscall.ECONNRESET + } + return false +} + +// Returns if the given err is "http2: client connection lost" error. +func IsHTTP2ConnectionLost(err error) bool { + return err != nil && strings.Contains(err.Error(), "http2: client connection lost") +} + +// Returns if the given err is "connection refused" error +func IsConnectionRefused(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + return errno == syscall.ECONNREFUSED + } + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/util_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/util_test.go new file mode 100644 index 0000000000..90b14a864c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/net/util_test.go @@ -0,0 +1,201 @@ +/* +Copyright 2016 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 net + +import ( + "fmt" + "io" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "syscall" + "testing" + "time" + + "golang.org/x/net/http2" + + netutils "k8s.io/utils/net" +) + +func getIPNet(cidr string) *net.IPNet { + _, ipnet, _ := netutils.ParseCIDRSloppy(cidr) + return ipnet +} + +func TestIPNetEqual(t *testing.T) { + testCases := []struct { + ipnet1 *net.IPNet + ipnet2 *net.IPNet + expect bool + }{ + // null case + { + getIPNet("10.0.0.1/24"), + getIPNet(""), + false, + }, + { + getIPNet("10.0.0.0/24"), + getIPNet("10.0.0.0/24"), + true, + }, + { + getIPNet("10.0.0.0/24"), + getIPNet("10.0.0.1/24"), + true, + }, + { + getIPNet("10.0.0.0/25"), + getIPNet("10.0.0.0/24"), + false, + }, + { + getIPNet("10.0.1.0/24"), + getIPNet("10.0.0.0/24"), + false, + }, + } + + for _, tc := range testCases { + if tc.expect != IPNetEqual(tc.ipnet1, tc.ipnet2) { + t.Errorf("Expect equality of %s and %s be to %v", tc.ipnet1.String(), tc.ipnet2.String(), tc.expect) + } + } +} + +func TestIsConnectionRefused(t *testing.T) { + testCases := []struct { + err error + expect bool + }{ + { + &url.Error{Err: &net.OpError{Err: syscall.ECONNRESET}}, + false, + }, + { + &url.Error{Err: &net.OpError{Err: syscall.ECONNREFUSED}}, + true, + }, + {&url.Error{Err: &net.OpError{Err: &os.SyscallError{Err: syscall.ECONNREFUSED}}}, + true, + }, + } + + for _, tc := range testCases { + if result := IsConnectionRefused(tc.err); result != tc.expect { + t.Errorf("Expect to be %v, but actual is %v", tc.expect, result) + } + } +} + +type tcpLB struct { + t *testing.T + ln net.Listener + serverURL string +} + +func (lb *tcpLB) handleConnection(in net.Conn, stopCh chan struct{}) { + out, err := net.Dial("tcp", lb.serverURL) + if err != nil { + lb.t.Log(err) + return + } + go io.Copy(out, in) + go io.Copy(in, out) + <-stopCh + if err := out.Close(); err != nil { + lb.t.Fatalf("failed to close connection: %v", err) + } +} + +func (lb *tcpLB) serve(stopCh chan struct{}) { + conn, err := lb.ln.Accept() + if err != nil { + lb.t.Fatalf("failed to accept: %v", err) + } + lb.handleConnection(conn, stopCh) +} + +func newLB(t *testing.T, serverURL string) *tcpLB { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to bind: %v", err) + } + lb := tcpLB{ + serverURL: serverURL, + ln: ln, + t: t, + } + return &lb +} + +func TestIsConnectionReset(t *testing.T) { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %s", r.Proto) + })) + ts.EnableHTTP2 = true + ts.StartTLS() + defer ts.Close() + + u, err := url.Parse(ts.URL) + if err != nil { + t.Fatalf("failed to parse URL from %q: %v", ts.URL, err) + } + lb := newLB(t, u.Host) + defer lb.ln.Close() + stopCh, stoppedCh := make(chan struct{}), make(chan struct{}) + go func() { + defer close(stoppedCh) + lb.serve(stopCh) + }() + + c := ts.Client() + transport, ok := ts.Client().Transport.(*http.Transport) + if !ok { + t.Fatalf("failed to assert *http.Transport") + } + t2, err := http2.ConfigureTransports(transport) + if err != nil { + t.Fatalf("failed to configure *http.Transport: %+v", err) + } + t2.ReadIdleTimeout = time.Second + t2.PingTimeout = time.Second + resp, err := c.Get("https://" + lb.ln.Addr().String()) + if err != nil { + t.Fatalf("unexpected error: %+v", err) + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unexpected error: %+v", err) + } + if string(data) != "Hello, HTTP/2.0" { + t.Fatalf("unexpected response: %s", data) + } + + // Deliberately let the LB stop proxying traffic for the current + // connection. This mimics a broken TCP connection that's not properly + // closed. + close(stopCh) + <-stoppedCh + _, err = c.Get("https://" + lb.ln.Addr().String()) + if !IsHTTP2ConnectionLost(err) { + t.Fatalf("expected HTTP2ConnectionLost error, got %v", err) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/portforward/constants.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/portforward/constants.go new file mode 100644 index 0000000000..6853288156 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/portforward/constants.go @@ -0,0 +1,24 @@ +/* +Copyright 2016 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 portforward + +const ( + PortForwardV1Name = "portforward.k8s.io" + WebsocketsSPDYTunnelingPrefix = "SPDY/3.1+" + KubernetesSuffix = ".k8s.io" + WebsocketsSPDYTunnelingPortForwardV1 = WebsocketsSPDYTunnelingPrefix + PortForwardV1Name +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/dial.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/dial.go new file mode 100644 index 0000000000..d6ba23d416 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/dial.go @@ -0,0 +1,122 @@ +/* +Copyright 2015 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 proxy + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "net/url" + + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/third_party/forked/golang/netutil" + "k8s.io/klog/v2" +) + +// DialURL will dial the specified URL using the underlying dialer held by the passed +// RoundTripper. The primary use of this method is to support proxying upgradable connections. +// For this reason this method will prefer to negotiate http/1.1 if the URL scheme is https. +// If you wish to ensure ALPN negotiates http2 then set NextProto=[]string{"http2"} in the +// TLSConfig of the http.Transport +func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (net.Conn, error) { + dialAddr := netutil.CanonicalAddr(url) + + dialer, err := utilnet.DialerFor(transport) + if err != nil { + klog.FromContext(ctx).V(5).Info("Unable to unwrap transport to get dialer", "type", fmt.Sprintf("%T", transport), "err", err) + } + + switch url.Scheme { + case "http": + if dialer != nil { + return dialer(ctx, "tcp", dialAddr) + } + var d net.Dialer + return d.DialContext(ctx, "tcp", dialAddr) + case "https": + // Get the tls config from the transport if we recognize it + tlsConfig, err := utilnet.TLSClientConfig(transport) + if err != nil { + klog.FromContext(ctx).V(5).Info("Unable to unwrap transport to get at TLS config", "type", fmt.Sprintf("%T", transport), "err", err) + } + + if dialer != nil { + // We have a dialer; use it to open the connection, then + // create a tls client using the connection. + netConn, err := dialer(ctx, "tcp", dialAddr) + if err != nil { + return nil, err + } + if tlsConfig == nil { + // tls.Client requires non-nil config + klog.FromContext(ctx).Info("Warning: using custom dialer with no TLSClientConfig, defaulting to InsecureSkipVerify") + // tls.Handshake() requires ServerName or InsecureSkipVerify + tlsConfig = &tls.Config{ + InsecureSkipVerify: true, + } + } else if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify { + // tls.HandshakeContext() requires ServerName or InsecureSkipVerify + // infer the ServerName from the hostname we're connecting to. + inferredHost := dialAddr + if host, _, err := net.SplitHostPort(dialAddr); err == nil { + inferredHost = host + } + // Make a copy to avoid polluting the provided config + tlsConfigCopy := tlsConfig.Clone() + tlsConfigCopy.ServerName = inferredHost + tlsConfig = tlsConfigCopy + } + + // Since this method is primarily used within a "Connection: Upgrade" call we assume the caller is + // going to write HTTP/1.1 request to the wire. http2 should not be allowed in the TLSConfig.NextProtos, + // so we explicitly set that here. We only do this check if the TLSConfig support http/1.1. + if supportsHTTP11(tlsConfig.NextProtos) { + tlsConfig = tlsConfig.Clone() + tlsConfig.NextProtos = []string{"http/1.1"} + } + + tlsConn := tls.Client(netConn, tlsConfig) + if err := tlsConn.HandshakeContext(ctx); err != nil { + netConn.Close() + return nil, err + } + return tlsConn, nil + } else { + // Dial. + tlsDialer := tls.Dialer{ + Config: tlsConfig, + } + return tlsDialer.DialContext(ctx, "tcp", dialAddr) + } + default: + return nil, fmt.Errorf("unknown scheme: %s", url.Scheme) + } +} + +func supportsHTTP11(nextProtos []string) bool { + if len(nextProtos) == 0 { + return true + } + for _, proto := range nextProtos { + if proto == "http/1.1" { + return true + } + } + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/dial_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/dial_test.go new file mode 100644 index 0000000000..488e878b72 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/dial_test.go @@ -0,0 +1,230 @@ +/* +Copyright 2016 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 proxy + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "regexp" + "testing" + + "github.com/google/go-cmp/cmp" + utilnet "k8s.io/apimachinery/pkg/util/net" +) + +func TestDialURL(t *testing.T) { + roots := x509.NewCertPool() + if !roots.AppendCertsFromPEM(localhostCert) { + t.Fatal("error setting up localhostCert pool") + } + + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Fatal(err) + } + var d net.Dialer + + testcases := map[string]struct { + TLSConfig *tls.Config + Dial utilnet.DialFunc + ExpectError string + ExpectProto string + }{ + "insecure": { + TLSConfig: &tls.Config{InsecureSkipVerify: true}, + }, + "secure, no roots": { + TLSConfig: &tls.Config{InsecureSkipVerify: false}, + ExpectError: "unknown authority|not trusted", + }, + "secure with roots": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots}, + }, + "secure with mismatched server": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "bogus.com"}, + ExpectError: "not bogus.com", + }, + "secure with matched server": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"}, + }, + + "insecure, custom dial": { + TLSConfig: &tls.Config{InsecureSkipVerify: true}, + Dial: d.DialContext, + }, + "secure, no roots, custom dial": { + TLSConfig: &tls.Config{InsecureSkipVerify: false}, + Dial: d.DialContext, + ExpectError: "unknown authority|not trusted", + }, + "secure with roots, custom dial": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots}, + Dial: d.DialContext, + }, + "secure with mismatched server, custom dial": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "bogus.com"}, + Dial: d.DialContext, + ExpectError: "not bogus.com", + }, + "secure with matched server, custom dial": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"}, + Dial: d.DialContext, + }, + "ensure we use http2 if specified": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com", NextProtos: []string{"http2"}}, + Dial: d.DialContext, + ExpectProto: "http2", + }, + "ensure we use http/1.1 if unspecified": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com"}, + Dial: d.DialContext, + ExpectProto: "http/1.1", + }, + "ensure we use http/1.1 if available": { + TLSConfig: &tls.Config{InsecureSkipVerify: false, RootCAs: roots, ServerName: "example.com", NextProtos: []string{"http2", "http/1.1"}}, + Dial: d.DialContext, + ExpectProto: "http/1.1", + }, + } + + for k, tc := range testcases { + func() { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {})) + defer ts.Close() + ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}, NextProtos: []string{"http2", "http/1.1"}} + ts.StartTLS() + + // Make a copy of the config + tlsConfigCopy := tc.TLSConfig.Clone() + // Clone() mutates the receiver (!), so also call it on the copy + tlsConfigCopy.Clone() + transport := &http.Transport{ + DialContext: tc.Dial, + TLSClientConfig: tlsConfigCopy, + } + + extractedDial, err := utilnet.DialerFor(transport) + if err != nil { + t.Fatal(err) + } + if fmt.Sprintf("%p", extractedDial) != fmt.Sprintf("%p", tc.Dial) { + t.Fatalf("%s: Unexpected dial", k) + } + + extractedTLSConfig, err := utilnet.TLSClientConfig(transport) + if err != nil { + t.Fatal(err) + } + if extractedTLSConfig == nil { + t.Fatalf("%s: Expected tlsConfig", k) + } + + u, _ := url.Parse(ts.URL) + _, p, _ := net.SplitHostPort(u.Host) + u.Host = net.JoinHostPort("127.0.0.1", p) + conn, err := DialURL(context.Background(), u, transport) + + // Make sure dialing doesn't mutate the transport's TLSConfig + if !reflect.DeepEqual(tc.TLSConfig, tlsConfigCopy) { + t.Errorf("%s: transport's copy of TLSConfig was mutated\n%s", k, cmp.Diff(tc.TLSConfig, tlsConfigCopy)) + } + + if err != nil { + if tc.ExpectError == "" { + t.Errorf("%s: expected no error, got %q", k, err.Error()) + } + if tc.ExpectError != "" && !regexp.MustCompile(tc.ExpectError).MatchString(err.Error()) { + t.Errorf("%s: expected error containing %q, got %q", k, tc.ExpectError, err.Error()) + } + return + } + + tlsConn := conn.(*tls.Conn) + if tc.ExpectProto != "" { + if tlsConn.ConnectionState().NegotiatedProtocol != tc.ExpectProto { + t.Errorf("%s: expected proto %s, got %s", k, tc.ExpectProto, tlsConn.ConnectionState().NegotiatedProtocol) + } + } + + conn.Close() + if tc.ExpectError != "" { + t.Errorf("%s: expected error %q, got none", k, tc.ExpectError) + } + }() + } + +} + +// localhostCert was generated from crypto/tls/generate_cert.go with the following command: +// +// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDGTCCAgGgAwIBAgIRAKfNl1LEAt7nFPYvHBnpv2swDQYJKoZIhvcNAQELBQAw +EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 +MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAKww39FwmV5lDIbAUIAuSYYVtZke6bca1oyq19ZrRL0uavwPXSJm ++Qxt4RKUQhzYhZ/alJp8iRfu/Z+Yv9Beez89dQB9V8YnHj/AX4Jph9lJ2aawWMI6 +AqPLdIzKLQVVvPw+UVKH9x8yy08H/23AIFGyK4Dbht+KZJeUbJQFiGlRFJim8atx +KA3C9NzCHw6hyhP46jguLl65rcxLMSzcTz97ToG0MP66YEUbsA/YzFTKDwht7ESH +nRMBnQ4wZfWpvAiXMr3XJGOa3NYJy1A+WkWyrfZO7guwsZ4L6dGqnlPpzA5QkKYx +H9Z5K1bUaYEi0Yi2ug7Jkvd1HE179nkF7t0CAwEAAaNoMGYwDgYDVR0PAQH/BAQD +AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R +BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI +hvcNAQELBQADggEBAAKSQToD1iLujFhQwaLnPVRV6r4nEFVXCxXYtQNEX1DVSKSj +JYbBGJnL50oc0N4Ar+Spqofm+THkiTQJUzptPtnYIzNpKYdE6+bPwqURWzFEI2OF +ks3fYZ4ZdbMbmJRo1qPJO34emm4KrOl9aoV0qwp2QyTvHgLroU3icKoe4e7+p4KK +02Rt3qczHvCKoUnw6m07Ql0n9e7Ncpujcs2A8PaQ1iPX+BVOmvjTVT8y5NSRDzwL +a2wur8BSZ5E8SVzzvNZJlLSi6BbObQUjALHkjVYm11dWv/BY8jHdt+iFhbNBRASx +ENuih3pX1Poki1qRYOtB/vAS99E1ORj9zJlUlzo= +-----END CERTIFICATE-----`) + +// localhostKey is the private key for localhostCert. +var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEArDDf0XCZXmUMhsBQgC5JhhW1mR7ptxrWjKrX1mtEvS5q/A9d +Imb5DG3hEpRCHNiFn9qUmnyJF+79n5i/0F57Pz11AH1XxiceP8BfgmmH2UnZprBY +wjoCo8t0jMotBVW8/D5RUof3HzLLTwf/bcAgUbIrgNuG34pkl5RslAWIaVEUmKbx +q3EoDcL03MIfDqHKE/jqOC4uXrmtzEsxLNxPP3tOgbQw/rpgRRuwD9jMVMoPCG3s +RIedEwGdDjBl9am8CJcyvdckY5rc1gnLUD5aRbKt9k7uC7Cxngvp0aqeU+nMDlCQ +pjEf1nkrVtRpgSLRiLa6DsmS93UcTXv2eQXu3QIDAQABAoIBACAYnB+2FWB7BXK4 +tkiuWBYeRdNc58OxxPxDfCgDprR8yoRheMLI3vNqJ+IGsKwf0AiT/c8uF3/WlIAD +QP3eHqsTEZQdyRaug/zuJt9wPFpMYb2ocWMC3Ssa6Ya0yN+Ns8Rw+UehAHdYSH1a +yEn03hFcXK+QO/u/GDEJAZQ108+NdznT4ql59tt791d97meNlMVJwkwVf/NqtDqi +UNx6BvSj5+6MoWjU8hqrYv9pkzP386QRsl70tVH+0LZd5XUZsSyof/IdV1EmfGUR +5les8tsd+fuo3LaPObksJu+GBwvEStmQPjZjiBUzw0Sx8VYTJfZr7gl2h4mmk/AJ +F5P+fSECgYEAzwDcJCuYPA8nzVB+ZOM+Wl+3uUKG/Xn8Yx8uWtWU7o9qsJmasLqO +sLtz1zadPtYOBXsb4H5PisNPuosVEqnthjRwmPhIA3tK/X3UzhnriACCrKpg3Ix0 +uJG2vqpdaPXYxmyTQfI8YSp5X0gTg3R4xQqmbGMyAQg+1NzcGAf+qQ8CgYEA1PKX +vkxzJuSPsfQYr34fnZRuogANNGUaWCTYMhH6sK8qrJu5RXmEemaraqT/esUUu1fl +cTAxRqUb8ysexA+RKR848hFkrvAR5M1t6xK2hPuSec1Lm9HNfHoFB7Pa5t7APoJ9 +8NkjNzI0mL9YqYcfJpzfFrxtzfLwlm6B3irS8VMCgYBg3skmUBRcvsbkiO+tLL7I +MhTbKGvdgNGAXV4m+d5JSWonHKrMW3Fc+Uv7gb5SYn+LRxJDmziD+mR8KowBAO57 +qFys6TtiDbeJKvKERJL5QSvlu5G6hCw3F1GKplUyQiJgsPy0lrR00BieYy9mjAHc +S+CXxk/nNcGZgYWp5UviNwKBgC7t46kpmfsJRe222LXcOsV0j8kd78sLOPoR7J9k +PPYxNFtj2jnIZPzAoahYAoGg60e6QDNopoNmIbm+WAJnV9tTKS6XzLOM7rSY3U+A +CT9XXdl/99i4LOvwzCj9ZxGYJ4/fHDg28j7YzqSXDsgVojTVP4j4L87CamkMo4w9 +rc1HAoGARE2WActS2PF75jRXCjj4SjB/3vOJVGKxrJdoo2HPzY0psTmdJJULOGYZ +MU1KC4EDzhSfM3juBbEhaZx9NFZOHVp2hxZpg77B5cQXGH6HIiZ20jCNjdcioHl9 +HeVeFG/9rJG0NcQe3pIm9f0EY5JCbzr0fa2tTPV3N9jGHc0sFtI= +-----END RSA PRIVATE KEY----- +`) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/doc.go new file mode 100644 index 0000000000..ea710f6b15 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/doc.go @@ -0,0 +1,18 @@ +/* +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 proxy provides transport and upgrade support for proxies. +package proxy diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/transport.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/transport.go new file mode 100644 index 0000000000..1c17f53df0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/transport.go @@ -0,0 +1,272 @@ +/* +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 proxy + +import ( + "bytes" + "compress/flate" + "compress/gzip" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "golang.org/x/net/html" + "golang.org/x/net/html/atom" + "k8s.io/klog/v2" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/sets" +) + +// atomsToAttrs states which attributes of which tags require URL substitution. +// Sources: http://www.w3.org/TR/REC-html40/index/attributes.html +// +// http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1 +var atomsToAttrs = map[atom.Atom]sets.String{ + atom.A: sets.NewString("href"), + atom.Applet: sets.NewString("codebase"), + atom.Area: sets.NewString("href"), + atom.Audio: sets.NewString("src"), + atom.Base: sets.NewString("href"), + atom.Blockquote: sets.NewString("cite"), + atom.Body: sets.NewString("background"), + atom.Button: sets.NewString("formaction"), + atom.Command: sets.NewString("icon"), + atom.Del: sets.NewString("cite"), + atom.Embed: sets.NewString("src"), + atom.Form: sets.NewString("action"), + atom.Frame: sets.NewString("longdesc", "src"), + atom.Head: sets.NewString("profile"), + atom.Html: sets.NewString("manifest"), + atom.Iframe: sets.NewString("longdesc", "src"), + atom.Img: sets.NewString("longdesc", "src", "usemap"), + atom.Input: sets.NewString("src", "usemap", "formaction"), + atom.Ins: sets.NewString("cite"), + atom.Link: sets.NewString("href"), + atom.Object: sets.NewString("classid", "codebase", "data", "usemap"), + atom.Q: sets.NewString("cite"), + atom.Script: sets.NewString("src"), + atom.Source: sets.NewString("src"), + atom.Video: sets.NewString("poster", "src"), + + // TODO: css URLs hidden in style elements. +} + +// Transport is a transport for text/html content that replaces URLs in html +// content with the prefix of the proxy server +type Transport struct { + Scheme string + Host string + PathPrepend string + + http.RoundTripper +} + +// RoundTrip implements the http.RoundTripper interface +func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + // Add reverse proxy headers. + forwardedURI := path.Join(t.PathPrepend, req.URL.EscapedPath()) + if strings.HasSuffix(req.URL.Path, "/") { + forwardedURI = forwardedURI + "/" + } + req.Header.Set("X-Forwarded-Uri", forwardedURI) + if len(t.Host) > 0 { + req.Header.Set("X-Forwarded-Host", t.Host) + } + if len(t.Scheme) > 0 { + req.Header.Set("X-Forwarded-Proto", t.Scheme) + } + + rt := t.RoundTripper + if rt == nil { + rt = http.DefaultTransport + } + resp, err := rt.RoundTrip(req) + + if err != nil { + return nil, errors.NewServiceUnavailable(fmt.Sprintf("error trying to reach service: %v", err)) + } + + if redirect := resp.Header.Get("Location"); redirect != "" { + targetURL, err := url.Parse(redirect) + if err != nil { + return nil, errors.NewInternalError(fmt.Errorf("error trying to parse Location header: %v", err)) + } + resp.Header.Set("Location", t.rewriteURL(targetURL, req.URL, req.Host)) + return resp, nil + } + + cType := resp.Header.Get("Content-Type") + cType = strings.TrimSpace(strings.SplitN(cType, ";", 2)[0]) + if cType != "text/html" { + // Do nothing, simply pass through + return resp, nil + } + + return t.rewriteResponse(req, resp) +} + +var _ = net.RoundTripperWrapper(&Transport{}) + +func (rt *Transport) WrappedRoundTripper() http.RoundTripper { + return rt.RoundTripper +} + +// rewriteURL rewrites a single URL to go through the proxy, if the URL refers +// to the same host as sourceURL, which is the page on which the target URL +// occurred, or if the URL matches the sourceRequestHost. +func (t *Transport) rewriteURL(url *url.URL, sourceURL *url.URL, sourceRequestHost string) string { + // Example: + // When API server processes a proxy request to a service (e.g. /api/v1/namespace/foo/service/bar/proxy/), + // the sourceURL.Host (i.e. req.URL.Host) is the endpoint IP address of the service. The + // sourceRequestHost (i.e. req.Host) is the Host header that specifies the host on which the + // URL is sought, which can be different from sourceURL.Host. For example, if user sends the + // request through "kubectl proxy" locally (i.e. localhost:8001/api/v1/namespace/foo/service/bar/proxy/), + // sourceRequestHost is "localhost:8001". + // + // If the service's response URL contains non-empty host, and url.Host is equal to either sourceURL.Host + // or sourceRequestHost, we should not consider the returned URL to be a completely different host. + // It's the API server's responsibility to rewrite a same-host-and-absolute-path URL and append the + // necessary URL prefix (i.e. /api/v1/namespace/foo/service/bar/proxy/). + isDifferentHost := url.Host != "" && url.Host != sourceURL.Host && url.Host != sourceRequestHost + isRelative := !strings.HasPrefix(url.Path, "/") + if isDifferentHost || isRelative { + return url.String() + } + + // Do not rewrite scheme and host if the Transport has empty scheme and host + // when targetURL already contains the sourceRequestHost + if !(url.Host == sourceRequestHost && t.Scheme == "" && t.Host == "") { + url.Scheme = t.Scheme + url.Host = t.Host + } + + origPath := url.Path + // Do not rewrite URL if the sourceURL already contains the necessary prefix. + if strings.HasPrefix(url.Path, t.PathPrepend) { + return url.String() + } + url.Path = path.Join(t.PathPrepend, url.Path) + if strings.HasSuffix(origPath, "/") { + // Add back the trailing slash, which was stripped by path.Join(). + url.Path += "/" + } + + return url.String() +} + +// rewriteHTML scans the HTML for tags with url-valued attributes, and updates +// those values with the urlRewriter function. The updated HTML is output to the +// writer. +func rewriteHTML(reader io.Reader, writer io.Writer, urlRewriter func(*url.URL) string) error { + // Note: This assumes the content is UTF-8. + tokenizer := html.NewTokenizer(reader) + + var err error + for err == nil { + tokenType := tokenizer.Next() + switch tokenType { + case html.ErrorToken: + err = tokenizer.Err() + case html.StartTagToken, html.SelfClosingTagToken: + token := tokenizer.Token() + if urlAttrs, ok := atomsToAttrs[token.DataAtom]; ok { + for i, attr := range token.Attr { + if urlAttrs.Has(attr.Key) { + url, err := url.Parse(attr.Val) + if err != nil { + // Do not rewrite the URL if it isn't valid. It is intended not + // to error here to prevent the inability to understand the + // content of the body to cause a fatal error. + continue + } + token.Attr[i].Val = urlRewriter(url) + } + } + } + _, err = writer.Write([]byte(token.String())) + default: + _, err = writer.Write(tokenizer.Raw()) + } + } + if err != io.EOF { + return err + } + return nil +} + +// rewriteResponse modifies an HTML response by updating absolute links referring +// to the original host to instead refer to the proxy transport. +func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*http.Response, error) { + origBody := resp.Body + defer origBody.Close() + + newContent := &bytes.Buffer{} + var reader io.Reader = origBody + var writer io.Writer = newContent + encoding := resp.Header.Get("Content-Encoding") + switch encoding { + case "gzip": + var err error + reader, err = gzip.NewReader(reader) + if err != nil { + return nil, fmt.Errorf("errorf making gzip reader: %v", err) + } + gzw := gzip.NewWriter(writer) + defer gzw.Close() + writer = gzw + case "deflate": + var err error + reader = flate.NewReader(reader) + flw, err := flate.NewWriter(writer, flate.BestCompression) + if err != nil { + return nil, fmt.Errorf("errorf making flate writer: %v", err) + } + defer func() { + flw.Close() + flw.Flush() + }() + writer = flw + case "": + // This is fine + default: + // Some encoding we don't understand-- don't try to parse this + klog.FromContext(req.Context()).Error(nil, "Proxy encountered unknown encoding for text/html, can't understand this so not fixing links", "encoding", encoding) + return resp, nil + } + + urlRewriter := func(targetUrl *url.URL) string { + return t.rewriteURL(targetUrl, req.URL, req.Host) + } + err := rewriteHTML(reader, writer, urlRewriter) + if err != nil { + klog.FromContext(req.Context()).Error(err, "Failed to rewrite URLs") + return resp, err + } + + resp.Body = io.NopCloser(newContent) + // Update header node with new content-length + // TODO: Remove any hash/signature headers here? + resp.Header.Del("Content-Length") + resp.ContentLength = int64(newContent.Len()) + + return resp, err +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/transport_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/transport_test.go new file mode 100644 index 0000000000..a50f1e406c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/transport_test.go @@ -0,0 +1,396 @@ +/* +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 proxy + +import ( + "bytes" + "compress/flate" + "compress/gzip" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +func parseURLOrDie(inURL string) *url.URL { + parsed, err := url.Parse(inURL) + if err != nil { + panic(err) + } + return parsed +} + +func TestProxyTransport(t *testing.T) { + testTransport := &Transport{ + Scheme: "http", + Host: "foo.com", + PathPrepend: "/proxy/node/node1:10250", + } + testTransport2 := &Transport{ + Scheme: "https", + Host: "foo.com", + PathPrepend: "/proxy/node/node1:8080", + } + emptyHostTransport := &Transport{ + Scheme: "https", + PathPrepend: "/proxy/node/node1:10250", + } + emptySchemeTransport := &Transport{ + Host: "foo.com", + PathPrepend: "/proxy/node/node1:10250", + } + emptyHostAndSchemeTransport := &Transport{ + PathPrepend: "/proxy/node/node1:10250", + } + type Item struct { + input string + sourceURL string + transport *Transport + output string + contentType string + forwardedURI string + redirect string + redirectWant string + reqHost string + } + + table := map[string]Item{ + "normal": { + input: `
kubelet.loggoogle.log
`, + sourceURL: "http://mynode.com/logs/log.log", + transport: testTransport, + output: `
kubelet.loggoogle.log
`, + contentType: "text/html", + forwardedURI: "/proxy/node/node1:10250/logs/log.log", + }, + "full document": { + input: `
kubelet.loggoogle.log
`, + sourceURL: "http://mynode.com/logs/log.log", + transport: testTransport, + output: `
kubelet.loggoogle.log
`, + contentType: "text/html", + forwardedURI: "/proxy/node/node1:10250/logs/log.log", + }, + "trailing slash": { + input: `
kubelet.loggoogle.log
`, + sourceURL: "http://mynode.com/logs/log.log", + transport: testTransport, + output: `
kubelet.loggoogle.log
`, + contentType: "text/html", + forwardedURI: "/proxy/node/node1:10250/logs/log.log", + }, + "content-type charset": { + input: `
kubelet.loggoogle.log
`, + sourceURL: "http://mynode.com/logs/log.log", + transport: testTransport, + output: `
kubelet.loggoogle.log
`, + contentType: "text/html; charset=utf-8", + forwardedURI: "/proxy/node/node1:10250/logs/log.log", + }, + "content-type passthrough": { + input: `
kubelet.loggoogle.log
`, + sourceURL: "http://mynode.com/logs/log.log", + transport: testTransport, + output: `
kubelet.loggoogle.log
`, + contentType: "text/plain", + forwardedURI: "/proxy/node/node1:10250/logs/log.log", + }, + "subdir": { + input: `kubelet.loggoogle.log`, + sourceURL: "http://mynode.com/whatever/apt/somelog.log", + transport: testTransport2, + output: `kubelet.loggoogle.log`, + contentType: "text/html", + forwardedURI: "/proxy/node/node1:8080/whatever/apt/somelog.log", + }, + "image": { + input: `
`, + sourceURL: "http://mynode.com/", + transport: testTransport, + output: `
`, + contentType: "text/html", + forwardedURI: "/proxy/node/node1:10250/", + }, + "abs": { + input: `", + transport: testTransport, + output: "", + contentType: "text/html", + forwardedURI: "/proxy/node/node1:10250/logs/log.log%00%3Cscript%3Ealert%281%29%3C/script%3E", + }, + "redirect rel must be escaped": { + sourceURL: "http://mynode.com/redirect", + transport: testTransport, + redirect: "/redirected/target/%00/", + redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", + forwardedURI: "/proxy/node/node1:10250/redirect", + }, + "redirect abs same host must be escaped": { + sourceURL: "http://mynode.com/redirect", + transport: testTransport, + redirect: "http://mynode.com/redirected/target/%00/", + redirectWant: "http://foo.com/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", + forwardedURI: "/proxy/node/node1:10250/redirect", + }, + "redirect abs other host must be escaped": { + sourceURL: "http://mynode.com/redirect", + transport: testTransport, + redirect: "http://example.com/redirected/target/%00/", + redirectWant: "http://example.com/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", + forwardedURI: "/proxy/node/node1:10250/redirect", + }, + "redirect abs use reqHost no host no scheme must be escaped": { + sourceURL: "http://mynode.com/redirect", + transport: emptyHostAndSchemeTransport, + redirect: "http://10.0.0.1:8001/redirected/target/%00/", + redirectWant: "http://10.0.0.1:8001/proxy/node/node1:10250/redirected/target/%00%3Cscript%3Ealert%281%29%3C/script%3E/", + forwardedURI: "/proxy/node/node1:10250/redirect", + reqHost: "10.0.0.1:8001", + }, + } + + testItem := func(name string, item *Item) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check request headers. + if got, want := r.Header.Get("X-Forwarded-Uri"), item.forwardedURI; got != want { + t.Errorf("%v: X-Forwarded-Uri = %q, want %q", name, got, want) + } + if len(item.transport.Host) == 0 { + _, present := r.Header["X-Forwarded-Host"] + if present { + t.Errorf("%v: X-Forwarded-Host header should not be present", name) + } + } else { + if got, want := r.Header.Get("X-Forwarded-Host"), item.transport.Host; got != want { + t.Errorf("%v: X-Forwarded-Host = %q, want %q", name, got, want) + } + } + if len(item.transport.Scheme) == 0 { + _, present := r.Header["X-Forwarded-Proto"] + if present { + t.Errorf("%v: X-Forwarded-Proto header should not be present", name) + } + } else { + if got, want := r.Header.Get("X-Forwarded-Proto"), item.transport.Scheme; got != want { + t.Errorf("%v: X-Forwarded-Proto = %q, want %q", name, got, want) + } + } + + // Send response. + if item.redirect != "" { + http.Redirect(w, r, item.redirect, http.StatusMovedPermanently) + return + } + w.Header().Set("Content-Type", item.contentType) + fmt.Fprint(w, item.input) + })) + defer server.Close() + + // Replace source URL with our test server address. + sourceURL := parseURLOrDie(item.sourceURL) + serverURL := parseURLOrDie(server.URL) + item.input = strings.Replace(item.input, sourceURL.Host, serverURL.Host, -1) + item.redirect = strings.Replace(item.redirect, sourceURL.Host, serverURL.Host, -1) + sourceURL.Host = serverURL.Host + + req, err := http.NewRequest(http.MethodGet, sourceURL.String(), nil) + if err != nil { + t.Errorf("%v: Unexpected error: %v", name, err) + return + } + if item.reqHost != "" { + req.Host = item.reqHost + } + resp, err := item.transport.RoundTrip(req) + if err != nil { + t.Errorf("%v: Unexpected error: %v", name, err) + return + } + if item.redirect != "" { + // Check that redirect URLs get rewritten properly. + if got, want := resp.Header.Get("Location"), item.redirectWant; got != want { + t.Errorf("%v: Location header = %q, want %q", name, got, want) + } + return + } + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("%v: Unexpected error: %v", name, err) + return + } + if e, a := item.output, string(body); e != a { + t.Errorf("%v: expected %v, but got %v", name, e, a) + } + } + + for name, item := range table { + testItem(name, &item) + } +} + +func TestRewriteResponse(t *testing.T) { + gzipbuf := bytes.NewBuffer(nil) + flatebuf := bytes.NewBuffer(nil) + + testTransport := &Transport{ + Scheme: "http", + Host: "foo.com", + PathPrepend: "/proxy/node/node1:10250", + } + expected := []string{ + "short body test", + strings.Repeat("long body test", 4097), + } + test := []struct { + encodeType string + writer func(string) *http.Response + reader func(*http.Response) string + }{ + { + encodeType: "gzip", + writer: func(ept string) *http.Response { + gzw := gzip.NewWriter(gzipbuf) + defer gzw.Close() + + gzw.Write([]byte(ept)) + gzw.Flush() + return &http.Response{ + Body: io.NopCloser(gzipbuf), + } + }, + reader: func(rep *http.Response) string { + reader, _ := gzip.NewReader(rep.Body) + s, _ := io.ReadAll(reader) + return string(s) + }, + }, + { + encodeType: "deflate", + writer: func(ept string) *http.Response { + flw, _ := flate.NewWriter(flatebuf, flate.BestCompression) + defer flw.Close() + + flw.Write([]byte(ept)) + flw.Flush() + return &http.Response{ + Body: io.NopCloser(flatebuf), + } + }, + reader: func(rep *http.Response) string { + reader := flate.NewReader(rep.Body) + s, _ := io.ReadAll(reader) + return string(s) + }, + }, + } + + errFn := func(encode string, err error) { + t.Errorf("%s failed to read and write: %v", encode, err) + } + for _, v := range test { + request, _ := http.NewRequest(http.MethodGet, "http://mynode.com/", nil) + request.Header.Set("Content-Encoding", v.encodeType) + request.Header.Add("Accept-Encoding", v.encodeType) + + for _, exp := range expected { + resp := v.writer(exp) + gotResponse, err := testTransport.rewriteResponse(request, resp) + + if err != nil { + errFn(v.encodeType, err) + } + + result := v.reader(gotResponse) + if result != exp { + errFn(v.encodeType, fmt.Errorf("expected %s, get %s", exp, result)) + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go new file mode 100644 index 0000000000..56f6214834 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go @@ -0,0 +1,559 @@ +/* +Copyright 2017 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 proxy + +import ( + "bufio" + "bytes" + "fmt" + "io" + "log" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "strings" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + utilnet "k8s.io/apimachinery/pkg/util/net" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/streaming/pkg/httpstream" + + "github.com/mxk/go-flowrate/flowrate" + + "k8s.io/klog/v2" +) + +// UpgradeRequestRoundTripper provides an additional method to decorate a request +// with any authentication or other protocol level information prior to performing +// an upgrade on the server. Any response will be handled by the intercepting +// proxy. +type UpgradeRequestRoundTripper interface { + http.RoundTripper + // WrapRequest takes a valid HTTP request and returns a suitably altered version + // of request with any HTTP level values required to complete the request half of + // an upgrade on the server. It does not get a chance to see the response and + // should bypass any request side logic that expects to see the response. + WrapRequest(*http.Request) (*http.Request, error) +} + +// UpgradeAwareHandler is a handler for proxy requests that may require an upgrade +type UpgradeAwareHandler struct { + // UpgradeRequired will reject non-upgrade connections if true. + UpgradeRequired bool + // Location is the location of the upstream proxy. It is used as the location to Dial on the upstream server + // for upgrade requests unless UseRequestLocationOnUpgrade is true. + Location *url.URL + // AppendLocationPath determines if the original path of the Location should be appended to the upstream proxy request path + AppendLocationPath bool + // Transport provides an optional round tripper to use to proxy. If nil, the default proxy transport is used + Transport http.RoundTripper + // UpgradeTransport, if specified, will be used as the backend transport when upgrade requests are provided. + // This allows clients to disable HTTP/2. + UpgradeTransport UpgradeRequestRoundTripper + // WrapTransport indicates whether the provided Transport should be wrapped with default proxy transport behavior (URL rewriting, X-Forwarded-* header setting) + WrapTransport bool + // UseRequestLocation will use the incoming request URL when talking to the backend server. + UseRequestLocation bool + // UseLocationHost overrides the HTTP host header in requests to the backend server to use the Host from Location. + // This will override the req.Host field of a request, while UseRequestLocation will override the req.URL field + // of a request. The req.URL.Host specifies the server to connect to, while the req.Host field + // specifies the Host header value to send in the HTTP request. If this is false, the incoming req.Host header will + // just be forwarded to the backend server. + UseLocationHost bool + // FlushInterval controls how often the standard HTTP proxy will flush content from the upstream. + FlushInterval time.Duration + // MaxBytesPerSec controls the maximum rate for an upstream connection. No rate is imposed if the value is zero. + MaxBytesPerSec int64 + // Responder is passed errors that occur while setting up proxying. + Responder ErrorResponder + // Reject to forward redirect response + RejectForwardingRedirects bool +} + +const defaultFlushInterval = 200 * time.Millisecond + +// ErrorResponder abstracts error reporting to the proxy handler to remove the need to hardcode a particular +// error format. +type ErrorResponder interface { + Error(w http.ResponseWriter, req *http.Request, err error) +} + +// SimpleErrorResponder is the legacy implementation of ErrorResponder for callers that only +// service a single request/response per proxy. +type SimpleErrorResponder interface { + Error(err error) +} + +func NewErrorResponder(r SimpleErrorResponder) ErrorResponder { + return simpleResponder{r} +} + +type simpleResponder struct { + responder SimpleErrorResponder +} + +func (r simpleResponder) Error(w http.ResponseWriter, req *http.Request, err error) { + r.responder.Error(err) +} + +// upgradeRequestRoundTripper implements proxy.UpgradeRequestRoundTripper. +type upgradeRequestRoundTripper struct { + http.RoundTripper + upgrader http.RoundTripper +} + +var ( + _ UpgradeRequestRoundTripper = &upgradeRequestRoundTripper{} + _ utilnet.RoundTripperWrapper = &upgradeRequestRoundTripper{} +) + +// WrappedRoundTripper returns the round tripper that a caller would use. +func (rt *upgradeRequestRoundTripper) WrappedRoundTripper() http.RoundTripper { + return rt.RoundTripper +} + +// WriteToRequest calls the nested upgrader and then copies the returned request +// fields onto the passed request. +func (rt *upgradeRequestRoundTripper) WrapRequest(req *http.Request) (*http.Request, error) { + resp, err := rt.upgrader.RoundTrip(req) + if err != nil { + return nil, err + } + return resp.Request, nil +} + +// onewayRoundTripper captures the provided request - which is assumed to have +// been modified by other round trippers - and then returns a fake response. +type onewayRoundTripper struct{} + +// RoundTrip returns a simple 200 OK response that captures the provided request. +func (onewayRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Body: io.NopCloser(&bytes.Buffer{}), + Request: req, + }, nil +} + +// MirrorRequest is a round tripper that can be called to get back the calling request as +// the core round tripper in a chain. +var MirrorRequest http.RoundTripper = onewayRoundTripper{} + +// NewUpgradeRequestRoundTripper takes two round trippers - one for the underlying TCP connection, and +// one that is able to write headers to an HTTP request. The request rt is used to set the request headers +// and that is written to the underlying connection rt. +func NewUpgradeRequestRoundTripper(connection, request http.RoundTripper) UpgradeRequestRoundTripper { + return &upgradeRequestRoundTripper{ + RoundTripper: connection, + upgrader: request, + } +} + +// normalizeLocation returns the result of parsing the full URL, with scheme set to http if missing +func normalizeLocation(location *url.URL) *url.URL { + normalized, _ := url.Parse(location.String()) + if len(normalized.Scheme) == 0 { + normalized.Scheme = "http" + } + return normalized +} + +// NewUpgradeAwareHandler creates a new proxy handler with a default flush interval. Responder is required for returning +// errors to the caller. +func NewUpgradeAwareHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder ErrorResponder) *UpgradeAwareHandler { + return &UpgradeAwareHandler{ + Location: normalizeLocation(location), + Transport: transport, + WrapTransport: wrapTransport, + UpgradeRequired: upgradeRequired, + FlushInterval: defaultFlushInterval, + Responder: responder, + } +} + +func proxyRedirectsforRootPath(path string, w http.ResponseWriter, req *http.Request) bool { + redirect := false + method := req.Method + + // From pkg/genericapiserver/endpoints/handlers/proxy.go#ServeHTTP: + // Redirect requests with an empty path to a location that ends with a '/' + // This is essentially a hack for https://issue.k8s.io/4958. + // Note: Keep this code after tryUpgrade to not break that flow. + if len(path) == 0 && (method == http.MethodGet || method == http.MethodHead) { + var queryPart string + if len(req.URL.RawQuery) > 0 { + queryPart = "?" + req.URL.RawQuery + } + w.Header().Set("Location", req.URL.Path+"/"+queryPart) + w.WriteHeader(http.StatusMovedPermanently) + redirect = true + } + return redirect +} + +// ServeHTTP handles the proxy request +func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if h.tryUpgrade(w, req) { + return + } + if h.UpgradeRequired { + h.Responder.Error(w, req, errors.NewBadRequest("Upgrade request required")) + return + } + + loc := *h.Location + loc.RawQuery = req.URL.RawQuery + + // If original request URL ended in '/', append a '/' at the end of the + // of the proxy URL + if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") { + loc.Path += "/" + } + + proxyRedirect := proxyRedirectsforRootPath(loc.Path, w, req) + if proxyRedirect { + return + } + + if h.Transport == nil || h.WrapTransport { + h.Transport = h.defaultProxyTransport(req.URL, h.Transport) + } + + // WithContext creates a shallow clone of the request with the same context. + newReq := req.WithContext(req.Context()) + newReq.Header = utilnet.CloneHeader(req.Header) + if !h.UseRequestLocation { + newReq.URL = &loc + } + if h.UseLocationHost { + // exchanging req.Host with the backend location is necessary for backends that act on the HTTP host header (e.g. API gateways), + // because req.Host has preference over req.URL.Host in filling this header field + newReq.Host = h.Location.Host + } + + // create the target location to use for the reverse proxy + reverseProxyLocation := &url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host} + if h.AppendLocationPath { + reverseProxyLocation.Path = h.Location.Path + } + + proxy := httputil.NewSingleHostReverseProxy(reverseProxyLocation) + proxy.Transport = h.Transport + proxy.FlushInterval = h.FlushInterval + proxy.ErrorLog = log.New(noSuppressPanicError{}, "", log.LstdFlags) + if h.RejectForwardingRedirects { + oldModifyResponse := proxy.ModifyResponse + proxy.ModifyResponse = func(response *http.Response) error { + code := response.StatusCode + if code >= 300 && code <= 399 && len(response.Header.Get("Location")) > 0 { + // close the original response + response.Body.Close() + msg := "the backend attempted to redirect this request, which is not permitted" + // replace the response + *response = http.Response{ + StatusCode: http.StatusBadGateway, + Status: fmt.Sprintf("%d %s", response.StatusCode, http.StatusText(response.StatusCode)), + Body: io.NopCloser(strings.NewReader(msg)), + ContentLength: int64(len(msg)), + } + } else { + if oldModifyResponse != nil { + if err := oldModifyResponse(response); err != nil { + return err + } + } + } + return nil + } + } + if h.Responder != nil { + // if an optional error interceptor/responder was provided wire it + // the custom responder might be used for providing a unified error reporting + // or supporting retry mechanisms by not sending non-fatal errors to the clients + proxy.ErrorHandler = h.Responder.Error + } + proxy.ServeHTTP(w, newReq) +} + +type noSuppressPanicError struct{} + +func (noSuppressPanicError) Write(p []byte) (n int, err error) { + // skip "suppressing panic for copyResponse error in test; copy error" error message + // that ends up in CI tests on each kube-apiserver termination as noise and + // everybody thinks this is fatal. + if strings.Contains(string(p), "suppressing panic") { + return len(p), nil + } + return os.Stderr.Write(p) +} + +// tryUpgrade returns true if the request was handled. +func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Request) bool { + logger := klog.FromContext(req.Context()) + if !httpstream.IsUpgradeRequest(req) { + logger.V(6).Info("Request was not an upgrade") + return false + } + + var ( + backendConn net.Conn + rawResponse []byte + err error + ) + + location := *h.Location + if h.UseRequestLocation { + location = *req.URL + location.Scheme = h.Location.Scheme + location.Host = h.Location.Host + if h.AppendLocationPath { + location.Path = singleJoiningSlash(h.Location.Path, location.Path) + } + } + + clone := utilnet.CloneRequest(req) + // Only append X-Forwarded-For in the upgrade path, since httputil.NewSingleHostReverseProxy + // handles this in the non-upgrade path. + utilnet.AppendForwardedForHeader(clone) + logger.V(6).Info("Connecting to backend proxy (direct dial)", "location", &location, "headers", clone.Header) + if h.UseLocationHost { + clone.Host = h.Location.Host + } + clone.URL = &location + logger.V(6).Info("UpgradeAwareProxy: dialing for SPDY upgrade with headers", "headers", clone.Header) + backendConn, err = h.DialForUpgrade(clone) + if err != nil { + logger.V(6).Info("Proxy connection error", "err", err) + h.Responder.Error(w, req, err) + return true + } + defer backendConn.Close() + + // determine the http response code from the backend by reading from rawResponse+backendConn + backendHTTPResponse, headerBytes, err := getResponse(io.MultiReader(bytes.NewReader(rawResponse), backendConn)) + if err != nil { + logger.V(6).Info("Proxy connection error", "err", err) + h.Responder.Error(w, req, err) + return true + } + if len(headerBytes) > len(rawResponse) { + // we read beyond the bytes stored in rawResponse, update rawResponse to the full set of bytes read from the backend + rawResponse = headerBytes + } + + // If the backend did not upgrade the request, return an error to the client. If the response was + // an error, the error is forwarded directly after the connection is hijacked. Otherwise, just + // return a generic error here. + if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols && backendHTTPResponse.StatusCode < 400 { + err := fmt.Errorf("invalid upgrade response: status code %d", backendHTTPResponse.StatusCode) + logger.Error(err, "Proxy upgrade error") + h.Responder.Error(w, req, err) + return true + } + + // Once the connection is hijacked, the ErrorResponder will no longer work, so + // hijacking should be the last step in the upgrade. + requestHijacker, ok := w.(http.Hijacker) + if !ok { + logger.Error(nil, "Unable to hijack response writer", "type", fmt.Sprintf("%T", w)) + h.Responder.Error(w, req, fmt.Errorf("request connection cannot be hijacked: %T", w)) + return true + } + requestHijackedConn, _, err := requestHijacker.Hijack() + if err != nil { + logger.Error(err, "Unable to hijack response") + h.Responder.Error(w, req, fmt.Errorf("error hijacking connection: %v", err)) + return true + } + defer requestHijackedConn.Close() + + if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols { + // If the backend did not upgrade the request, echo the response from the backend to the client and return, closing the connection. + logger.V(6).Info("Proxy upgrade error", "statusCode", backendHTTPResponse.StatusCode) + // set read/write deadlines + deadline := time.Now().Add(10 * time.Second) + backendConn.SetReadDeadline(deadline) + requestHijackedConn.SetWriteDeadline(deadline) + // write the response to the client + err := backendHTTPResponse.Write(requestHijackedConn) + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + logger.Error(err, "Error proxying data from backend to client") + } + // Indicate we handled the request + return true + } + + // Forward raw response bytes back to client. + if len(rawResponse) > 0 { + logger.V(6).Info("Writing to hijacked connection", "length", len(rawResponse)) + if _, err = requestHijackedConn.Write(rawResponse); err != nil { + utilruntime.HandleErrorWithLogger(logger, err, "Error proxying response from backend to client") + } + } + + // Proxy the connection. This is bidirectional, so we need a goroutine + // to copy in each direction. Once one side of the connection exits, we + // exit the function which performs cleanup and in the process closes + // the other half of the connection in the defer. + writerComplete := make(chan struct{}) + readerComplete := make(chan struct{}) + + go func() { + var writer io.WriteCloser + if h.MaxBytesPerSec > 0 { + writer = flowrate.NewWriter(backendConn, h.MaxBytesPerSec) + } else { + writer = backendConn + } + _, err := io.Copy(writer, requestHijackedConn) + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + logger.Error(err, "Error proxying data from client to backend") + } + close(writerComplete) + }() + + go func() { + var reader io.ReadCloser + if h.MaxBytesPerSec > 0 { + reader = flowrate.NewReader(backendConn, h.MaxBytesPerSec) + } else { + reader = backendConn + } + _, err := io.Copy(requestHijackedConn, reader) + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + logger.Error(err, "Error proxying data from backend to client") + } + close(readerComplete) + }() + + // Wait for one half the connection to exit. Once it does the defer will + // clean up the other half of the connection. + select { + case <-writerComplete: + case <-readerComplete: + } + logger.V(6).Info("Disconnecting from backend proxy", "location", &location, "headers", clone.Header) + + return true +} + +// FIXME: Taken from net/http/httputil/reverseproxy.go as singleJoiningSlash is not exported to be re-used. +// See-also: https://github.com/golang/go/issues/44290 +func singleJoiningSlash(a, b string) string { + aslash := strings.HasSuffix(a, "/") + bslash := strings.HasPrefix(b, "/") + switch { + case aslash && bslash: + return a + b[1:] + case !aslash && !bslash: + return a + "/" + b + } + return a + b +} + +func (h *UpgradeAwareHandler) DialForUpgrade(req *http.Request) (net.Conn, error) { + if h.UpgradeTransport == nil { + return dial(req, h.Transport) + } + updatedReq, err := h.UpgradeTransport.WrapRequest(req) + if err != nil { + return nil, err + } + return dial(updatedReq, h.UpgradeTransport) +} + +// getResponseCode reads a http response from the given reader, returns the response, +// the bytes read from the reader, and any error encountered +func getResponse(r io.Reader) (*http.Response, []byte, error) { + rawResponse := bytes.NewBuffer(make([]byte, 0, 256)) + // Save the bytes read while reading the response headers into the rawResponse buffer + resp, err := http.ReadResponse(bufio.NewReader(io.TeeReader(r, rawResponse)), nil) + if err != nil { + return nil, nil, err + } + // return the http response and the raw bytes consumed from the reader in the process + return resp, rawResponse.Bytes(), nil +} + +// dial dials the backend at req.URL and writes req to it. +func dial(req *http.Request, transport http.RoundTripper) (net.Conn, error) { + conn, err := DialURL(req.Context(), req.URL, transport) + if err != nil { + return nil, fmt.Errorf("error dialing backend: %v", err) + } + + if err = req.Write(conn); err != nil { + conn.Close() + return nil, fmt.Errorf("error sending request: %v", err) + } + + return conn, err +} + +func (h *UpgradeAwareHandler) defaultProxyTransport(url *url.URL, internalTransport http.RoundTripper) http.RoundTripper { + scheme := url.Scheme + host := url.Host + suffix := h.Location.Path + if strings.HasSuffix(url.Path, "/") && !strings.HasSuffix(suffix, "/") { + suffix += "/" + } + pathPrepend := strings.TrimSuffix(url.Path, suffix) + rewritingTransport := &Transport{ + Scheme: scheme, + Host: host, + PathPrepend: pathPrepend, + RoundTripper: internalTransport, + } + return &corsRemovingTransport{ + RoundTripper: rewritingTransport, + } +} + +// corsRemovingTransport is a wrapper for an internal transport. It removes CORS headers +// from the internal response. +// Implements pkg/util/net.RoundTripperWrapper +type corsRemovingTransport struct { + http.RoundTripper +} + +var _ = utilnet.RoundTripperWrapper(&corsRemovingTransport{}) + +func (rt *corsRemovingTransport) RoundTrip(req *http.Request) (*http.Response, error) { + resp, err := rt.RoundTripper.RoundTrip(req) + if err != nil { + return nil, err + } + removeCORSHeaders(resp) + return resp, nil +} + +func (rt *corsRemovingTransport) WrappedRoundTripper() http.RoundTripper { + return rt.RoundTripper +} + +// removeCORSHeaders strip CORS headers sent from the backend +// This should be called on all responses before returning +func removeCORSHeaders(resp *http.Response) { + resp.Header.Del("Access-Control-Allow-Credentials") + resp.Header.Del("Access-Control-Allow-Headers") + resp.Header.Del("Access-Control-Allow-Methods") + resp.Header.Del("Access-Control-Allow-Origin") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go new file mode 100644 index 0000000000..4a0f821feb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go @@ -0,0 +1,1261 @@ +/* +Copyright 2017 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 proxy + +import ( + "bufio" + "bytes" + "compress/gzip" + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/httptest" + "net/http/httputil" + "net/url" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "golang.org/x/net/websocket" + + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/streaming/pkg/httpstream" +) + +const fakeStatusCode = 567 + +type fakeResponder struct { + t *testing.T + called bool + err error + // called chan error + w http.ResponseWriter +} + +func (r *fakeResponder) Error(w http.ResponseWriter, req *http.Request, err error) { + if r.called { + r.t.Errorf("Error responder called again!\nprevious error: %v\nnew error: %v", r.err, err) + } + + w.WriteHeader(fakeStatusCode) + _, writeErr := w.Write([]byte(err.Error())) + assert.NoError(r.t, writeErr) + + r.called = true + r.err = err +} + +type fakeConn struct { + err error // The error to return when io is performed over the connection. +} + +func (f *fakeConn) Read([]byte) (int, error) { return 0, f.err } +func (f *fakeConn) Write([]byte) (int, error) { return 0, f.err } +func (f *fakeConn) Close() error { return nil } +func (fakeConn) LocalAddr() net.Addr { return nil } +func (fakeConn) RemoteAddr() net.Addr { return nil } +func (fakeConn) SetDeadline(t time.Time) error { return nil } +func (fakeConn) SetReadDeadline(t time.Time) error { return nil } +func (fakeConn) SetWriteDeadline(t time.Time) error { return nil } + +type SimpleBackendHandler struct { + requestURL url.URL + requestHost string + requestHeader http.Header + requestBody []byte + requestMethod string + responseBody string + responseHeader map[string]string + t *testing.T +} + +func (s *SimpleBackendHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + s.requestURL = *req.URL + s.requestHost = req.Host + s.requestHeader = req.Header + s.requestMethod = req.Method + var err error + s.requestBody, err = io.ReadAll(req.Body) + if err != nil { + s.t.Errorf("Unexpected error: %v", err) + return + } + + if s.responseHeader != nil { + for k, v := range s.responseHeader { + w.Header().Add(k, v) + } + } + w.Write([]byte(s.responseBody)) +} + +func validateParameters(t *testing.T, name string, actual url.Values, expected map[string]string) { + for k, v := range expected { + actualValue, ok := actual[k] + if !ok { + t.Errorf("%s: Expected parameter %s not received", name, k) + continue + } + if actualValue[0] != v { + t.Errorf("%s: Parameter %s values don't match. Actual: %#v, Expected: %s", + name, k, actualValue, v) + } + } +} + +func validateHeaders(t *testing.T, name string, actual http.Header, expected map[string]string, notExpected []string) { + for k, v := range expected { + actualValue, ok := actual[k] + if !ok { + t.Errorf("%s: Expected header %s not received", name, k) + continue + } + if actualValue[0] != v { + t.Errorf("%s: Header %s values don't match. Actual: %s, Expected: %s", + name, k, actualValue, v) + } + } + if notExpected == nil { + return + } + for _, h := range notExpected { + if _, present := actual[h]; present { + t.Errorf("%s: unexpected header: %s", name, h) + } + } +} + +func TestServeHTTP(t *testing.T) { + tests := []struct { + name string + method string + requestPath string + expectedPath string + requestBody string + requestParams map[string]string + requestHeader map[string]string + responseHeader map[string]string + expectedRespHeader map[string]string + notExpectedRespHeader []string + upgradeRequired bool + appendLocationPath bool + expectError func(err error) bool + useLocationHost bool + }{ + { + name: "root path, simple get", + method: http.MethodGet, + requestPath: "/", + expectedPath: "/", + }, + { + name: "no upgrade header sent", + method: http.MethodGet, + requestPath: "/", + upgradeRequired: true, + expectError: func(err error) bool { + return err != nil && strings.Contains(err.Error(), "Upgrade request required") + }, + }, + { + name: "simple path, get", + method: http.MethodGet, + requestPath: "/path/to/test", + expectedPath: "/path/to/test", + }, + { + name: "request params", + method: http.MethodPost, + requestPath: "/some/path/", + expectedPath: "/some/path/", + requestParams: map[string]string{"param1": "value/1", "param2": "value%2"}, + requestBody: "test request body", + }, + { + name: "request headers", + method: http.MethodPut, + requestPath: "/some/path", + expectedPath: "/some/path", + requestHeader: map[string]string{"Header1": "value1", "Header2": "value2"}, + }, + { + name: "empty path - slash should be added", + method: http.MethodGet, + requestPath: "", + expectedPath: "/", + }, + { + name: "remove CORS headers", + method: http.MethodGet, + requestPath: "/some/path", + expectedPath: "/some/path", + responseHeader: map[string]string{ + "Header1": "value1", + "Access-Control-Allow-Origin": "some.server", + "Access-Control-Allow-Methods": http.MethodGet}, + expectedRespHeader: map[string]string{ + "Header1": "value1", + }, + notExpectedRespHeader: []string{ + "Access-Control-Allow-Origin", + "Access-Control-Allow-Methods", + }, + }, + { + name: "use location host", + method: http.MethodGet, + requestPath: "/some/path", + expectedPath: "/some/path", + useLocationHost: true, + }, + { + name: "use location host - invalid upgrade", + method: http.MethodGet, + upgradeRequired: true, + requestHeader: map[string]string{ + httpstream.HeaderConnection: httpstream.HeaderUpgrade, + }, + expectError: func(err error) bool { + return err != nil && strings.Contains(err.Error(), "invalid upgrade response: status code 200") + }, + requestPath: "/some/path", + expectedPath: "/some/path", + useLocationHost: true, + }, + { + name: "append server path to request path", + method: http.MethodGet, + requestPath: "/base", + expectedPath: "/base/base", + appendLocationPath: true, + }, + { + name: "append server path to request path with ending slash", + method: http.MethodGet, + requestPath: "/base/", + expectedPath: "/base/base/", + appendLocationPath: true, + }, + { + name: "don't append server path to request path", + method: http.MethodGet, + requestPath: "/base", + expectedPath: "/base", + appendLocationPath: false, + }, + } + + for i, test := range tests { + func() { + backendResponse := "Hello" + backendResponseHeader := test.responseHeader + // Test a simple header if not specified in the test + if backendResponseHeader == nil && test.expectedRespHeader == nil { + backendResponseHeader = map[string]string{"Content-Type": "text/html"} + test.expectedRespHeader = map[string]string{"Content-Type": "text/html"} + } + backendHandler := &SimpleBackendHandler{ + responseBody: backendResponse, + responseHeader: backendResponseHeader, + } + backendServer := httptest.NewServer(backendHandler) + defer backendServer.Close() + + responder := &fakeResponder{t: t} + backendURL, _ := url.Parse(backendServer.URL) + backendURL.Path = test.requestPath + proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, test.upgradeRequired, responder) + proxyHandler.UseLocationHost = test.useLocationHost + proxyHandler.AppendLocationPath = test.appendLocationPath + proxyServer := httptest.NewServer(proxyHandler) + defer proxyServer.Close() + proxyURL, _ := url.Parse(proxyServer.URL) + proxyURL.Path = test.requestPath + paramValues := url.Values{} + for k, v := range test.requestParams { + paramValues[k] = []string{v} + } + proxyURL.RawQuery = paramValues.Encode() + var requestBody io.Reader + if test.requestBody != "" { + requestBody = bytes.NewBufferString(test.requestBody) + } + req, err := http.NewRequest(test.method, proxyURL.String(), requestBody) + if test.requestHeader != nil { + header := http.Header{} + for k, v := range test.requestHeader { + header.Add(k, v) + } + req.Header = header + } + if err != nil { + t.Errorf("Error creating client request: %v", err) + } + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + t.Errorf("Error from proxy request: %v", err) + } + + // Host + if test.useLocationHost && backendHandler.requestHost != backendURL.Host { + t.Errorf("Unexpected request host: %s", backendHandler.requestHost) + } else if !test.useLocationHost && backendHandler.requestHost == backendURL.Host { + t.Errorf("Unexpected request host: %s", backendHandler.requestHost) + } + + if test.expectError != nil { + if !responder.called { + t.Errorf("%d: responder was not invoked", i) + return + } + if !test.expectError(responder.err) { + t.Errorf("%d: unexpected error: %v", i, responder.err) + } + return + } + + // Validate backend request + // Method + if backendHandler.requestMethod != test.method { + t.Errorf("Unexpected request method: %s. Expected: %s", + backendHandler.requestMethod, test.method) + } + + // Body + if string(backendHandler.requestBody) != test.requestBody { + t.Errorf("Unexpected request body: %s. Expected: %s", + string(backendHandler.requestBody), test.requestBody) + } + + // Path + if backendHandler.requestURL.Path != test.expectedPath { + t.Errorf("Unexpected request path: %s", backendHandler.requestURL.Path) + } + // Parameters + validateParameters(t, test.name, backendHandler.requestURL.Query(), test.requestParams) + + // Headers + validateHeaders(t, test.name+" backend request", backendHandler.requestHeader, + test.requestHeader, nil) + + // Validate proxy response + + // Response Headers + validateHeaders(t, test.name+" backend headers", res.Header, test.expectedRespHeader, test.notExpectedRespHeader) + + // Validate Body + responseBody, err := io.ReadAll(res.Body) + if err != nil { + t.Errorf("Unexpected error reading response body: %v", err) + } + if rb := string(responseBody); rb != backendResponse { + t.Errorf("Did not get expected response body: %s. Expected: %s", rb, backendResponse) + } + + // Error + if responder.called { + t.Errorf("Unexpected proxy handler error: %v", responder.err) + } + }() + } +} + +type RoundTripperFunc func(req *http.Request) (*http.Response, error) + +func (fn RoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return fn(req) +} + +func TestProxyUpgrade(t *testing.T) { + + localhostPool := x509.NewCertPool() + if !localhostPool.AppendCertsFromPEM(localhostCert) { + t.Errorf("error setting up localhostCert pool") + } + var d net.Dialer + + testcases := map[string]struct { + ServerFunc func(http.Handler) *httptest.Server + ProxyTransport http.RoundTripper + UpgradeTransport UpgradeRequestRoundTripper + ExpectedAuth string + }{ + "http": { + ServerFunc: httptest.NewServer, + ProxyTransport: nil, + }, + "both client and server support http2, but force to http/1.1 for upgrade": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(exampleCert, exampleKey) + if err != nil { + t.Errorf("https (invalid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + NextProtos: []string{"http2", "http/1.1"}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{ + NextProtos: []string{"http2", "http/1.1"}, + InsecureSkipVerify: true, + }}), + }, + "https (invalid hostname + InsecureSkipVerify)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(exampleCert, exampleKey) + if err != nil { + t.Errorf("https (invalid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}), + }, + "https (valid hostname + RootCAs)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), + }, + "https (valid hostname + RootCAs + custom dialer)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{DialContext: d.DialContext, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), + }, + "https (valid hostname + RootCAs + custom dialer + bearer token)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{DialContext: d.DialContext, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), + UpgradeTransport: NewUpgradeRequestRoundTripper( + utilnet.SetOldTransportDefaults(&http.Transport{DialContext: d.DialContext, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), + RoundTripperFunc(func(req *http.Request) (*http.Response, error) { + req = utilnet.CloneRequest(req) + req.Header.Set("Authorization", "Bearer 1234") + return MirrorRequest.RoundTrip(req) + }), + ), + ExpectedAuth: "Bearer 1234", + }, + } + + for k, tc := range testcases { + tcName := k + backendPath := "/hello" + func() { // Cleanup after each test case. + backend := http.NewServeMux() + backend.Handle("/hello", websocket.Handler(func(ws *websocket.Conn) { + if ws.Request().Header.Get("Authorization") != tc.ExpectedAuth { + t.Errorf("%s: unexpected headers on request: %v", k, ws.Request().Header) + defer ws.Close() + ws.Write([]byte("you failed")) + return + } + defer ws.Close() + body := make([]byte, 5) + ws.Read(body) + ws.Write([]byte("hello " + string(body))) + })) + backend.Handle("/redirect", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/hello", http.StatusFound) + })) + backendServer := tc.ServerFunc(backend) + defer backendServer.Close() + + serverURL, _ := url.Parse(backendServer.URL) + serverURL.Path = backendPath + proxyHandler := NewUpgradeAwareHandler(serverURL, tc.ProxyTransport, false, false, &noErrorsAllowed{t: t}) + proxyHandler.UpgradeTransport = tc.UpgradeTransport + proxy := httptest.NewServer(proxyHandler) + defer proxy.Close() + + ws, err := websocket.Dial("ws://"+proxy.Listener.Addr().String()+"/some/path", "", "http://127.0.0.1/") + if err != nil { + t.Fatalf("%s: websocket dial err: %s", tcName, err) + } + defer ws.Close() + + if _, err := ws.Write([]byte("world")); err != nil { + t.Fatalf("%s: write err: %s", tcName, err) + } + + response := make([]byte, 20) + n, err := ws.Read(response) + if err != nil { + t.Fatalf("%s: read err: %s", tcName, err) + } + if e, a := "hello world", string(response[0:n]); e != a { + t.Fatalf("%s: expected '%#v', got '%#v'", tcName, e, a) + } + }() + } +} + +type noErrorsAllowed struct { + t *testing.T +} + +func (r *noErrorsAllowed) Error(w http.ResponseWriter, req *http.Request, err error) { + r.t.Error(err) +} + +func TestProxyUpgradeConnectionErrorResponse(t *testing.T) { + var ( + responder *fakeResponder + expectedErr = errors.New("EXPECTED") + ) + proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return &fakeConn{err: expectedErr}, nil + }, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + responder = &fakeResponder{t: t, w: w} + proxyHandler := NewUpgradeAwareHandler( + &url.URL{ + Host: "fake-backend", + }, + transport, + false, + true, + responder, + ) + proxyHandler.ServeHTTP(w, r) + })) + defer proxy.Close() + + // Send request to proxy server. + req, err := http.NewRequest(http.MethodPost, "http://"+proxy.Listener.Addr().String()+"/some/path", nil) + require.NoError(t, err) + req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + // Expect error response. + assert.True(t, responder.called) + assert.Equal(t, fakeStatusCode, resp.StatusCode) + msg, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Contains(t, string(msg), expectedErr.Error()) +} + +func TestProxyUpgradeErrorResponseTerminates(t *testing.T) { + for _, code := range []int{400, 500} { + t.Run(fmt.Sprintf("code=%v", code), func(t *testing.T) { + // Set up a backend server + backend := http.NewServeMux() + backend.Handle("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(code) + w.Write([]byte(`some data`)) + })) + backend.Handle("/there", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Error("request to /there") + })) + backendServer := httptest.NewServer(backend) + defer backendServer.Close() + backendServerURL, _ := url.Parse(backendServer.URL) + backendServerURL.Path = "/hello" + + // Set up a proxy pointing to a specific path on the backend + proxyHandler := NewUpgradeAwareHandler(backendServerURL, nil, false, false, &noErrorsAllowed{t: t}) + proxy := httptest.NewServer(proxyHandler) + defer proxy.Close() + proxyURL, _ := url.Parse(proxy.URL) + + conn, err := net.Dial("tcp", proxyURL.Host) + require.NoError(t, err) + bufferedReader := bufio.NewReader(conn) + + // Send upgrade request resulting in a non-101 response from the backend + req, _ := http.NewRequest(http.MethodGet, "/", nil) + req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade) + require.NoError(t, req.Write(conn)) + // Verify we get the correct response and full message body content + resp, err := http.ReadResponse(bufferedReader, nil) + require.NoError(t, err) + data, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, resp.StatusCode, code) + require.Equal(t, data, []byte(`some data`)) + resp.Body.Close() + + // try to read from the connection to verify it was closed + b := make([]byte, 1) + conn.SetReadDeadline(time.Now().Add(time.Second)) + if _, err := conn.Read(b); err != io.EOF { + t.Errorf("expected EOF, got %v", err) + } + + // Send another request to another endpoint to verify it is not received + req, _ = http.NewRequest(http.MethodGet, "/there", nil) + req.Write(conn) + // wait to ensure the handler does not receive the request + time.Sleep(time.Second) + + // clean up + conn.Close() + }) + } +} + +func TestProxyUpgradeErrorResponse(t *testing.T) { + for _, code := range []int{200, 300, 302, 307} { + t.Run(fmt.Sprintf("code=%v", code), func(t *testing.T) { + // Set up a backend server + backend := http.NewServeMux() + backend.Handle("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "https://example.com/there", code) + })) + backendServer := httptest.NewServer(backend) + defer backendServer.Close() + backendServerURL, _ := url.Parse(backendServer.URL) + backendServerURL.Path = "/hello" + + // Set up a proxy pointing to a specific path on the backend + proxyHandler := NewUpgradeAwareHandler(backendServerURL, nil, false, false, &fakeResponder{t: t}) + proxy := httptest.NewServer(proxyHandler) + defer proxy.Close() + proxyURL, _ := url.Parse(proxy.URL) + + conn, err := net.Dial("tcp", proxyURL.Host) + require.NoError(t, err) + bufferedReader := bufio.NewReader(conn) + + // Send upgrade request resulting in a non-101 response from the backend + req, _ := http.NewRequest(http.MethodGet, "/", nil) + req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade) + require.NoError(t, req.Write(conn)) + // Verify we get the correct response and full message body content + resp, err := http.ReadResponse(bufferedReader, nil) + require.NoError(t, err) + assert.Equal(t, fakeStatusCode, resp.StatusCode) + resp.Body.Close() + + // clean up + conn.Close() + }) + } +} + +func TestRejectForwardingRedirectsOption(t *testing.T) { + originalBody := []byte(`some data`) + testCases := []struct { + name string + rejectForwardingRedirects bool + serverStatusCode int + redirect string + expectStatusCode int + expectBody []byte + }{ + { + name: "reject redirection enabled in proxy, backend server sending 200 response", + rejectForwardingRedirects: true, + serverStatusCode: 200, + expectStatusCode: 200, + expectBody: originalBody, + }, + { + name: "reject redirection enabled in proxy, backend server sending 301 response", + rejectForwardingRedirects: true, + serverStatusCode: 301, + redirect: "/", + expectStatusCode: 502, + expectBody: []byte(`the backend attempted to redirect this request, which is not permitted`), + }, + { + name: "reject redirection enabled in proxy, backend server sending 304 response with a location header", + rejectForwardingRedirects: true, + serverStatusCode: 304, + redirect: "/", + expectStatusCode: 502, + expectBody: []byte(`the backend attempted to redirect this request, which is not permitted`), + }, + { + name: "reject redirection enabled in proxy, backend server sending 304 response with no location header", + rejectForwardingRedirects: true, + serverStatusCode: 304, + expectStatusCode: 304, + expectBody: []byte{}, // client doesn't read the body for 304 responses + }, + { + name: "reject redirection disabled in proxy, backend server sending 200 response", + rejectForwardingRedirects: false, + serverStatusCode: 200, + expectStatusCode: 200, + expectBody: originalBody, + }, + { + name: "reject redirection disabled in proxy, backend server sending 301 response", + rejectForwardingRedirects: false, + serverStatusCode: 301, + redirect: "/", + expectStatusCode: 301, + expectBody: originalBody, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Set up a backend server + backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if tc.redirect != "" { + w.Header().Set("Location", tc.redirect) + } + w.WriteHeader(tc.serverStatusCode) + w.Write(originalBody) + })) + defer backendServer.Close() + backendServerURL, _ := url.Parse(backendServer.URL) + + // Set up a proxy pointing to the backend + proxyHandler := NewUpgradeAwareHandler(backendServerURL, nil, false, false, &fakeResponder{t: t}) + proxyHandler.RejectForwardingRedirects = tc.rejectForwardingRedirects + proxy := httptest.NewServer(proxyHandler) + defer proxy.Close() + proxyURL, _ := url.Parse(proxy.URL) + + conn, err := net.Dial("tcp", proxyURL.Host) + require.NoError(t, err) + bufferedReader := bufio.NewReader(conn) + + req, _ := http.NewRequest(http.MethodGet, proxyURL.String(), nil) + require.NoError(t, req.Write(conn)) + // Verify we get the correct response and message body content + resp, err := http.ReadResponse(bufferedReader, nil) + require.NoError(t, err) + assert.Equal(t, tc.expectStatusCode, resp.StatusCode) + data, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, tc.expectBody, data) + assert.Equal(t, int64(len(tc.expectBody)), resp.ContentLength) + resp.Body.Close() + + // clean up + conn.Close() + }) + } +} + +func TestDefaultProxyTransport(t *testing.T) { + tests := []struct { + name, + url, + location, + expectedScheme, + expectedHost, + expectedPathPrepend string + }{ + { + name: "simple path", + url: "http://test.server:8080/a/test/location", + location: "http://localhost/location", + expectedScheme: "http", + expectedHost: "test.server:8080", + expectedPathPrepend: "/a/test", + }, + { + name: "empty path", + url: "http://test.server:8080/a/test/", + location: "http://localhost", + expectedScheme: "http", + expectedHost: "test.server:8080", + expectedPathPrepend: "/a/test", + }, + { + name: "location ending in slash", + url: "http://test.server:8080/a/test/", + location: "http://localhost/", + expectedScheme: "http", + expectedHost: "test.server:8080", + expectedPathPrepend: "/a/test", + }, + } + + for _, test := range tests { + locURL, _ := url.Parse(test.location) + URL, _ := url.Parse(test.url) + h := NewUpgradeAwareHandler(locURL, nil, false, false, nil) + result := h.defaultProxyTransport(URL, nil) + transport := result.(*corsRemovingTransport).RoundTripper.(*Transport) + if transport.Scheme != test.expectedScheme { + t.Errorf("%s: unexpected scheme. Actual: %s, Expected: %s", test.name, transport.Scheme, test.expectedScheme) + } + if transport.Host != test.expectedHost { + t.Errorf("%s: unexpected host. Actual: %s, Expected: %s", test.name, transport.Host, test.expectedHost) + } + if transport.PathPrepend != test.expectedPathPrepend { + t.Errorf("%s: unexpected path prepend. Actual: %s, Expected: %s", test.name, transport.PathPrepend, test.expectedPathPrepend) + } + } +} + +func TestProxyRequestContentLengthAndTransferEncoding(t *testing.T) { + chunk := func(data []byte) []byte { + out := &bytes.Buffer{} + chunker := httputil.NewChunkedWriter(out) + for _, b := range data { + if _, err := chunker.Write([]byte{b}); err != nil { + panic(err) + } + } + chunker.Close() + out.Write([]byte("\r\n")) + return out.Bytes() + } + + zip := func(data []byte) []byte { + out := &bytes.Buffer{} + zipper := gzip.NewWriter(out) + if _, err := zipper.Write(data); err != nil { + panic(err) + } + zipper.Close() + return out.Bytes() + } + + sampleData := []byte("abcde") + + table := map[string]struct { + reqHeaders http.Header + reqBody []byte + + expectedHeaders http.Header + expectedBody []byte + }{ + "content-length": { + reqHeaders: http.Header{ + "Content-Length": []string{"5"}, + }, + reqBody: sampleData, + + expectedHeaders: http.Header{ + "Content-Length": []string{"5"}, + "Content-Encoding": nil, // none set + "Transfer-Encoding": nil, // none set + }, + expectedBody: sampleData, + }, + + "content-length + gzip content-encoding": { + reqHeaders: http.Header{ + "Content-Length": []string{strconv.Itoa(len(zip(sampleData)))}, + "Content-Encoding": []string{"gzip"}, + }, + reqBody: zip(sampleData), + + expectedHeaders: http.Header{ + "Content-Length": []string{strconv.Itoa(len(zip(sampleData)))}, + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": nil, // none set + }, + expectedBody: zip(sampleData), + }, + + "chunked transfer-encoding": { + reqHeaders: http.Header{ + "Transfer-Encoding": []string{"chunked"}, + }, + reqBody: chunk(sampleData), + + expectedHeaders: http.Header{ + "Content-Length": nil, // none set + "Content-Encoding": nil, // none set + "Transfer-Encoding": nil, // Transfer-Encoding gets removed + }, + expectedBody: sampleData, // sample data is unchunked + }, + + "chunked transfer-encoding + gzip content-encoding": { + reqHeaders: http.Header{ + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": []string{"chunked"}, + }, + reqBody: chunk(zip(sampleData)), + + expectedHeaders: http.Header{ + "Content-Length": nil, // none set + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": nil, // gets removed + }, + expectedBody: zip(sampleData), // sample data is unchunked, but content-encoding is preserved + }, + + // "Transfer-Encoding: gzip" is not supported by go + // See http/transfer.go#fixTransferEncoding (https://golang.org/src/net/http/transfer.go#L427) + // Once it is supported, this test case should succeed + // + // "gzip+chunked transfer-encoding": { + // reqHeaders: http.Header{ + // "Transfer-Encoding": []string{"chunked,gzip"}, + // }, + // reqBody: chunk(zip(sampleData)), + // + // expectedHeaders: http.Header{ + // "Content-Length": nil, // no content-length headers + // "Transfer-Encoding": nil, // Transfer-Encoding gets removed + // }, + // expectedBody: sampleData, + // }, + } + + successfulResponse := "backend passed tests" + for k, item := range table { + // Start the downstream server + downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Verify headers + for header, v := range item.expectedHeaders { + if !reflect.DeepEqual(v, req.Header[header]) { + t.Errorf("%s: Expected headers for %s to be %v, got %v", k, header, v, req.Header[header]) + } + } + + // Read body + body, err := io.ReadAll(req.Body) + if err != nil { + t.Errorf("%s: unexpected error %v", k, err) + } + req.Body.Close() + + // Verify length + if req.ContentLength > 0 && req.ContentLength != int64(len(body)) { + t.Errorf("%s: ContentLength was %d, len(data) was %d", k, req.ContentLength, len(body)) + } + + // Verify content + if !bytes.Equal(item.expectedBody, body) { + t.Errorf("%s: Expected %q, got %q", k, string(item.expectedBody), string(body)) + } + + // Write successful response + w.Write([]byte(successfulResponse)) + })) + defer downstreamServer.Close() + + responder := &fakeResponder{t: t} + backendURL, _ := url.Parse(downstreamServer.URL) + proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, false, responder) + proxyServer := httptest.NewServer(proxyHandler) + defer proxyServer.Close() + + // Dial the proxy server + conn, err := net.Dial(proxyServer.Listener.Addr().Network(), proxyServer.Listener.Addr().String()) + if err != nil { + t.Errorf("unexpected error %v", err) + continue + } + defer conn.Close() + + // Add standard http 1.1 headers + if item.reqHeaders == nil { + item.reqHeaders = http.Header{} + } + item.reqHeaders.Add("Connection", "close") + item.reqHeaders.Add("Host", proxyServer.Listener.Addr().String()) + + // Write the request headers + if _, err := fmt.Fprint(conn, "POST / HTTP/1.1\r\n"); err != nil { + t.Fatalf("%s unexpected error %v", k, err) + } + for header, values := range item.reqHeaders { + for _, value := range values { + if _, err := fmt.Fprintf(conn, "%s: %s\r\n", header, value); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + } + } + // Header separator + if _, err := fmt.Fprint(conn, "\r\n"); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + // Body + if _, err := conn.Write(item.reqBody); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + + // Read response + response, err := io.ReadAll(conn) + if err != nil { + t.Errorf("%s: unexpected error %v", k, err) + continue + } + if !strings.HasSuffix(string(response), successfulResponse) { + t.Errorf("%s: Did not get successful response: %s", k, string(response)) + continue + } + } +} + +func TestFlushIntervalHeaders(t *testing.T) { + const expected = "hi" + stopCh := make(chan struct{}) + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("MyHeader", expected) + w.WriteHeader(200) + w.(http.Flusher).Flush() + <-stopCh + })) + defer backend.Close() + defer close(stopCh) + + backendURL, err := url.Parse(backend.URL) + if err != nil { + t.Fatal(err) + } + + responder := &fakeResponder{t: t} + proxyHandler := NewUpgradeAwareHandler(backendURL, nil, false, false, responder) + + frontend := httptest.NewServer(proxyHandler) + defer frontend.Close() + + req, _ := http.NewRequest(http.MethodGet, frontend.URL, nil) + req.Close = true + + ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second) + defer cancel() + req = req.WithContext(ctx) + + res, err := frontend.Client().Do(req) + if err != nil { + t.Fatalf("Get: %v", err) + } + defer res.Body.Close() + + if res.Header.Get("MyHeader") != expected { + t.Errorf("got header %q; expected %q", res.Header.Get("MyHeader"), expected) + } +} + +type fakeRT struct { + err error +} + +func (frt *fakeRT) RoundTrip(*http.Request) (*http.Response, error) { + return nil, frt.err +} + +// TestErrorPropagation checks if the default transport doesn't swallow the errors by providing a fakeResponder that intercepts and stores the error. +func TestErrorPropagation(t *testing.T) { + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + panic("unreachable") + })) + defer backend.Close() + + backendURL, err := url.Parse(backend.URL) + if err != nil { + t.Fatal(err) + } + + responder := &fakeResponder{t: t} + expectedErr := errors.New("nasty error") + proxyHandler := NewUpgradeAwareHandler(backendURL, &fakeRT{err: expectedErr}, true, false, responder) + + frontend := httptest.NewServer(proxyHandler) + defer frontend.Close() + + req, _ := http.NewRequest(http.MethodGet, frontend.URL, nil) + req.Close = true + + ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second) + defer cancel() + req = req.WithContext(ctx) + + res, err := frontend.Client().Do(req) + if err != nil { + t.Fatalf("Get: %v", err) + } + defer res.Body.Close() + if res.StatusCode != fakeStatusCode { + t.Fatalf("unexpected HTTP status code returned: %v, expected: %v", res.StatusCode, fakeStatusCode) + } + if !strings.Contains(responder.err.Error(), expectedErr.Error()) { + t.Fatalf("responder got unexpected error: %v, expected the error to contain %q", responder.err.Error(), expectedErr.Error()) + } +} + +func TestProxyRedirectsforRootPath(t *testing.T) { + + tests := []struct { + name string + method string + requestPath string + expectedHeader http.Header + expectedStatusCode int + redirect bool + }{ + { + name: "root path, simple get", + method: http.MethodGet, + requestPath: "", + redirect: true, + expectedStatusCode: 301, + expectedHeader: http.Header{ + "Location": []string{"/"}, + }, + }, + { + name: "root path, simple put", + method: http.MethodPut, + requestPath: "", + redirect: false, + expectedStatusCode: 200, + }, + { + name: "root path, simple head", + method: http.MethodHead, + requestPath: "", + redirect: true, + expectedStatusCode: 301, + expectedHeader: http.Header{ + "Location": []string{"/"}, + }, + }, + { + name: "root path, simple delete with params", + method: http.MethodDelete, + requestPath: "", + redirect: false, + expectedStatusCode: 200, + }, + } + + for _, test := range tests { + func() { + w := httptest.NewRecorder() + req, err := http.NewRequest(test.method, test.requestPath, nil) + if err != nil { + t.Fatal(err) + } + + redirect := proxyRedirectsforRootPath(test.requestPath, w, req) + if got, want := redirect, test.redirect; got != want { + t.Errorf("Expected redirect state %v; got %v", want, got) + } + + res := w.Result() + if got, want := res.StatusCode, test.expectedStatusCode; got != want { + t.Errorf("Expected status code %d; got %d", want, got) + } + + if res.StatusCode == 301 && !reflect.DeepEqual(res.Header, test.expectedHeader) { + t.Errorf("Expected location header to be %v, got %v", test.expectedHeader, res.Header) + } + }() + } +} + +// exampleCert was generated from crypto/tls/generate_cert.go with the following command: +// +// go run generate_cert.go --rsa-bits 1024 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var exampleCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDADCCAeigAwIBAgIQVHG3Fn9SdWayyLOZKCW1vzANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9p+tn +zm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/JlNy ++i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj7aMi +vXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJOoUG +f8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb+9he +7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABo1AwTjAOBgNVHQ8BAf8EBAMC +AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREE +DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAig4AIi9xWs1+pLES +eeGGdSDoclplFpcbXANnsYYFyLf+8pcWgVi2bOmb2gXMbHFkB07MA82wRJAUTaA+ +2iNXVQMhPCoA7J6ADUbww9doJX2S9HGyArhiV/MhHtE8txzMn2EKNLdhhk3N9rmV +x/qRbWAY1U2z4BpdrAR87Fe81Nlj7h45csW9K+eS+NgXipiNTIfEShKgCFM8EdxL +1WXg7r9AvYV3TNDPWTjLsm1rQzzZQ7Uvcf6deWiNodZd8MOT/BFLclDPTK6cF2Hr +UU4dq6G4kCwMSxWE4cM3HlZ4u1dyIt47VbkP0rtvkBCXx36y+NXYA5lzntchNFZP +uvEQdw== +-----END CERTIFICATE-----`) + +var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9 +p+tnzm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/ +JlNy+i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj +7aMivXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJ +OoUGf8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb ++9he7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABAoIBAQClt4CiYaaF5ltx +wVDjz6TNcJUBUs3CKE+uWAYFnF5Ii1nyU876Pxj8Aaz9fHZ6Kde0GkwiXY7gFOj1 +YHo2tzcELSKS/SEDZcYbYFTGCjq13g1AH74R+SV6WZLn+5m8kPvVrM1ZWap188H5 +bmuCkRDqVmIvShkbRW7EwhC35J9fiuW3majC/sjmsxtxyP6geWmu4f5/Ttqahcdb +osPZIgIIPzqAkNtkLTi7+meHYI9wlrGhL7XZTwnJ1Oc/Y67zzmbthLYB5YFSLUew +rXT58jtSjX4gbiQyheBSrWxW08QE4qYg6jJlAdffHhWv72hJW2MCXhuXp8gJs/Do +XLRHGwSBAoGBAMdNtsbe4yae/QeHUPGxNW0ipa0yoTF6i+VYoxvqiRMzDM3+3L8k +dgI1rr4330SivqDahMA/odWtM/9rVwJI2B2QhZLMHA0n9ytH007OO9TghgVB12nN +xosRYBpKdHXyyvV/MUZl7Jux6zKIzRDWOkF95VVYPcAaxJqd1E5/jJ6JAoGBAN51 +QrebA1w/jfydeqQTz1sK01sbO4HYj4qGfo/JarVqGEkm1azeBBPPRnHz3jNKnCkM +S4PpqRDased3NIcViXlAgoqPqivZ8mQa/Rb146l7WaTErASHsZ023OGrxsr/Ed6N +P3GrmvxVJjebaFNaQ9sP80dLkpgeas0t2TY8iQNRAoGATOcnx8TpUVW3vNfx29DN +FLdxxkrq9/SZVn3FMlhlXAsuva3B799ZybB9JNjaRdmmRNsMrkHfaFvU3JHGmRMS +kRXa9LHdgRYSwZiNaLMbUyDvlce6HxFPswmZU4u3NGvi9KeHk+pwSgN1BaLTvdNr +1ymE/FF4QlAR3LdZ3JBK6kECgYEA0wW4/CJ31ZIURoW8SNjh4iMqy0nR8SJVR7q9 +Y/hU2TKDRyEnoIwaohAFayNCrLUh3W5kVAXa8roB+OgDVAECH5sqOfZ+HorofD19 +x8II7ESujLZj1whBXDkm3ovsT7QWZ17lyBZZNvQvBKDPHgKKS8udowv1S4fPGENd +wS07a4ECgYEAwLSbmMIVJme0jFjsp5d1wOGA2Qi2ZwGIAVlsbnJtygrU/hSBfnu8 +VfyJSCgg3fPe7kChWKlfcOebVKSb68LKRsz1Lz1KdbY0HOJFp/cT4lKmDAlRY9gq +LB4rdf46lV0mUkvd2/oofIbTrzukjQSnyfLawb/2uJGV1IkTcZcn9CI= +-----END RSA PRIVATE KEY-----`) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/rand/rand.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/rand/rand.go new file mode 100644 index 0000000000..82a473bb14 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/rand/rand.go @@ -0,0 +1,127 @@ +/* +Copyright 2015 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 rand provides utilities related to randomization. +package rand + +import ( + "math/rand" + "sync" + "time" +) + +var rng = struct { + sync.Mutex + rand *rand.Rand +}{ + rand: rand.New(rand.NewSource(time.Now().UnixNano())), +} + +// Int returns a non-negative pseudo-random int. +func Int() int { + rng.Lock() + defer rng.Unlock() + return rng.rand.Int() +} + +// Intn generates an integer in range [0,max). +// By design this should panic if input is invalid, <= 0. +func Intn(max int) int { + rng.Lock() + defer rng.Unlock() + return rng.rand.Intn(max) +} + +// IntnRange generates an integer in range [min,max). +// By design this should panic if input is invalid, <= 0. +func IntnRange(min, max int) int { + rng.Lock() + defer rng.Unlock() + return rng.rand.Intn(max-min) + min +} + +// IntnRange generates an int64 integer in range [min,max). +// By design this should panic if input is invalid, <= 0. +func Int63nRange(min, max int64) int64 { + rng.Lock() + defer rng.Unlock() + return rng.rand.Int63n(max-min) + min +} + +// Seed seeds the rng with the provided seed. +func Seed(seed int64) { + rng.Lock() + defer rng.Unlock() + + rng.rand = rand.New(rand.NewSource(seed)) +} + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n) +// from the default Source. +func Perm(n int) []int { + rng.Lock() + defer rng.Unlock() + return rng.rand.Perm(n) +} + +const ( + // We omit vowels from the set of available characters to reduce the chances + // of "bad words" being formed. + alphanums = "bcdfghjklmnpqrstvwxz2456789" + // No. of bits required to index into alphanums string. + alphanumsIdxBits = 5 + // Mask used to extract last alphanumsIdxBits of an int. + alphanumsIdxMask = 1<>= alphanumsIdxBits + remaining-- + } + return string(b) +} + +// SafeEncodeString encodes s using the same characters as rand.String. This reduces the chances of bad words and +// ensures that strings generated from hash functions appear consistent throughout the API. +func SafeEncodeString(s string) string { + r := make([]byte, len(s)) + for i, b := range []rune(s) { + r[i] = alphanums[(int(b) % len(alphanums))] + } + return string(r) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/rand/rand_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/rand/rand_test.go new file mode 100644 index 0000000000..e677aa9750 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/rand/rand_test.go @@ -0,0 +1,114 @@ +/* +Copyright 2015 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 rand + +import ( + "math/rand" + "strings" + "testing" +) + +const ( + maxRangeTestCount = 500 + testStringLength = 32 +) + +func TestString(t *testing.T) { + valid := "bcdfghjklmnpqrstvwxz2456789" + for _, l := range []int{0, 1, 2, 10, 123} { + s := String(l) + if len(s) != l { + t.Errorf("expected string of size %d, got %q", l, s) + } + for _, c := range s { + if !strings.ContainsRune(valid, c) { + t.Errorf("expected valid characters, got %v", c) + } + } + } +} + +// Confirm that panic occurs on invalid input. +func TestRangePanic(t *testing.T) { + defer func() { + if err := recover(); err == nil { + t.Errorf("Panic didn't occur!") + } + }() + // Should result in an error... + Intn(0) +} + +func TestIntn(t *testing.T) { + // 0 is invalid. + for _, max := range []int{1, 2, 10, 123} { + inrange := Intn(max) + if inrange < 0 || inrange > max { + t.Errorf("%v out of range (0,%v)", inrange, max) + } + } +} + +func TestPerm(t *testing.T) { + Seed(5) + r := rand.New(rand.NewSource(5)) + for i := 1; i < 20; i++ { + actual := Perm(i) + expected := r.Perm(i) + for j := 0; j < i; j++ { + if actual[j] != expected[j] { + t.Errorf("Perm call result is unexpected") + } + } + } +} + +func TestIntnRange(t *testing.T) { + // 0 is invalid. + for min, max := range map[int]int{1: 2, 10: 123, 100: 500} { + for i := 0; i < maxRangeTestCount; i++ { + inrange := IntnRange(min, max) + if inrange < min || inrange >= max { + t.Errorf("%v out of range (%v,%v)", inrange, min, max) + } + } + } +} + +func TestInt63nRange(t *testing.T) { + // 0 is invalid. + for min, max := range map[int64]int64{1: 2, 10: 123, 100: 500} { + for i := 0; i < maxRangeTestCount; i++ { + inrange := Int63nRange(min, max) + if inrange < min || inrange >= max { + t.Errorf("%v out of range (%v,%v)", inrange, min, max) + } + } + } +} + +func BenchmarkRandomStringGeneration(b *testing.B) { + b.ResetTimer() + var s string + for i := 0; i < b.N; i++ { + s = String(testStringLength) + } + b.StopTimer() + if len(s) == 0 { + b.Fatal(s) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/remotecommand/constants.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/remotecommand/constants.go new file mode 100644 index 0000000000..ba153ee24f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/remotecommand/constants.go @@ -0,0 +1,67 @@ +/* +Copyright 2016 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 remotecommand + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + DefaultStreamCreationTimeout = 30 * time.Second + + // The SPDY subprotocol "channel.k8s.io" is used for remote command + // attachment/execution. This represents the initial unversioned subprotocol, + // which has the known bugs https://issues.k8s.io/13394 and + // https://issues.k8s.io/13395. + StreamProtocolV1Name = "channel.k8s.io" + + // The SPDY subprotocol "v2.channel.k8s.io" is used for remote command + // attachment/execution. It is the second version of the subprotocol and + // resolves the issues present in the first version. + StreamProtocolV2Name = "v2.channel.k8s.io" + + // The SPDY subprotocol "v3.channel.k8s.io" is used for remote command + // attachment/execution. It is the third version of the subprotocol and + // adds support for resizing container terminals. + StreamProtocolV3Name = "v3.channel.k8s.io" + + // The SPDY subprotocol "v4.channel.k8s.io" is used for remote command + // attachment/execution. It is the 4th version of the subprotocol and + // adds support for exit codes. + StreamProtocolV4Name = "v4.channel.k8s.io" + + // The subprotocol "v5.channel.k8s.io" is used for remote command + // attachment/execution. It is the 5th version of the subprotocol and + // adds support for a CLOSE signal. + StreamProtocolV5Name = "v5.channel.k8s.io" + + NonZeroExitCodeReason = metav1.StatusReason("NonZeroExitCode") + ExitCodeCauseType = metav1.CauseType("ExitCode") + + // RemoteCommand stream identifiers. The first three identifiers (for STDIN, + // STDOUT, STDERR) are the same as their file descriptors. + StreamStdIn = 0 + StreamStdOut = 1 + StreamStdErr = 2 + StreamErr = 3 + StreamResize = 4 + StreamClose = 255 +) + +var SupportedStreamingProtocols = []string{StreamProtocolV4Name, StreamProtocolV3Name, StreamProtocolV2Name, StreamProtocolV1Name} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/resourceversion/resourceversion.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/resourceversion/resourceversion.go new file mode 100644 index 0000000000..6f672c4b46 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/resourceversion/resourceversion.go @@ -0,0 +1,85 @@ +/* +Copyright 2025 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 resourceversion + +import ( + "fmt" + "strings" +) + +type InvalidResourceVersion struct { + rv string +} + +func (i InvalidResourceVersion) Error() string { + return fmt.Sprintf("resource version is not well formed: %s", i.rv) +} + +// CompareResourceVersion runs a comparison between two ResourceVersions. This +// only has semantic meaning when the comparison is done on two objects of the +// same resource. The return values are: +// +// -1: If RV a < RV b +// 0: If RV a == RV b +// +1: If RV a > RV b +// +// The function will return an error if the resource version is not a properly +// formatted positive integer, but has no restriction on length. A properly +// formatted integer will not contain leading zeros or non integer characters. +// Zero is also considered an invalid value as it is used as a special value in +// list/watch events and will never be a live resource version. +func CompareResourceVersion(a, b string) (int, error) { + if !isWellFormed(a) { + return 0, InvalidResourceVersion{rv: a} + } + if !isWellFormed(b) { + return 0, InvalidResourceVersion{rv: b} + } + // both are well-formed integer strings with no leading zeros + aLen := len(a) + bLen := len(b) + switch { + case aLen < bLen: + // shorter is less + return -1, nil + case aLen > bLen: + // longer is greater + return 1, nil + default: + // equal-length compares lexically + return strings.Compare(a, b), nil + } +} + +func isWellFormed(s string) bool { + if len(s) == 0 { + return false + } + if s[0] == '0' { + return false + } + for i := range s { + if !isDigit(s[i]) { + return false + } + } + return true +} + +func isDigit(b byte) bool { + return b >= '0' && b <= '9' +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/resourceversion/resourceversion_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/resourceversion/resourceversion_test.go new file mode 100644 index 0000000000..dd3785e9ac --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/resourceversion/resourceversion_test.go @@ -0,0 +1,162 @@ +/* +Copyright 2025 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 resourceversion + +import ( + "testing" +) + +func TestCompareResourceVersion(t *testing.T) { + testCases := []struct { + name string + a string + b string + expected int + err bool + }{ + { + name: "a less than b", + a: "100", + b: "200", + expected: -1, + }, + { + name: "a is zero, invalid", + a: "0", + b: "1", + err: true, + }, + { + name: "both zero", + a: "0", + b: "0", + err: true, + }, + { + name: "a greater than b", + a: "200", + b: "100", + expected: 1, + }, + { + name: "b is 0, invalid", + a: "1", + b: "0", + err: true, + }, + { + name: "a equal to b small", + a: "1", + b: "1", + expected: 0, + }, + { + name: "a equal to b", + a: "100", + b: "100", + expected: 0, + }, + { + name: "a shorter than b", + a: "99", + b: "100", + expected: -1, + }, + { + name: "a longer than b", + a: "100", + b: "99", + expected: 1, + }, + { + name: "a with leading zero", + a: "0100", + b: "100", + expected: 0, + err: true, + }, + { + name: "b with leading zero", + a: "100", + b: "0100", + expected: 0, + err: true, + }, + { + name: "a empty", + a: "", + b: "100", + expected: 0, + err: true, + }, + { + name: "b empty", + a: "100", + b: "", + expected: 0, + err: true, + }, + { + name: "a non-digit", + a: "100a", + b: "100", + err: true, + }, + { + name: "b non-digit", + a: "100", + b: "100a", + err: true, + }, + { + name: "large int a less than b", + a: "99999999999999999999999999999999999999999999999999", + b: "100000000000000000000000000000000000000000000000000", + expected: -1, + }, + { + name: "large int a greater than b", + a: "100000000000000000000000000000000000000000000000000", + b: "99999999999999999999999999999999999999999999999999", + expected: 1, + }, + { + name: "large int a equal to b", + a: "12345678901234567890123456789012345678901234567890", + b: "12345678901234567890123456789012345678901234567890", + expected: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual, err := CompareResourceVersion(tc.a, tc.b) + if tc.err { + if err == nil { + t.Fatalf("expected error, but got none") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if actual != tc.expected { + t.Errorf("expected %d, got %d", tc.expected, actual) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime.go new file mode 100644 index 0000000000..38c1f7b03c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime.go @@ -0,0 +1,326 @@ +/* +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 runtime + +import ( + "bytes" + "context" + "fmt" + "net/http" + "runtime" + "strings" + "sync" + "time" + + "k8s.io/klog/v2" + "k8s.io/klog/v2/textlogger" +) + +var ( + // ReallyCrash controls the behavior of HandleCrash and defaults to + // true. It's exposed so components can optionally set to false + // to restore prior behavior. This flag is mostly used for tests to validate + // crash conditions. + ReallyCrash = true +) + +// PanicHandlers is a list of functions which will be invoked when a panic happens. +// +// The code invoking these handlers prepares a contextual logger so that +// klog.FromContext(ctx) already skips over the panic handler itself and +// several other intermediate functions, ideally such that the log output +// is attributed to the code which triggered the panic. +var PanicHandlers = []func(context.Context, interface{}){logPanic} + +// HandleCrash simply catches a crash and logs an error. Meant to be called via +// defer. Additional context-specific handlers can be provided, and will be +// called in case of panic. HandleCrash actually crashes, after calling the +// handlers and logging the panic message. +// +// E.g., you can provide one or more additional handlers for something like shutting down go routines gracefully. +// +// Contextual logging: HandleCrashWithContext or HandleCrashWithLogger should be used instead of HandleCrash in code which supports contextual logging. +func HandleCrash(additionalHandlers ...func(interface{})) { + if r := recover(); r != nil { + additionalHandlersWithContext := make([]func(context.Context, interface{}), len(additionalHandlers)) + for i, handler := range additionalHandlers { + additionalHandlersWithContext[i] = func(_ context.Context, r interface{}) { + handler(r) + } + } + + handleCrash(context.Background(), r, additionalHandlersWithContext...) + } +} + +// HandleCrashWithContext simply catches a crash and logs an error. Meant to be called via +// defer. Additional context-specific handlers can be provided, and will be +// called in case of panic. HandleCrash actually crashes, after calling the +// handlers and logging the panic message. +// +// E.g., you can provide one or more additional handlers for something like shutting down go routines gracefully. +// +// The context is used to determine how to log. +func HandleCrashWithContext(ctx context.Context, additionalHandlers ...func(context.Context, interface{})) { + if r := recover(); r != nil { + handleCrash(ctx, r, additionalHandlers...) + } +} + +// HandleCrashWithLogger simply catches a crash and logs an error. Meant to be called via +// defer. Additional context-specific handlers can be provided, and will be +// called in case of panic. HandleCrash actually crashes, after calling the +// handlers and logging the panic message. +// +// E.g., you can provide one or more additional handlers for something like shutting down go routines gracefully. +func HandleCrashWithLogger(logger klog.Logger, additionalHandlers ...func(context.Context, interface{})) { + if r := recover(); r != nil { + ctx := klog.NewContext(context.Background(), logger) + handleCrash(ctx, r, additionalHandlers...) + } +} + +// handleCrash is the common implementation of the HandleCrash* variants. +// Having those call a common implementation ensures that the stack depth +// is the same regardless through which path the handlers get invoked. +func handleCrash(ctx context.Context, r any, additionalHandlers ...func(context.Context, interface{})) { + // We don't really know how many call frames to skip because the Go + // panic handler is between us and the code where the panic occurred. + // If it's one function (as in Go 1.21), then skipping four levels + // gets us to the function which called the `defer HandleCrashWithontext(...)`. + logger := klog.FromContext(ctx).WithCallDepth(4) + ctx = klog.NewContext(ctx, logger) + + for _, fn := range PanicHandlers { + fn(ctx, r) + } + for _, fn := range additionalHandlers { + fn(ctx, r) + } + if ReallyCrash { + // Actually proceed to panic. + panic(r) + } +} + +// logPanic logs the caller tree when a panic occurs (except in the special case of http.ErrAbortHandler). +func logPanic(ctx context.Context, r interface{}) { + if r == http.ErrAbortHandler { + // honor the http.ErrAbortHandler sentinel panic value: + // ErrAbortHandler is a sentinel panic value to abort a handler. + // While any panic from ServeHTTP aborts the response to the client, + // panicking with ErrAbortHandler also suppresses logging of a stack trace to the server's error log. + return + } + + // Same as stdlib http server code. Manually allocate stack trace buffer size + // to prevent excessively large logs + const size = 64 << 10 + stacktrace := make([]byte, size) + stacktrace = stacktrace[:runtime.Stack(stacktrace, false)] + + logger := klog.FromContext(ctx) + + // For backwards compatibility, conversion to string + // is handled here instead of defering to the logging + // backend. + if _, ok := r.(string); ok { + logger.Error(nil, "Observed a panic", "panic", r, "stacktrace", string(stacktrace)) + } else { + logger.Error(nil, "Observed a panic", "panic", fmt.Sprintf("%v", r), "panicGoValue", fmt.Sprintf("%#v", r), "stacktrace", string(stacktrace)) + } +} + +// ErrorHandlers is a list of functions which will be invoked when a nonreturnable +// error occurs. +// TODO(lavalamp): for testability, this and the below HandleError function +// should be packaged up into a testable and reusable object. +var ErrorHandlers = []ErrorHandler{ + logError, + // 1ms was the number folks were able to stomach as a global rate limit. + // If you need to log errors more than 1000 times a second, you + // should probably consider fixing your code instead. :) + backoffError(1 * time.Millisecond), +} + +// ErrorHandler is called indirectly through [HandleError], [HandleErrorWithContext] or [HandleErrorWithLogger]. +// It is passed the same parameters that a structured logging backend needs to log a problem. +// It follows the semantic described for [HandleErrorWithContext] and [logr.Logger.Error]: +// - err is optional and may be nil +// - msg is string that describes the problem +// - keysAndValues contains additional information that varies between different occurrences of the problem +// +// [ErrorToString] can be used to convert these parameters into a single string, using the klog text output. +type ErrorHandler func(ctx context.Context, err error, msg string, keysAndValues ...interface{}) + +// ErrorToString takes the parameters passed to [ErrorHandler] and +// formats them as a string using the klog text output. +// +// If any of the values is a multi-line string, then the resulting +// string also uses line breaks and indention for the sake of readability. +// Does not include a trailing newline. +// +// Use errors.New if an error instead of a string is needed. +func ErrorToString(err error, msg string, keysAndValues ...interface{}) string { + var buffer bytes.Buffer + config := textlogger.NewConfig( + textlogger.Output(&buffer), + textlogger.WithHeader(false), + ) + logger := textlogger.NewLogger(config) + logger.Error(err, msg, keysAndValues...) + result := buffer.String() + result = strings.TrimSpace(result) + return result +} + +// HandlerError is a method to invoke when a non-user facing piece of code cannot +// return an error and needs to indicate it has been ignored. Invoking this method +// is preferable to logging the error - the default behavior is to log but the +// errors may be sent to a remote server for analysis. +// +// Contextual logging: HandleErrorWithContext should be used instead of HandleError in code which supports contextual logging. +func HandleError(err error) { + // this is sometimes called with a nil error. We probably shouldn't fail and should do nothing instead + if err == nil { + return + } + + handleError(context.Background(), err, "Unhandled Error") +} + +// HandlerErrorWithContext is a method to invoke when a non-user facing piece of code cannot +// return an error and needs to indicate it has been ignored. Invoking this method +// is preferable to logging the error - the default behavior is to log but the +// errors may be sent to a remote server for analysis. The context is used to +// determine how to log the error. +// +// If contextual logging is enabled, the default log output is equivalent to +// +// logr.FromContext(ctx).WithName("UnhandledError").Error(err, msg, keysAndValues...) +// +// Without contextual logging, it is equivalent to: +// +// klog.ErrorS(err, msg, keysAndValues...) +// +// In contrast to HandleError, passing nil for the error is still going to +// trigger a log entry. Don't construct a new error or wrap an error +// with fmt.Errorf. Instead, add additional information via the mssage +// and key/value pairs. +// +// This variant should be used instead of HandleError because it supports +// structured, contextual logging. Alternatively, [HandleErrorWithLogger] can +// be used if a logger is available instead of a context. +func HandleErrorWithContext(ctx context.Context, err error, msg string, keysAndValues ...interface{}) { + handleError(ctx, err, msg, keysAndValues...) +} + +// HandleErrorWithLogger is an alternative to [HandlerErrorWithContext] which accepts +// a logger for contextual logging. +func HandleErrorWithLogger(logger klog.Logger, err error, msg string, keysAndValues ...interface{}) { + handleError(klog.NewContext(context.Background(), logger), err, msg, keysAndValues...) +} + +// handleError is the common implementation of the HandleError* variants. +// Using this common implementation ensures that the stack depth +// is the same regardless through which path the handlers get invoked. +func handleError(ctx context.Context, err error, msg string, keysAndValues ...interface{}) { + for _, fn := range ErrorHandlers { + fn(ctx, err, msg, keysAndValues...) + } +} + +// logError prints an error with the call stack of the location it was reported. +// It expects to be called as -> HandleError[WithContext] -> handleError -> logError. +func logError(ctx context.Context, err error, msg string, keysAndValues ...interface{}) { + logger := klog.FromContext(ctx).WithCallDepth(3) + logger = klog.LoggerWithName(logger, "UnhandledError") + logger.Error(err, msg, keysAndValues...) //nolint:logcheck // logcheck complains about unknown key/value pairs. +} + +// backoffError blocks if it is called more often than the minPeriod. +func backoffError(minPeriod time.Duration) ErrorHandler { + r := &rudimentaryErrorBackoff{ + lastErrorTime: time.Now(), + minPeriod: minPeriod, + } + + return func(ctx context.Context, err error, msg string, keysAndValues ...interface{}) { + r.OnError() + } +} + +type rudimentaryErrorBackoff struct { + minPeriod time.Duration // immutable + // TODO(lavalamp): use the clock for testability. Need to move that + // package for that to be accessible here. + lastErrorTimeLock sync.Mutex + lastErrorTime time.Time +} + +// OnError will block if it is called more often than the embedded period time. +// This will prevent overly tight hot error loops. +func (r *rudimentaryErrorBackoff) OnError() { + now := time.Now() // start the timer before acquiring the lock + r.lastErrorTimeLock.Lock() + d := now.Sub(r.lastErrorTime) + r.lastErrorTime = time.Now() + r.lastErrorTimeLock.Unlock() + + // Do not sleep with the lock held because that causes all callers of HandleError to block. + // We only want the current goroutine to block. + // A negative or zero duration causes time.Sleep to return immediately. + // If the time moves backwards for any reason, do nothing. + time.Sleep(r.minPeriod - d) +} + +// GetCaller returns the caller of the function that calls it. +func GetCaller() string { + var pc [1]uintptr + runtime.Callers(3, pc[:]) + f := runtime.FuncForPC(pc[0]) + if f == nil { + return "Unable to find caller" + } + return f.Name() +} + +// RecoverFromPanic replaces the specified error with an error containing the +// original error, and the call tree when a panic occurs. This enables error +// handlers to handle errors and panics the same way. +func RecoverFromPanic(err *error) { + if r := recover(); r != nil { + // Same as stdlib http server code. Manually allocate stack trace buffer size + // to prevent excessively large logs + const size = 64 << 10 + stacktrace := make([]byte, size) + stacktrace = stacktrace[:runtime.Stack(stacktrace, false)] + + *err = fmt.Errorf( + "recovered from panic %q. (err=%v) Call stack:\n%s", + r, + *err, + stacktrace) + } +} + +// Must panics on non-nil errors. Useful to handling programmer level errors. +func Must(err error) { + if err != nil { + panic(err) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime_stack_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime_stack_test.go new file mode 100644 index 0000000000..380ad5b1a5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime_stack_test.go @@ -0,0 +1,71 @@ +/* +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 runtime + +import ( + "bytes" + "context" + "errors" + "flag" + "fmt" + "regexp" + + "k8s.io/klog/v2" +) + +//nolint:logcheck // Several functions are normally not okay in a package. +func ExampleHandleErrorWithContext() { + state := klog.CaptureState() + defer state.Restore() + var fs flag.FlagSet + klog.InitFlags(&fs) + for flag, value := range map[string]string{ + "one_output": "true", + "logtostderr": "false", + } { + if err := fs.Set(flag, value); err != nil { + fmt.Printf("Unexpected error configuring klog: %v", err) + return + } + } + var buffer bytes.Buffer + klog.SetOutput(&buffer) + + logger := klog.Background() + logger = klog.LoggerWithValues(logger, "request", 42) + ctx := klog.NewContext(context.Background(), logger) + + // The line number of the next call must be at line 60. Here are some + // blank lines that can be removed to keep the line unchanged. + // + // + // + // + // + // + HandleErrorWithContext(ctx, errors.New("fake error"), "test") + + klog.Flush() + // Strip varying header. Code location should be constant and something + // that needs to be tested. + output := buffer.String() + output = regexp.MustCompile(`^.* ([^[:space:]]*.go:[[:digit:]]*)\] `).ReplaceAllString(output, `xxx $1] `) + fmt.Print(output) + + // Output: + // xxx runtime_stack_test.go:60] "test" err="fake error" logger="UnhandledError" request=42 +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go new file mode 100644 index 0000000000..5af48a7a80 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/runtime/runtime_test.go @@ -0,0 +1,349 @@ +/* +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 runtime + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + "os" + "regexp" + "runtime" + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "k8s.io/klog/v2" + "k8s.io/klog/v2/textlogger" +) + +func TestHandleCrash(t *testing.T) { + defer func() { + if x := recover(); x == nil { + t.Errorf("Expected a panic to recover from") + } + }() + //nolint:logcheck // Intentionally uses the old API. + defer HandleCrash() + panic("Test Panic") +} + +func TestCustomHandleCrash(t *testing.T) { + old := PanicHandlers + defer func() { PanicHandlers = old }() + var result interface{} + PanicHandlers = []func(context.Context, interface{}){ + func(_ context.Context, r interface{}) { + result = r + }, + } + func() { + defer func() { + if x := recover(); x == nil { + t.Errorf("Expected a panic to recover from") + } + }() + //nolint:logcheck // Intentionally uses the old API. + defer HandleCrash() + panic("test") + }() + if result != "test" { + t.Errorf("did not receive custom handler") + } +} + +func TestCustomHandleError(t *testing.T) { + old := ErrorHandlers + defer func() { ErrorHandlers = old }() + var result error + ErrorHandlers = []ErrorHandler{ + func(_ context.Context, err error, msg string, keysAndValues ...interface{}) { + result = err + }, + } + err := fmt.Errorf("test") + //nolint:logcheck // Intentionally uses the old API. + HandleError(err) + if result != err { + t.Errorf("did not receive custom handler") + } +} + +func TestHandleCrashLog(t *testing.T) { + log, err := captureStderr(func() { + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected a panic to recover from") + } + }() + //nolint:logcheck // Intentionally uses the old API. + defer HandleCrash() + panic("test panic") + }) + if err != nil { + t.Fatalf("%v", err) + } + // Example log: + // + // ...] Observed a panic: test panic + // goroutine 6 [running]: + // command-line-arguments.logPanic(0x..., 0x...) + // .../src/k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/runtime/runtime.go:69 +0x... + lines := strings.Split(log, "\n") + if len(lines) < 4 { + t.Fatalf("panic log should have 1 line of message, 1 line per goroutine and 2 lines per function call") + } + t.Logf("Got log output:\n%s", strings.Join(lines, "\n")) + if match, _ := regexp.MatchString(`"Observed a panic" panic="test panic"`, lines[0]); !match { + t.Errorf("mismatch panic message: %s", lines[0]) + } + // The following regexp's verify that Kubernetes panic log matches Golang stdlib + // stacktrace pattern. We need to update these regexp's if stdlib changes its pattern. + if match, _ := regexp.MatchString(`goroutine [0-9]+ \[.+\]:`, lines[1]); !match { + t.Errorf("mismatch goroutine: %s", lines[1]) + } + if match, _ := regexp.MatchString(`logPanic(.*)`, lines[2]); !match { + t.Errorf("mismatch symbolized function name: %s", lines[2]) + } + if match, _ := regexp.MatchString(`runtime\.go:[0-9]+ \+0x`, lines[3]); !match { + t.Errorf("mismatch file/line/offset information: %s", lines[3]) + } +} + +func TestHandleCrashContextual(t *testing.T) { + for name, handleCrash := range map[string]func(logger klog.Logger, trigger func(), additionalHandlers ...func(context.Context, interface{})){ + "WithLogger": func(logger klog.Logger, trigger func(), additionalHandlers ...func(context.Context, interface{})) { + logger = logger.WithCallDepth(2) // This function *and* the trigger helper. + defer HandleCrashWithLogger(logger, additionalHandlers...) + trigger() + }, + "WithContext": func(logger klog.Logger, trigger func(), additionalHandlers ...func(context.Context, interface{})) { + logger = logger.WithCallDepth(2) + defer HandleCrashWithContext(klog.NewContext(context.Background(), logger), additionalHandlers...) + trigger() + }, + } { + t.Run(name, func(t *testing.T) { + for name, tt := range map[string]struct { + trigger func() + expectPanic string + }{ + "no-panic": { + trigger: func() {}, + expectPanic: "", + }, + "string-panic": { + trigger: func() { panic("fake") }, + expectPanic: "fake", + }, + "int-panic": { + trigger: func() { panic(42) }, + expectPanic: "42", + }, + } { + t.Run(name, func(t *testing.T) { + var buffer bytes.Buffer + timeInUTC := time.Date(2009, 12, 1, 13, 30, 40, 42000, time.UTC) + timeString := "1201 13:30:40.000042" + logger := textlogger.NewLogger(textlogger.NewConfig( + textlogger.FixedTime(timeInUTC), + textlogger.Output(&buffer), + )) + ReallyCrash = false + defer func() { ReallyCrash = true }() + + handler := func(ctx context.Context, r interface{}) { + // Same formatting as in HandleCrash. + str, ok := r.(string) + if !ok { + str = fmt.Sprintf("%v", r) + } + klog.FromContext(ctx).Info("handler called", "panic", str) + } + + _, _, line, _ := runtime.Caller(0) + handleCrash(logger, tt.trigger, handler) + if tt.expectPanic != "" { + assert.Contains(t, buffer.String(), fmt.Sprintf(`E%s %7d runtime_test.go:%d] "Observed a panic" panic=%q`, timeString, os.Getpid(), line+1, tt.expectPanic)) + assert.Contains(t, buffer.String(), fmt.Sprintf(`I%s %7d runtime_test.go:%d] "handler called" panic=%q +`, timeString, os.Getpid(), line+1, tt.expectPanic)) + } else { + assert.Empty(t, buffer.String()) + } + }) + } + }) + } +} + +func TestHandleCrashLogSilenceHTTPErrAbortHandler(t *testing.T) { + log, err := captureStderr(func() { + defer func() { + if r := recover(); r != http.ErrAbortHandler { + t.Fatalf("expected to recover from http.ErrAbortHandler") + } + }() + //nolint:logcheck // Intentionally uses the old API. + defer HandleCrash() + panic(http.ErrAbortHandler) + }) + if err != nil { + t.Fatalf("%v", err) + } + if len(log) > 0 { + t.Fatalf("expected no stderr log, got: %s", log) + } +} + +// captureStderr redirects stderr to result string, and then restore stderr from backup +func captureStderr(f func()) (string, error) { + r, w, err := os.Pipe() + if err != nil { + return "", err + } + bak := os.Stderr + os.Stderr = w + defer func() { os.Stderr = bak }() + + resultCh := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + resultCh <- buf.String() + }() + + f() + w.Close() + + return <-resultCh, nil +} + +func Test_rudimentaryErrorBackoff_OnError_ParallelSleep(t *testing.T) { + r := &rudimentaryErrorBackoff{ + minPeriod: time.Second, + } + + start := make(chan struct{}) + var wg sync.WaitGroup + for i := 0; i < 30; i++ { + wg.Add(1) + go func() { + <-start + r.OnError() + wg.Done() + }() + } + st := time.Now() + close(start) + wg.Wait() + + if since := time.Since(st); since > 5*time.Second { + t.Errorf("OnError slept for too long: %s", since) + } +} + +func TestHandleError(t *testing.T) { + for name, handleError := range map[string]func(logger klog.Logger, err error, msg string, keysAndValues ...interface{}){ + "WithLogger": func(logger klog.Logger, err error, msg string, keysAndValues ...interface{}) { + helper, logger := logger.WithCallStackHelper() + helper() + HandleErrorWithLogger(logger, err, msg, keysAndValues...) + }, + "WithContext": func(logger klog.Logger, err error, msg string, keysAndValues ...interface{}) { + helper, logger := logger.WithCallStackHelper() + helper() + HandleErrorWithContext(klog.NewContext(context.Background(), logger), err, msg, keysAndValues...) + }, + } { + t.Run(name, func(t *testing.T) { + for name, tc := range map[string]struct { + err error + msg string + keysAndValues []interface{} + expectLog string + }{ + "no-error": { + msg: "hello world", + expectLog: `"hello world" logger="UnhandledError"`, + }, + "complex": { + err: errors.New("fake error"), + msg: "ignore", + keysAndValues: []interface{}{"a", 1, "b", "c"}, + expectLog: `"ignore" err="fake error" logger="UnhandledError" a=1 b="c"`, + }, + } { + t.Run(name, func(t *testing.T) { + var buffer bytes.Buffer + timeInUTC := time.Date(2009, 12, 1, 13, 30, 40, 42000, time.UTC) + timeString := "1201 13:30:40.000042" + logger := textlogger.NewLogger(textlogger.NewConfig( + textlogger.FixedTime(timeInUTC), + textlogger.Output(&buffer), + )) + + _, _, line, _ := runtime.Caller(0) + handleError(logger, tc.err, tc.msg, tc.keysAndValues...) + assert.Equal(t, fmt.Sprintf("E%s %7d runtime_test.go:%d] %s\n", timeString, os.Getpid(), line+1, tc.expectLog), buffer.String()) + }) + } + }) + } +} + +func TestErrorToString(t *testing.T) { + for name, tt := range map[string]struct { + err error + msg string + kvs []any + expectString string + }{ + "simple": { + errors.New("some error"), + "Unhandled error", + nil, + `"Unhandled error" err="some error"`, + }, + "nil-error": { + nil, + "Some problem occurred", + nil, + `"Some problem occurred"`, + }, + "keys-and-values": { + errors.New("some error"), + "Some error occurred", + []any{"str", "foobar", "int", 1, "multiLine", "line 1\nline 2"}, + `"Some error occurred" err="some error" str="foobar" int=1 multiLine=< + line 1 + line 2 + >`, + }, + } { + t.Run(name, func(t *testing.T) { + actualString := ErrorToString(tt.err, tt.msg, tt.kvs...) + assert.Equal(t, tt.expectString, actualString) + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/byte.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/byte.go new file mode 100644 index 0000000000..4d7a17c3af --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/byte.go @@ -0,0 +1,137 @@ +/* +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 sets + +// Byte is a set of bytes, implemented via map[byte]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[byte]{} +// s2 := New[byte]() +type Byte map[byte]Empty + +// NewByte creates a Byte from a list of values. +func NewByte(items ...byte) Byte { + return Byte(New[byte](items...)) +} + +// ByteKeySet creates a Byte from a keys of a map[byte](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func ByteKeySet[T any](theMap map[byte]T) Byte { + return Byte(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Byte) Insert(items ...byte) Byte { + return Byte(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Byte) Delete(items ...byte) Byte { + return Byte(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Byte) Has(item byte) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Byte) HasAll(items ...byte) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Byte) HasAny(items ...byte) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Byte) Clone() Byte { + return Byte(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Byte) Difference(s2 Byte) Byte { + return Byte(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Byte) SymmetricDifference(s2 Byte) Byte { + return Byte(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Byte) Union(s2 Byte) Byte { + return Byte(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Byte) Intersection(s2 Byte) Byte { + return Byte(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Byte) IsSuperset(s2 Byte) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Byte) Equal(s2 Byte) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted byte slice. +func (s Byte) List() []byte { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Byte) UnsortedList() []byte { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Byte) PopAny() (byte, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Byte) Len() int { + return len(s) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/doc.go new file mode 100644 index 0000000000..194883390c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/doc.go @@ -0,0 +1,19 @@ +/* +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 sets has generic set and specified sets. Generic set will +// replace specified ones over time. And specific ones are deprecated. +package sets diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/empty.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/empty.go new file mode 100644 index 0000000000..fbb1df06d9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/empty.go @@ -0,0 +1,21 @@ +/* +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 sets + +// Empty is public since it is used by some internal API objects for conversions between external +// string arrays and internal sets, and conversion logic requires public types today. +type Empty struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int.go new file mode 100644 index 0000000000..5876fc9deb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int.go @@ -0,0 +1,137 @@ +/* +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 sets + +// Int is a set of ints, implemented via map[int]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[int]{} +// s2 := New[int]() +type Int map[int]Empty + +// NewInt creates a Int from a list of values. +func NewInt(items ...int) Int { + return Int(New[int](items...)) +} + +// IntKeySet creates a Int from a keys of a map[int](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func IntKeySet[T any](theMap map[int]T) Int { + return Int(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Int) Insert(items ...int) Int { + return Int(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Int) Delete(items ...int) Int { + return Int(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Int) Has(item int) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int) HasAll(items ...int) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Int) HasAny(items ...int) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Int) Clone() Int { + return Int(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Int) Difference(s2 Int) Int { + return Int(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Int) SymmetricDifference(s2 Int) Int { + return Int(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Int) Union(s2 Int) Int { + return Int(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Int) Intersection(s2 Int) Int { + return Int(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Int) IsSuperset(s2 Int) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Int) Equal(s2 Int) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted int slice. +func (s Int) List() []int { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int) UnsortedList() []int { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Int) PopAny() (int, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Int) Len() int { + return len(s) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int32.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int32.go new file mode 100644 index 0000000000..2c640c5d0f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int32.go @@ -0,0 +1,137 @@ +/* +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 sets + +// Int32 is a set of int32s, implemented via map[int32]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[int32]{} +// s2 := New[int32]() +type Int32 map[int32]Empty + +// NewInt32 creates a Int32 from a list of values. +func NewInt32(items ...int32) Int32 { + return Int32(New[int32](items...)) +} + +// Int32KeySet creates a Int32 from a keys of a map[int32](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func Int32KeySet[T any](theMap map[int32]T) Int32 { + return Int32(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Int32) Insert(items ...int32) Int32 { + return Int32(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Int32) Delete(items ...int32) Int32 { + return Int32(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Int32) Has(item int32) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int32) HasAll(items ...int32) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Int32) HasAny(items ...int32) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Int32) Clone() Int32 { + return Int32(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Int32) Difference(s2 Int32) Int32 { + return Int32(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Int32) SymmetricDifference(s2 Int32) Int32 { + return Int32(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Int32) Union(s2 Int32) Int32 { + return Int32(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Int32) Intersection(s2 Int32) Int32 { + return Int32(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Int32) IsSuperset(s2 Int32) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Int32) Equal(s2 Int32) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted int32 slice. +func (s Int32) List() []int32 { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int32) UnsortedList() []int32 { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Int32) PopAny() (int32, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Int32) Len() int { + return len(s) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int64.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int64.go new file mode 100644 index 0000000000..bf3eb3ffa2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/int64.go @@ -0,0 +1,137 @@ +/* +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 sets + +// Int64 is a set of int64s, implemented via map[int64]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[int64]{} +// s2 := New[int64]() +type Int64 map[int64]Empty + +// NewInt64 creates a Int64 from a list of values. +func NewInt64(items ...int64) Int64 { + return Int64(New[int64](items...)) +} + +// Int64KeySet creates a Int64 from a keys of a map[int64](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func Int64KeySet[T any](theMap map[int64]T) Int64 { + return Int64(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s Int64) Insert(items ...int64) Int64 { + return Int64(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s Int64) Delete(items ...int64) Int64 { + return Int64(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s Int64) Has(item int64) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int64) HasAll(items ...int64) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s Int64) HasAny(items ...int64) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s Int64) Clone() Int64 { + return Int64(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Int64) Difference(s2 Int64) Int64 { + return Int64(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Int64) SymmetricDifference(s2 Int64) Int64 { + return Int64(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Int64) Union(s2 Int64) Int64 { + return Int64(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Int64) Intersection(s2 Int64) Int64 { + return Int64(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Int64) IsSuperset(s2 Int64) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Int64) Equal(s2 Int64) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted int64 slice. +func (s Int64) List() []int64 { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int64) UnsortedList() []int64 { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s Int64) PopAny() (int64, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s Int64) Len() int { + return len(s) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set.go new file mode 100644 index 0000000000..ae3d15eb25 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set.go @@ -0,0 +1,223 @@ +/* +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 sets + +import ( + "cmp" + "slices" +) + +// Set is a set of the same type elements, implemented via map[comparable]struct{} for minimal memory consumption. +type Set[T comparable] map[T]Empty + +// cast transforms specified set to generic Set[T]. +func cast[T comparable](s map[T]Empty) Set[T] { return s } + +// New creates a Set from a list of values. +// NOTE: type param must be explicitly instantiated if given items are empty. +func New[T comparable](items ...T) Set[T] { + ss := make(Set[T], len(items)) + ss.Insert(items...) + return ss +} + +// KeySet creates a Set from a keys of a map[comparable](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func KeySet[T comparable, V any](theMap map[T]V) Set[T] { + ret := make(Set[T], len(theMap)) + for keyValue := range theMap { + ret.Insert(keyValue) + } + return ret +} + +// Insert adds items to the set. +func (s Set[T]) Insert(items ...T) Set[T] { + for _, item := range items { + s[item] = Empty{} + } + return s +} + +func Insert[T comparable](set Set[T], items ...T) Set[T] { + return set.Insert(items...) +} + +// Delete removes all items from the set. +func (s Set[T]) Delete(items ...T) Set[T] { + for _, item := range items { + delete(s, item) + } + return s +} + +// Clear empties the set. +// It is preferable to replace the set with a newly constructed set, +// but not all callers can do that (when there are other references to the map). +func (s Set[T]) Clear() Set[T] { + clear(s) + return s +} + +// Has returns true if and only if item is contained in the set. +func (s Set[T]) Has(item T) bool { + _, contained := s[item] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Set[T]) HasAll(items ...T) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// HasAny returns true if any items are contained in the set. +func (s Set[T]) HasAny(items ...T) bool { + for _, item := range items { + if s.Has(item) { + return true + } + } + return false +} + +// Clone returns a new set which is a copy of the current set. +func (s Set[T]) Clone() Set[T] { + result := make(Set[T], len(s)) + for key := range s { + result.Insert(key) + } + return result +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 Set[T]) Difference(s2 Set[T]) Set[T] { + result := New[T]() + for key := range s1 { + if !s2.Has(key) { + result.Insert(key) + } + } + return result +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 Set[T]) SymmetricDifference(s2 Set[T]) Set[T] { + return s1.Difference(s2).Union(s2.Difference(s1)) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Set[T]) Union(s2 Set[T]) Set[T] { + result := s1.Clone() + for key := range s2 { + result.Insert(key) + } + return result +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Set[T]) Intersection(s2 Set[T]) Set[T] { + var walk, other Set[T] + result := New[T]() + if s1.Len() < s2.Len() { + walk = s1 + other = s2 + } else { + walk = s2 + other = s1 + } + for key := range walk { + if other.Has(key) { + result.Insert(key) + } + } + return result +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Set[T]) IsSuperset(s2 Set[T]) bool { + for item := range s2 { + if !s1.Has(item) { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Set[T]) Equal(s2 Set[T]) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +// List returns the contents as a sorted T slice. +// +// This is a separate function and not a method because not all types supported +// by Generic are ordered and only those can be sorted. +func List[T cmp.Ordered](s Set[T]) []T { + res := s.UnsortedList() + slices.Sort(res) + return res +} + +// UnsortedList returns the slice with contents in random order. +func (s Set[T]) UnsortedList() []T { + res := make([]T, 0, len(s)) + for key := range s { + res = append(res, key) + } + return res +} + +// PopAny returns a single element from the set. +func (s Set[T]) PopAny() (T, bool) { + for key := range s { + s.Delete(key) + return key, true + } + var zeroValue T + return zeroValue, false +} + +// Len returns the size of the set. +func (s Set[T]) Len() int { + return len(s) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set_generic_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set_generic_test.go new file mode 100644 index 0000000000..e439a553d0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set_generic_test.go @@ -0,0 +1,375 @@ +/* +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 sets_test + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/util/sets" +) + +func TestSet(t *testing.T) { + s := sets.Set[string]{} + s2 := sets.Set[string]{} + if len(s) != 0 { + t.Errorf("Expected len=0: %d", len(s)) + } + s.Insert("a", "b") + if len(s) != 2 { + t.Errorf("Expected len=2: %d", len(s)) + } + s.Insert("c") + if s.Has("d") { + t.Errorf("Unexpected contents: %#v", s) + } + if !s.Has("a") { + t.Errorf("Missing contents: %#v", s) + } + s.Delete("a") + if s.Has("a") { + t.Errorf("Unexpected contents: %#v", s) + } + s.Insert("a") + if s.HasAll("a", "b", "d") { + t.Errorf("Unexpected contents: %#v", s) + } + if !s.HasAll("a", "b") { + t.Errorf("Missing contents: %#v", s) + } + s2.Insert("a", "b", "d") + if s.IsSuperset(s2) { + t.Errorf("Unexpected contents: %#v", s) + } + s2.Delete("d") + if !s.IsSuperset(s2) { + t.Errorf("Missing contents: %#v", s) + } +} + +func TestSetDeleteMultiples(t *testing.T) { + s := sets.Set[string]{} + s.Insert("a", "b", "c") + if len(s) != 3 { + t.Errorf("Expected len=3: %d", len(s)) + } + + s.Delete("a", "c") + if len(s) != 1 { + t.Errorf("Expected len=1: %d", len(s)) + } + if s.Has("a") { + t.Errorf("Unexpected contents: %#v", s) + } + if s.Has("c") { + t.Errorf("Unexpected contents: %#v", s) + } + if !s.Has("b") { + t.Errorf("Missing contents: %#v", s) + } + +} + +func TestSetClear(t *testing.T) { + s := sets.Set[string]{} + s.Insert("a", "b", "c") + if s.Len() != 3 { + t.Errorf("Expected len=3: %d", s.Len()) + } + + s.Clear() + if s.Len() != 0 { + t.Errorf("Expected len=0: %d", s.Len()) + } +} + +func TestSetClearWithSharedReference(t *testing.T) { + s := sets.Set[string]{} + s.Insert("a", "b", "c") + if s.Len() != 3 { + t.Errorf("Expected len=3: %d", s.Len()) + } + + m := s + s.Clear() + if s.Len() != 0 { + t.Errorf("Expected len=0 on the cleared set: %d", s.Len()) + } + if m.Len() != 0 { + t.Errorf("Expected len=0 on the shared reference: %d", m.Len()) + } +} + +func TestSetClearInSeparateFunction(t *testing.T) { + s := sets.Set[string]{} + s.Insert("a", "b", "c") + if s.Len() != 3 { + t.Errorf("Expected len=3: %d", s.Len()) + } + + clearSetAndAdd(s, "d") + if s.Len() != 1 { + t.Errorf("Expected len=1: %d", s.Len()) + } + if !s.Has("d") { + t.Errorf("Unexpected contents: %#v", s) + } +} + +func clearSetAndAdd[T comparable](s sets.Set[T], a T) { + s.Clear() + s.Insert(a) +} + +func TestNewSet(t *testing.T) { + s := sets.New("a", "b", "c") + if len(s) != 3 { + t.Errorf("Expected len=3: %d", len(s)) + } + if !s.Has("a") || !s.Has("b") || !s.Has("c") { + t.Errorf("Unexpected contents: %#v", s) + } +} + +func TestKeySet(t *testing.T) { + m := map[string]int{"a": 1, "b": 2, "c": 3} + ss := sets.KeySet[string](m) + if !ss.Equal(sets.New("a", "b", "c")) { + t.Errorf("Unexpected contents: %#v", sets.List(ss)) + } +} + +func TestNewEmptySet(t *testing.T) { + s := sets.New[string]() + if len(s) != 0 { + t.Errorf("Expected len=0: %d", len(s)) + } + s.Insert("a", "b", "c") + if len(s) != 3 { + t.Errorf("Expected len=3: %d", len(s)) + } + if !s.Has("a") || !s.Has("b") || !s.Has("c") { + t.Errorf("Unexpected contents: %#v", s) + } +} + +func TestSortedList(t *testing.T) { + s := sets.New("z", "y", "x", "a") + if !reflect.DeepEqual(sets.List(s), []string{"a", "x", "y", "z"}) { + t.Errorf("List gave unexpected result: %#v", sets.List(s)) + } +} + +func TestSetDifference(t *testing.T) { + a := sets.New("1", "2", "3") + b := sets.New("1", "2", "4", "5") + c := a.Difference(b) + d := b.Difference(a) + if len(c) != 1 { + t.Errorf("Expected len=1: %d", len(c)) + } + if !c.Has("3") { + t.Errorf("Unexpected contents: %#v", sets.List(c)) + } + if len(d) != 2 { + t.Errorf("Expected len=2: %d", len(d)) + } + if !d.Has("4") || !d.Has("5") { + t.Errorf("Unexpected contents: %#v", sets.List(d)) + } +} + +func TestSetSymmetricDifference(t *testing.T) { + a := sets.New("1", "2", "3") + b := sets.New("1", "2", "4", "5") + c := a.SymmetricDifference(b) + d := b.SymmetricDifference(a) + if !c.Equal(sets.New("3", "4", "5")) { + t.Errorf("Unexpected contents: %#v", sets.List(c)) + } + if !d.Equal(sets.New("3", "4", "5")) { + t.Errorf("Unexpected contents: %#v", sets.List(d)) + } +} + +func TestSetHasAny(t *testing.T) { + a := sets.New("1", "2", "3") + + if !a.HasAny("1", "4") { + t.Errorf("expected true, got false") + } + + if a.HasAny("0", "4") { + t.Errorf("expected false, got true") + } +} + +func TestSetEquals(t *testing.T) { + // Simple case (order doesn't matter) + a := sets.New("1", "2") + b := sets.New("2", "1") + if !a.Equal(b) { + t.Errorf("Expected to be equal: %v vs %v", a, b) + } + + // It is a set; duplicates are ignored + b = sets.New("2", "2", "1") + if !a.Equal(b) { + t.Errorf("Expected to be equal: %v vs %v", a, b) + } + + // Edge cases around empty sets / empty strings + a = sets.New[string]() + b = sets.New[string]() + if !a.Equal(b) { + t.Errorf("Expected to be equal: %v vs %v", a, b) + } + + b = sets.New("1", "2", "3") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } + + b = sets.New("1", "2", "") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } + + // Check for equality after mutation + a = sets.New[string]() + a.Insert("1") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } + + a.Insert("2") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } + + a.Insert("") + if !a.Equal(b) { + t.Errorf("Expected to be equal: %v vs %v", a, b) + } + + a.Delete("") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } +} + +func TestUnion(t *testing.T) { + tests := []struct { + s1 sets.Set[string] + s2 sets.Set[string] + expected sets.Set[string] + }{ + { + sets.New("1", "2", "3", "4"), + sets.New("3", "4", "5", "6"), + sets.New("1", "2", "3", "4", "5", "6"), + }, + { + sets.New("1", "2", "3", "4"), + sets.New[string](), + sets.New("1", "2", "3", "4"), + }, + { + sets.New[string](), + sets.New("1", "2", "3", "4"), + sets.New("1", "2", "3", "4"), + }, + { + sets.New[string](), + sets.New[string](), + sets.New[string](), + }, + } + + for _, test := range tests { + union := test.s1.Union(test.s2) + if union.Len() != test.expected.Len() { + t.Errorf("Expected union.Len()=%d but got %d", test.expected.Len(), union.Len()) + } + + if !union.Equal(test.expected) { + t.Errorf("Expected union.Equal(expected) but not true. union:%v expected:%v", sets.List(union), sets.List(test.expected)) + } + } +} + +func TestIntersection(t *testing.T) { + tests := []struct { + s1 sets.Set[string] + s2 sets.Set[string] + expected sets.Set[string] + }{ + { + sets.New("1", "2", "3", "4"), + sets.New("3", "4", "5", "6"), + sets.New("3", "4"), + }, + { + sets.New("1", "2", "3", "4"), + sets.New("1", "2", "3", "4"), + sets.New("1", "2", "3", "4"), + }, + { + sets.New("1", "2", "3", "4"), + sets.New[string](), + sets.New[string](), + }, + { + sets.New[string](), + sets.New("1", "2", "3", "4"), + sets.New[string](), + }, + { + sets.New[string](), + sets.New[string](), + sets.New[string](), + }, + } + + for _, test := range tests { + intersection := test.s1.Intersection(test.s2) + if intersection.Len() != test.expected.Len() { + t.Errorf("Expected intersection.Len()=%d but got %d", test.expected.Len(), intersection.Len()) + } + + if !intersection.Equal(test.expected) { + t.Errorf("Expected intersection.Equal(expected) but not true. intersection:%v expected:%v", sets.List(intersection), sets.List(intersection)) + } + } +} + +func BenchmarkListSmall(b *testing.B) { + s := sets.New("a", "b", "c", "d", "e", "f", "g", "h", "i", "j") + for b.Loop() { + sets.List(s) + } +} + +func BenchmarkListLarge(b *testing.B) { + s := make(sets.Set[int], 12345) + for i := range 12345 { + s.Insert(i * 7) + } + for b.Loop() { + sets.List(s) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set_test.go new file mode 100644 index 0000000000..9f492c1f4b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/set_test.go @@ -0,0 +1,373 @@ +/* +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 sets + +import ( + "fmt" + "math/rand" + "reflect" + "testing" +) + +func TestStringSet(t *testing.T) { + s := String{} + s2 := String{} + if len(s) != 0 { + t.Errorf("Expected len=0: %d", len(s)) + } + s.Insert("a", "b") + if len(s) != 2 { + t.Errorf("Expected len=2: %d", len(s)) + } + s.Insert("c") + if s.Has("d") { + t.Errorf("Unexpected contents: %#v", s) + } + if !s.Has("a") { + t.Errorf("Missing contents: %#v", s) + } + s.Delete("a") + if s.Has("a") { + t.Errorf("Unexpected contents: %#v", s) + } + s.Insert("a") + if s.HasAll("a", "b", "d") { + t.Errorf("Unexpected contents: %#v", s) + } + if !s.HasAll("a", "b") { + t.Errorf("Missing contents: %#v", s) + } + s2.Insert("a", "b", "d") + if s.IsSuperset(s2) { + t.Errorf("Unexpected contents: %#v", s) + } + s2.Delete("d") + if !s.IsSuperset(s2) { + t.Errorf("Missing contents: %#v", s) + } +} + +func TestStringSetDeleteMultiples(t *testing.T) { + s := String{} + s.Insert("a", "b", "c") + if len(s) != 3 { + t.Errorf("Expected len=3: %d", len(s)) + } + + s.Delete("a", "c") + if len(s) != 1 { + t.Errorf("Expected len=1: %d", len(s)) + } + if s.Has("a") { + t.Errorf("Unexpected contents: %#v", s) + } + if s.Has("c") { + t.Errorf("Unexpected contents: %#v", s) + } + if !s.Has("b") { + t.Errorf("Missing contents: %#v", s) + } + +} + +func TestNewStringSet(t *testing.T) { + s := NewString("a", "b", "c") + if len(s) != 3 { + t.Errorf("Expected len=3: %d", len(s)) + } + if !s.Has("a") || !s.Has("b") || !s.Has("c") { + t.Errorf("Unexpected contents: %#v", s) + } +} + +func TestStringSetList(t *testing.T) { + s := NewString("z", "y", "x", "a") + if !reflect.DeepEqual(s.List(), []string{"a", "x", "y", "z"}) { + t.Errorf("List gave unexpected result: %#v", s.List()) + } +} + +func TestStringSetDifference(t *testing.T) { + a := NewString("1", "2", "3") + b := NewString("1", "2", "4", "5") + c := a.Difference(b) + d := b.Difference(a) + if len(c) != 1 { + t.Errorf("Expected len=1: %d", len(c)) + } + if !c.Has("3") { + t.Errorf("Unexpected contents: %#v", c.List()) + } + if len(d) != 2 { + t.Errorf("Expected len=2: %d", len(d)) + } + if !d.Has("4") || !d.Has("5") { + t.Errorf("Unexpected contents: %#v", d.List()) + } +} + +func TestStringSetSymmetricDifference(t *testing.T) { + a := NewString("1", "2", "3") + b := NewString("1", "2", "4", "5") + c := a.SymmetricDifference(b) + d := b.SymmetricDifference(a) + if !c.Equal(NewString("3", "4", "5")) { + t.Errorf("Unexpected contents: %#v", c.List()) + } + if !d.Equal(NewString("3", "4", "5")) { + t.Errorf("Unexpected contents: %#v", d.List()) + } +} + +func TestStringSetHasAny(t *testing.T) { + a := NewString("1", "2", "3") + + if !a.HasAny("1", "4") { + t.Errorf("expected true, got false") + } + + if a.HasAny("0", "4") { + t.Errorf("expected false, got true") + } +} + +func TestStringSetEquals(t *testing.T) { + // Simple case (order doesn't matter) + a := NewString("1", "2") + b := NewString("2", "1") + if !a.Equal(b) { + t.Errorf("Expected to be equal: %v vs %v", a, b) + } + + // It is a set; duplicates are ignored + b = NewString("2", "2", "1") + if !a.Equal(b) { + t.Errorf("Expected to be equal: %v vs %v", a, b) + } + + // Edge cases around empty sets / empty strings + a = NewString() + b = NewString() + if !a.Equal(b) { + t.Errorf("Expected to be equal: %v vs %v", a, b) + } + + b = NewString("1", "2", "3") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } + + b = NewString("1", "2", "") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } + + // Check for equality after mutation + a = NewString() + a.Insert("1") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } + + a.Insert("2") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } + + a.Insert("") + if !a.Equal(b) { + t.Errorf("Expected to be equal: %v vs %v", a, b) + } + + a.Delete("") + if a.Equal(b) { + t.Errorf("Expected to be not-equal: %v vs %v", a, b) + } +} + +func TestStringUnion(t *testing.T) { + tests := []struct { + s1 String + s2 String + expected String + }{ + { + NewString("1", "2", "3", "4"), + NewString("3", "4", "5", "6"), + NewString("1", "2", "3", "4", "5", "6"), + }, + { + NewString("1", "2", "3", "4"), + NewString(), + NewString("1", "2", "3", "4"), + }, + { + NewString(), + NewString("1", "2", "3", "4"), + NewString("1", "2", "3", "4"), + }, + { + NewString(), + NewString(), + NewString(), + }, + } + + for _, test := range tests { + union := test.s1.Union(test.s2) + if union.Len() != test.expected.Len() { + t.Errorf("Expected union.Len()=%d but got %d", test.expected.Len(), union.Len()) + } + + if !union.Equal(test.expected) { + t.Errorf("Expected union.Equal(expected) but not true. union:%v expected:%v", union.List(), test.expected.List()) + } + } +} + +func TestStringIntersection(t *testing.T) { + tests := []struct { + s1 String + s2 String + expected String + }{ + { + NewString("1", "2", "3", "4"), + NewString("3", "4", "5", "6"), + NewString("3", "4"), + }, + { + NewString("1", "2", "3", "4"), + NewString("1", "2", "3", "4"), + NewString("1", "2", "3", "4"), + }, + { + NewString("1", "2", "3", "4"), + NewString(), + NewString(), + }, + { + NewString(), + NewString("1", "2", "3", "4"), + NewString(), + }, + { + NewString(), + NewString(), + NewString(), + }, + } + + for _, test := range tests { + intersection := test.s1.Intersection(test.s2) + if intersection.Len() != test.expected.Len() { + t.Errorf("Expected intersection.Len()=%d but got %d", test.expected.Len(), intersection.Len()) + } + + if !intersection.Equal(test.expected) { + t.Errorf("Expected intersection.Equal(expected) but not true. intersection:%v expected:%v", intersection.List(), test.expected.List()) + } + } +} + +type randomStringAlphabet string + +func (a randomStringAlphabet) makeString(minLen, maxLen int) string { + n := minLen + if minLen < maxLen { + n += rand.Intn(maxLen - minLen) + } + var s string + for i := 0; i < n; i++ { + s += string(a[rand.Intn(len(a))]) + } + return s +} + +var randomStringMaker = randomStringAlphabet("abcdefghijklmnopqrstuvwxyz0123456789") + +func BenchmarkStringSet(b *testing.B) { + cases := []struct { + size int + minStringLen int + maxStringLen int + }{ + {20, 10, 20}, + {50, 10, 30}, + {100, 20, 40}, + {500, 20, 50}, + {1000, 20, 60}, + } + + for i := range cases { + here := cases[i] + makeSet := func() String { + s := NewString() + for j := 0; j < here.size; j++ { + s.Insert(randomStringMaker.makeString(here.minStringLen, here.maxStringLen)) + } + return s + } + operands := make([]String, 500) + for i := range operands { + operands[i] = makeSet() + } + randOperand := func() String { return operands[rand.Intn(len(operands))] } + + b.Run(fmt.Sprintf("insert-%v", here.size), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + makeSet() + } + }) + + b.Run(fmt.Sprintf("key-set-%v", here.size), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + StringKeySet(randOperand()) + } + }) + + b.Run(fmt.Sprintf("has-%v", here.size), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + randOperand().Has(randomStringMaker.makeString(here.minStringLen, here.maxStringLen)) + } + }) + + b.Run(fmt.Sprintf("intersection-%v", here.size), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + randOperand().Intersection(randOperand()) + } + }) + + b.Run(fmt.Sprintf("symmetric-difference-%v", here.size), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + randOperand().SymmetricDifference(randOperand()) + } + }) + + b.Run(fmt.Sprintf("list-%v", here.size), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + randOperand().List() + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/string.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/string.go new file mode 100644 index 0000000000..1dab6d13cc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sets/string.go @@ -0,0 +1,137 @@ +/* +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 sets + +// String is a set of strings, implemented via map[string]struct{} for minimal memory consumption. +// +// Deprecated: use generic Set instead. +// new ways: +// s1 := Set[string]{} +// s2 := New[string]() +type String map[string]Empty + +// NewString creates a String from a list of values. +func NewString(items ...string) String { + return String(New[string](items...)) +} + +// StringKeySet creates a String from a keys of a map[string](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func StringKeySet[T any](theMap map[string]T) String { + return String(KeySet(theMap)) +} + +// Insert adds items to the set. +func (s String) Insert(items ...string) String { + return String(cast(s).Insert(items...)) +} + +// Delete removes all items from the set. +func (s String) Delete(items ...string) String { + return String(cast(s).Delete(items...)) +} + +// Has returns true if and only if item is contained in the set. +func (s String) Has(item string) bool { + return cast(s).Has(item) +} + +// HasAll returns true if and only if all items are contained in the set. +func (s String) HasAll(items ...string) bool { + return cast(s).HasAll(items...) +} + +// HasAny returns true if any items are contained in the set. +func (s String) HasAny(items ...string) bool { + return cast(s).HasAny(items...) +} + +// Clone returns a new set which is a copy of the current set. +func (s String) Clone() String { + return String(cast(s).Clone()) +} + +// Difference returns a set of objects that are not in s2. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s1 String) Difference(s2 String) String { + return String(cast(s1).Difference(cast(s2))) +} + +// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection. +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.SymmetricDifference(s2) = {a3, a4, a5} +// s2.SymmetricDifference(s1) = {a3, a4, a5} +func (s1 String) SymmetricDifference(s2 String) String { + return String(cast(s1).SymmetricDifference(cast(s2))) +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 String) Union(s2 String) String { + return String(cast(s1).Union(cast(s2))) +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 String) Intersection(s2 String) String { + return String(cast(s1).Intersection(cast(s2))) +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 String) IsSuperset(s2 String) bool { + return cast(s1).IsSuperset(cast(s2)) +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 String) Equal(s2 String) bool { + return cast(s1).Equal(cast(s2)) +} + +// List returns the contents as a sorted string slice. +func (s String) List() []string { + return List(cast(s)) +} + +// UnsortedList returns the slice with contents in random order. +func (s String) UnsortedList() []string { + return cast(s).UnsortedList() +} + +// PopAny returns a single element from the set. +func (s String) PopAny() (string, bool) { + return cast(s).PopAny() +} + +// Len returns the size of the set. +func (s String) Len() int { + return len(s) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sort/sort.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sort/sort.go new file mode 100644 index 0000000000..6c2e0eb7a5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sort/sort.go @@ -0,0 +1,191 @@ +/* +Copyright 2025 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 sort + +import ( + "container/heap" + "fmt" + "sort" + + "k8s.io/apimachinery/pkg/util/sets" +) + +// MergePreservingRelativeOrder performs a topological consensus sort of items from multiple sources. +// It merges multiple lists of strings into a single list, preserving the relative order of +// elements within each source list. +// +// For any two items, if one appears before the other in any of the input lists, +// that relative order will be preserved in the output. If no relative ordering is +// defined between two items, they are sorted lexicographically. +// +// The function uses Kahn's algorithm for topological sorting with a min-heap to ensure +// deterministic output. Items with no dependencies are processed in lexicographic order, +// guaranteeing consistent results across multiple invocations with the same input. +// +// This function contains a shortcut optimization that returns an input list directly +// if it already contains all unique items. This provides O(n) performance in the best case. +// +// Example: +// - Input: {{"a", "b", "c"}, {"b", "c"}} returns {"a", "b", "c"} +// - Input: {{"a", "c"}, {"b", "c"}} returns {"a", "b", "c"} (lexicographic tie-breaking) +// - Input: {{"a", "b"}, {"b", "a"}} returns error (cycle detected) +// +// Complexity: O(L*n + V*log(V) + E) where L is the number of lists, n is the average +// list size, V is the number of unique items, and E is the number of precedence edges. +// +// This is useful for creating a stable, consistent ordering when merging data from +// multiple sources that may have partial but not conflicting orderings. +func MergePreservingRelativeOrder(inputLists [][]string) []string { + if len(inputLists) == 0 { + return nil + } + + // Build a directed graph of precedence relationships + graph := make(map[string]*graphNode) + for _, list := range inputLists { + for i, item := range list { + node := getOrCreateNode(graph, item) + + // Add edge from current item to next item in list + if i < len(list)-1 { + nextItem := list[i+1] + nextNode := getOrCreateNode(graph, nextItem) + + // Only add edge if not already present (avoid incrementing in-degree multiple times) + if !node.outEdges.Has(nextItem) { + node.outEdges.Insert(nextItem) + nextNode.inDegree++ + } + } + } + } + + // Shortcut: if any input list contains all items (no duplicates), use it + allItems := sets.New[string]() + for name := range graph { + allItems.Insert(name) + } + for _, list := range inputLists { + if len(list) == allItems.Len() && isUnique(list) { + return list + } + } + + // Perform topological sort using Kahn's algorithm with min-heap for determinism + result, err := topologicalSort(graph) + if err != nil { + // This should not happen with valid input, but if it does, + // fall back to lexicographic sort to provide some result + items := make([]string, 0, len(graph)) + for name := range graph { + items = append(items, name) + } + sort.Strings(items) + return items + } + + return result +} + +// getOrCreateNode retrieves or creates a graph node for the given name +func getOrCreateNode(graph map[string]*graphNode, name string) *graphNode { + if graph[name] == nil { + graph[name] = &graphNode{ + outEdges: sets.New[string](), + inDegree: 0, + } + } + return graph[name] +} + +// isUnique checks if a list contains no duplicate items +func isUnique(list []string) bool { + seen := make(map[string]bool, len(list)) + for _, item := range list { + if seen[item] { + return false + } + seen[item] = true + } + return true +} + +// topologicalSort performs Kahn's algorithm with a min-heap for deterministic ordering +func topologicalSort(graph map[string]*graphNode) ([]string, error) { + // Initialize min-heap with all nodes that have no incoming edges + pq := &stringMinHeap{} + heap.Init(pq) + + for name, node := range graph { + if node.inDegree == 0 { + heap.Push(pq, name) + } + } + + result := make([]string, 0, len(graph)) + + for pq.Len() > 0 { + // Pop item with lowest lexicographic value + current := heap.Pop(pq).(string) + result = append(result, current) + + currentNode := graph[current] + + // Reduce in-degree for all neighbors + for neighbor := range currentNode.outEdges { + neighborNode := graph[neighbor] + neighborNode.inDegree-- + + // If in-degree becomes 0, add to heap + if neighborNode.inDegree == 0 { + heap.Push(pq, neighbor) + } + } + } + + // Check for cycles + if len(result) != len(graph) { + return nil, fmt.Errorf("cycle detected in precedence graph: sorted %d items but graph has %d items", len(result), len(graph)) + } + + return result, nil +} + +// graphNode represents a node in the precedence graph +type graphNode struct { + // Items that should come after this item + outEdges sets.Set[string] + // Number of items that should come before this item + inDegree int +} + +// stringMinHeap implements heap.Interface for strings (min-heap with lexicographic ordering) +type stringMinHeap []string + +func (h stringMinHeap) Len() int { return len(h) } +func (h stringMinHeap) Less(i, j int) bool { return h[i] < h[j] } +func (h stringMinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h *stringMinHeap) Push(x interface{}) { + *h = append(*h, x.(string)) +} +func (h *stringMinHeap) Pop() interface{} { + old := *h + n := len(old) + x := old[n-1] + *h = old[0 : n-1] + return x +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sort/sort_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sort/sort_test.go new file mode 100644 index 0000000000..a21b97b5ab --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/sort/sort_test.go @@ -0,0 +1,148 @@ +/* +Copyright 2025 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 sort + +import ( + "testing" +) + +func TestSortDiscoveryGroupsTopo(t *testing.T) { + cases := []struct { + name string + input [][]string + want []string + }{ + { + name: "consensus ordering", + input: [][]string{ + {"A", "B", "C", "D"}, + {"A", "B", "C", "D"}, + {"A", "X", "Z", "D"}, + {"Z", "Y"}, + {"Q"}, + }, + want: []string{"A", "B", "C", "Q", "X", "Z", "D", "Y"}, + }, + { + name: "empty input", + input: [][]string{}, + want: []string{}, + }, + { + name: "single peer", + input: [][]string{{"foo", "bar", "baz"}}, + want: []string{"foo", "bar", "baz"}, + }, + { + name: "conflicting orderings", + input: [][]string{{"A", "B"}, {"B", "A"}}, + want: []string{"A", "B"}, + }, + { + name: "empty list merged with non-empty list", + input: [][]string{{}, {"A", "B", "C"}}, + want: []string{"A", "B", "C"}, + }, + { + name: "multiple empty lists merged", + input: [][]string{{}, {}, {}}, + want: []string{}, + }, + { + name: "lexical tiebreak at beginning", + input: [][]string{ + {"C", "D", "E"}, + {"B", "D", "E"}, + {"A", "D", "E"}, + }, + // A, B, C have no precedence constraints, so lexical order + want: []string{"A", "B", "C", "D", "E"}, + }, + { + name: "lexical tiebreak in middle", + input: [][]string{ + {"A", "D", "E"}, + {"A", "C", "E"}, + {"A", "B", "E"}, + }, + // A comes first (consensus), then B, C, D (lexical), then E (consensus) + want: []string{"A", "B", "C", "D", "E"}, + }, + { + name: "conflicting orderings of 3 lists", + input: [][]string{ + {"A", "B", "C"}, + {"B", "C", "A"}, + {"C", "A", "B"}, + }, + // Creates cycle: A->B, B->C, C->A + // Fallback to lexicographic sort + want: []string{"A", "B", "C"}, + }, + { + name: "conflicting ordering with different list lengths", + input: [][]string{ + {"A", "B", "C", "D"}, + {"B", "A"}, + {"C", "D"}, + }, + // A->B->C->D from first list, but B->A from second + // Creates cycle between A and B + // Fallback to lexicographic sort + want: []string{"A", "B", "C", "D"}, + }, + { + name: "conflicting partial lists", + input: [][]string{ + {"A", "B"}, + {"C", "D"}, + {"B", "A"}, + }, + // A->B from first, B->A from third (cycle) + // C->D is independent + // Fallback to lexicographic sort + want: []string{"A", "B", "C", "D"}, + }, + { + name: "cycle", + input: [][]string{ + {"A", "B"}, + {"B", "C"}, + {"C", "A"}, + }, + // Creates cycle: A->B->C->A + // Fallback to lexicographic sort + want: []string{"A", "B", "C"}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := MergePreservingRelativeOrder(tc.input) + if len(got) != len(tc.want) { + t.Errorf("length mismatch:\n got: %d\n want: %d", len(got), len(tc.want)) + return + } + for i := range got { + if got[i] != tc.want[i] { + t.Errorf("mismatch got: %v\n want: %v", got, tc.want) + return + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/OWNERS new file mode 100644 index 0000000000..73244449f2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/OWNERS @@ -0,0 +1,9 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - apelisse + - pwittrock +reviewers: + - apelisse +emeritus_approvers: + - mengqiy diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/errors.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/errors.go new file mode 100644 index 0000000000..ab66d04523 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/errors.go @@ -0,0 +1,49 @@ +/* +Copyright 2017 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 strategicpatch + +import ( + "fmt" +) + +type LookupPatchMetaError struct { + Path string + Err error +} + +func (e LookupPatchMetaError) Error() string { + return fmt.Sprintf("LookupPatchMetaError(%s): %v", e.Path, e.Err) +} + +type FieldNotFoundError struct { + Path string + Field string +} + +func (e FieldNotFoundError) Error() string { + return fmt.Sprintf("unable to find api field %q in %s", e.Field, e.Path) +} + +type InvalidTypeError struct { + Path string + Expected string + Actual string +} + +func (e InvalidTypeError) Error() string { + return fmt.Sprintf("invalid type for %s: got %q, expected %q", e.Path, e.Actual, e.Expected) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go new file mode 100644 index 0000000000..1bfed1c2ec --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go @@ -0,0 +1,283 @@ +/* +Copyright 2017 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 strategicpatch + +import ( + "errors" + "fmt" + "reflect" + "strings" + + "k8s.io/apimachinery/pkg/util/mergepatch" + forkedjson "k8s.io/apimachinery/third_party/forked/golang/json" + openapi "k8s.io/kube-openapi/pkg/util/proto" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +const patchMergeKey = "x-kubernetes-patch-merge-key" +const patchStrategy = "x-kubernetes-patch-strategy" + +type PatchMeta struct { + patchStrategies []string + patchMergeKey string +} + +func (pm *PatchMeta) GetPatchStrategies() []string { + if pm.patchStrategies == nil { + return []string{} + } + return pm.patchStrategies +} + +func (pm *PatchMeta) SetPatchStrategies(ps []string) { + pm.patchStrategies = ps +} + +func (pm *PatchMeta) GetPatchMergeKey() string { + return pm.patchMergeKey +} + +func (pm *PatchMeta) SetPatchMergeKey(pmk string) { + pm.patchMergeKey = pmk +} + +type LookupPatchMeta interface { + // LookupPatchMetadataForStruct gets subschema and the patch metadata (e.g. patch strategy and merge key) for map. + LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) + // LookupPatchMetadataForSlice get subschema and the patch metadata for slice. + LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) + // Get the type name of the field + Name() string +} + +type PatchMetaFromStruct struct { + T reflect.Type +} + +func NewPatchMetaFromStruct(dataStruct interface{}) (PatchMetaFromStruct, error) { + t, err := getTagStructType(dataStruct) + return PatchMetaFromStruct{T: t}, err +} + +var _ LookupPatchMeta = PatchMetaFromStruct{} + +func (s PatchMetaFromStruct) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) { + fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadataForStruct(s.T, key) + if err != nil { + return nil, PatchMeta{}, err + } + + return PatchMetaFromStruct{T: fieldType}, + PatchMeta{ + patchStrategies: fieldPatchStrategies, + patchMergeKey: fieldPatchMergeKey, + }, nil +} + +func (s PatchMetaFromStruct) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) { + subschema, patchMeta, err := s.LookupPatchMetadataForStruct(key) + if err != nil { + return nil, PatchMeta{}, err + } + elemPatchMetaFromStruct := subschema.(PatchMetaFromStruct) + t := elemPatchMetaFromStruct.T + + var elemType reflect.Type + switch t.Kind() { + // If t is an array or a slice, get the element type. + // If element is still an array or a slice, return an error. + // Otherwise, return element type. + case reflect.Array, reflect.Slice: + elemType = t.Elem() + if elemType.Kind() == reflect.Array || elemType.Kind() == reflect.Slice { + return nil, PatchMeta{}, errors.New("unexpected slice of slice") + } + // If t is an pointer, get the underlying element. + // If the underlying element is neither an array nor a slice, the pointer is pointing to a slice, + // e.g. https://github.com/kubernetes/kubernetes/blob/bc22e206c79282487ea0bf5696d5ccec7e839a76/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go#L2782-L2822 + // If the underlying element is either an array or a slice, return its element type. + case reflect.Pointer: + t = t.Elem() + if t.Kind() == reflect.Array || t.Kind() == reflect.Slice { + t = t.Elem() + } + elemType = t + default: + return nil, PatchMeta{}, fmt.Errorf("expected slice or array type, but got: %s", s.T.Kind().String()) + } + + return PatchMetaFromStruct{T: elemType}, patchMeta, nil +} + +func (s PatchMetaFromStruct) Name() string { + return s.T.Kind().String() +} + +func getTagStructType(dataStruct interface{}) (reflect.Type, error) { + if dataStruct == nil { + return nil, mergepatch.ErrBadArgKind(struct{}{}, nil) + } + + t := reflect.TypeOf(dataStruct) + // Get the underlying type for pointers + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + return nil, mergepatch.ErrBadArgKind(struct{}{}, dataStruct) + } + + return t, nil +} + +func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type { + t, err := getTagStructType(dataStruct) + if err != nil { + panic(err) + } + return t +} + +type PatchMetaFromOpenAPIV3 struct { + // SchemaList is required to resolve OpenAPI V3 references + SchemaList map[string]*spec.Schema + Schema *spec.Schema +} + +func (s PatchMetaFromOpenAPIV3) traverse(key string) (PatchMetaFromOpenAPIV3, error) { + if s.Schema == nil { + return PatchMetaFromOpenAPIV3{}, nil + } + if len(s.Schema.Properties) == 0 { + return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key) + } + subschema, ok := s.Schema.Properties[key] + if !ok { + return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key) + } + return PatchMetaFromOpenAPIV3{SchemaList: s.SchemaList, Schema: &subschema}, nil +} + +func resolve(l *PatchMetaFromOpenAPIV3) error { + if len(l.Schema.AllOf) > 0 { + l.Schema = &l.Schema.AllOf[0] + } + if refString := l.Schema.Ref.String(); refString != "" { + str := strings.TrimPrefix(refString, "#/components/schemas/") + sch, ok := l.SchemaList[str] + if ok { + l.Schema = sch + } else { + return fmt.Errorf("unable to resolve %s in OpenAPI V3", refString) + } + } + return nil +} + +func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) { + l, err := s.traverse(key) + if err != nil { + return l, PatchMeta{}, err + } + p := PatchMeta{} + f, ok := l.Schema.Extensions[patchMergeKey] + if ok { + p.SetPatchMergeKey(f.(string)) + } + g, ok := l.Schema.Extensions[patchStrategy] + if ok { + p.SetPatchStrategies(strings.Split(g.(string), ",")) + } + + err = resolve(&l) + return l, p, err +} + +func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) { + l, err := s.traverse(key) + if err != nil { + return l, PatchMeta{}, err + } + p := PatchMeta{} + f, ok := l.Schema.Extensions[patchMergeKey] + if ok { + p.SetPatchMergeKey(f.(string)) + } + g, ok := l.Schema.Extensions[patchStrategy] + if ok { + p.SetPatchStrategies(strings.Split(g.(string), ",")) + } + if l.Schema.Items != nil { + l.Schema = l.Schema.Items.Schema + } + err = resolve(&l) + return l, p, err +} + +func (s PatchMetaFromOpenAPIV3) Name() string { + schema := s.Schema + if len(schema.Type) > 0 { + return strings.Join(schema.Type, "") + } + return "Struct" +} + +type PatchMetaFromOpenAPI struct { + Schema openapi.Schema +} + +func NewPatchMetaFromOpenAPI(s openapi.Schema) PatchMetaFromOpenAPI { + return PatchMetaFromOpenAPI{Schema: s} +} + +var _ LookupPatchMeta = PatchMetaFromOpenAPI{} + +func (s PatchMetaFromOpenAPI) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) { + if s.Schema == nil { + return &PatchMetaFromOpenAPI{}, PatchMeta{}, nil + } + kindItem := NewKindItem(key, s.Schema.GetPath()) + s.Schema.Accept(kindItem) + + err := kindItem.Error() + if err != nil { + return nil, PatchMeta{}, err + } + return PatchMetaFromOpenAPI{Schema: kindItem.subschema}, + kindItem.patchmeta, nil +} + +func (s PatchMetaFromOpenAPI) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) { + if s.Schema == nil { + return nil, PatchMeta{}, nil + } + sliceItem := NewSliceItem(key, s.Schema.GetPath()) + s.Schema.Accept(sliceItem) + + err := sliceItem.Error() + if err != nil { + return nil, PatchMeta{}, err + } + return PatchMetaFromOpenAPI{Schema: sliceItem.subschema}, + sliceItem.patchmeta, nil +} + +func (s PatchMetaFromOpenAPI) Name() string { + schema := s.Schema + return schema.GetName() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go new file mode 100644 index 0000000000..71f6b5e875 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/patch.go @@ -0,0 +1,2253 @@ +/* +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 strategicpatch + +import ( + "fmt" + "reflect" + "sort" + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/apimachinery/pkg/util/mergepatch" +) + +// An alternate implementation of JSON Merge Patch +// (https://tools.ietf.org/html/rfc7386) which supports the ability to annotate +// certain fields with metadata that indicates whether the elements of JSON +// lists should be merged or replaced. +// +// For more information, see the PATCH section of docs/devel/api-conventions.md. +// +// Some of the content of this package was borrowed with minor adaptations from +// evanphx/json-patch and openshift/origin. + +const ( + directiveMarker = "$patch" + deleteDirective = "delete" + replaceDirective = "replace" + mergeDirective = "merge" + + retainKeysStrategy = "retainKeys" + + deleteFromPrimitiveListDirectivePrefix = "$deleteFromPrimitiveList" + retainKeysDirective = "$" + retainKeysStrategy + setElementOrderDirectivePrefix = "$setElementOrder" +) + +// JSONMap is a representations of JSON object encoded as map[string]interface{} +// where the children can be either map[string]interface{}, []interface{} or +// primitive type). +// Operating on JSONMap representation is much faster as it doesn't require any +// json marshaling and/or unmarshaling operations. +type JSONMap map[string]interface{} + +type DiffOptions struct { + // SetElementOrder determines whether we generate the $setElementOrder parallel list. + SetElementOrder bool + // IgnoreChangesAndAdditions indicates if we keep the changes and additions in the patch. + IgnoreChangesAndAdditions bool + // IgnoreDeletions indicates if we keep the deletions in the patch. + IgnoreDeletions bool + // We introduce a new value retainKeys for patchStrategy. + // It indicates that all fields needing to be preserved must be + // present in the `retainKeys` list. + // And the fields that are present will be merged with live object. + // All the missing fields will be cleared when patching. + BuildRetainKeysDirective bool +} + +type MergeOptions struct { + // MergeParallelList indicates if we are merging the parallel list. + // We don't merge parallel list when calling mergeMap() in CreateThreeWayMergePatch() + // which is called client-side. + // We merge parallel list iff when calling mergeMap() in StrategicMergeMapPatch() + // which is called server-side + MergeParallelList bool + // IgnoreUnmatchedNulls indicates if we should process the unmatched nulls. + IgnoreUnmatchedNulls bool +} + +// The following code is adapted from github.com/openshift/origin/pkg/util/jsonmerge. +// Instead of defining a Delta that holds an original, a patch and a set of preconditions, +// the reconcile method accepts a set of preconditions as an argument. + +// CreateTwoWayMergePatch creates a patch that can be passed to StrategicMergePatch from an original +// document and a modified document, which are passed to the method as json encoded content. It will +// return a patch that yields the modified document when applied to the original document, or an error +// if either of the two documents is invalid. +func CreateTwoWayMergePatch(original, modified []byte, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) ([]byte, error) { + schema, err := NewPatchMetaFromStruct(dataStruct) + if err != nil { + return nil, err + } + + return CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema, fns...) +} + +func CreateTwoWayMergePatchUsingLookupPatchMeta( + original, modified []byte, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) ([]byte, error) { + originalMap := map[string]interface{}{} + if len(original) > 0 { + if err := json.Unmarshal(original, &originalMap); err != nil { + return nil, mergepatch.ErrBadJSONDoc + } + } + + modifiedMap := map[string]interface{}{} + if len(modified) > 0 { + if err := json.Unmarshal(modified, &modifiedMap); err != nil { + return nil, mergepatch.ErrBadJSONDoc + } + } + + patchMap, err := CreateTwoWayMergeMapPatchUsingLookupPatchMeta(originalMap, modifiedMap, schema, fns...) + if err != nil { + return nil, err + } + + return json.Marshal(patchMap) +} + +// CreateTwoWayMergeMapPatch creates a patch from an original and modified JSON objects, +// encoded JSONMap. +// The serialized version of the map can then be passed to StrategicMergeMapPatch. +func CreateTwoWayMergeMapPatch(original, modified JSONMap, dataStruct interface{}, fns ...mergepatch.PreconditionFunc) (JSONMap, error) { + schema, err := NewPatchMetaFromStruct(dataStruct) + if err != nil { + return nil, err + } + + return CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified, schema, fns...) +} + +func CreateTwoWayMergeMapPatchUsingLookupPatchMeta(original, modified JSONMap, schema LookupPatchMeta, fns ...mergepatch.PreconditionFunc) (JSONMap, error) { + diffOptions := DiffOptions{ + SetElementOrder: true, + } + patchMap, err := diffMaps(original, modified, schema, diffOptions) + if err != nil { + return nil, err + } + + // Apply the preconditions to the patch, and return an error if any of them fail. + for _, fn := range fns { + if !fn(patchMap) { + return nil, mergepatch.NewErrPreconditionFailed(patchMap) + } + } + + return patchMap, nil +} + +// Returns a (recursive) strategic merge patch that yields modified when applied to original. +// Including: +// - Adding fields to the patch present in modified, missing from original +// - Setting fields to the patch present in modified and original with different values +// - Delete fields present in original, missing from modified through +// - IFF map field - set to nil in patch +// - IFF list of maps && merge strategy - use deleteDirective for the elements +// - IFF list of primitives && merge strategy - use parallel deletion list +// - IFF list of maps or primitives with replace strategy (default) - set patch value to the value in modified +// - Build $retainKeys directive for fields with retainKeys patch strategy +func diffMaps(original, modified map[string]interface{}, schema LookupPatchMeta, diffOptions DiffOptions) (map[string]interface{}, error) { + patch := map[string]interface{}{} + + // This will be used to build the $retainKeys directive sent in the patch + retainKeysList := make([]interface{}, 0, len(modified)) + + // Compare each value in the modified map against the value in the original map + for key, modifiedValue := range modified { + // Get the underlying type for pointers + if diffOptions.BuildRetainKeysDirective && modifiedValue != nil { + retainKeysList = append(retainKeysList, key) + } + + originalValue, ok := original[key] + if !ok { + // Key was added, so add to patch + if !diffOptions.IgnoreChangesAndAdditions { + patch[key] = modifiedValue + } + continue + } + + // The patch may have a patch directive + // TODO: figure out if we need this. This shouldn't be needed by apply. When would the original map have patch directives in it? + foundDirectiveMarker, err := handleDirectiveMarker(key, originalValue, modifiedValue, patch) + if err != nil { + return nil, err + } + if foundDirectiveMarker { + continue + } + + if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) { + // Types have changed, so add to patch + if !diffOptions.IgnoreChangesAndAdditions { + patch[key] = modifiedValue + } + continue + } + + // Types are the same, so compare values + switch originalValueTyped := originalValue.(type) { + case map[string]interface{}: + modifiedValueTyped := modifiedValue.(map[string]interface{}) + err = handleMapDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions) + case []interface{}: + modifiedValueTyped := modifiedValue.([]interface{}) + err = handleSliceDiff(key, originalValueTyped, modifiedValueTyped, patch, schema, diffOptions) + default: + replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions) + } + if err != nil { + return nil, err + } + } + + updatePatchIfMissing(original, modified, patch, diffOptions) + // Insert the retainKeysList iff there are values present in the retainKeysList and + // either of the following is true: + // - the patch is not empty + // - there are additional field in original that need to be cleared + if len(retainKeysList) > 0 && + (len(patch) > 0 || hasAdditionalNewField(original, modified)) { + patch[retainKeysDirective] = sortScalars(retainKeysList) + } + return patch, nil +} + +// handleDirectiveMarker handles how to diff directive marker between 2 objects +func handleDirectiveMarker(key string, originalValue, modifiedValue interface{}, patch map[string]interface{}) (bool, error) { + if key == directiveMarker { + originalString, ok := originalValue.(string) + if !ok { + return false, fmt.Errorf("invalid value for special key: %s", directiveMarker) + } + modifiedString, ok := modifiedValue.(string) + if !ok { + return false, fmt.Errorf("invalid value for special key: %s", directiveMarker) + } + if modifiedString != originalString { + patch[directiveMarker] = modifiedValue + } + return true, nil + } + return false, nil +} + +// handleMapDiff diff between 2 maps `originalValueTyped` and `modifiedValue`, +// puts the diff in the `patch` associated with `key` +// key is the key associated with originalValue and modifiedValue. +// originalValue, modifiedValue are the old and new value respectively.They are both maps +// patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue +// diffOptions contains multiple options to control how we do the diff. +func handleMapDiff(key string, originalValue, modifiedValue, patch map[string]interface{}, + schema LookupPatchMeta, diffOptions DiffOptions) error { + subschema, patchMeta, err := schema.LookupPatchMetadataForStruct(key) + + if err != nil { + // We couldn't look up metadata for the field + // If the values are identical, this doesn't matter, no patch is needed + if reflect.DeepEqual(originalValue, modifiedValue) { + return nil + } + // Otherwise, return the error + return err + } + retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) + if err != nil { + return err + } + diffOptions.BuildRetainKeysDirective = retainKeys + switch patchStrategy { + // The patch strategic from metadata tells us to replace the entire object instead of diffing it + case replaceDirective: + if !diffOptions.IgnoreChangesAndAdditions { + patch[key] = modifiedValue + } + default: + patchValue, err := diffMaps(originalValue, modifiedValue, subschema, diffOptions) + if err != nil { + return err + } + // Maps were not identical, use provided patch value + if len(patchValue) > 0 { + patch[key] = patchValue + } + } + return nil +} + +// handleSliceDiff diff between 2 slices `originalValueTyped` and `modifiedValue`, +// puts the diff in the `patch` associated with `key` +// key is the key associated with originalValue and modifiedValue. +// originalValue, modifiedValue are the old and new value respectively.They are both slices +// patch is the patch map that contains key and the updated value, and it is the parent of originalValue, modifiedValue +// diffOptions contains multiple options to control how we do the diff. +func handleSliceDiff(key string, originalValue, modifiedValue []interface{}, patch map[string]interface{}, + schema LookupPatchMeta, diffOptions DiffOptions) error { + subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(key) + if err != nil { + // We couldn't look up metadata for the field + // If the values are identical, this doesn't matter, no patch is needed + if reflect.DeepEqual(originalValue, modifiedValue) { + return nil + } + // Otherwise, return the error + return err + } + retainKeys, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) + if err != nil { + return err + } + switch patchStrategy { + // Merge the 2 slices using mergePatchKey + case mergeDirective: + diffOptions.BuildRetainKeysDirective = retainKeys + addList, deletionList, setOrderList, err := diffLists(originalValue, modifiedValue, subschema, patchMeta.GetPatchMergeKey(), diffOptions) + if err != nil { + return err + } + if len(addList) > 0 { + patch[key] = addList + } + // generate a parallel list for deletion + if len(deletionList) > 0 { + parallelDeletionListKey := fmt.Sprintf("%s/%s", deleteFromPrimitiveListDirectivePrefix, key) + patch[parallelDeletionListKey] = deletionList + } + if len(setOrderList) > 0 { + parallelSetOrderListKey := fmt.Sprintf("%s/%s", setElementOrderDirectivePrefix, key) + patch[parallelSetOrderListKey] = setOrderList + } + default: + replacePatchFieldIfNotEqual(key, originalValue, modifiedValue, patch, diffOptions) + } + return nil +} + +// replacePatchFieldIfNotEqual updates the patch if original and modified are not deep equal +// if diffOptions.IgnoreChangesAndAdditions is false. +// original is the old value, maybe either the live cluster object or the last applied configuration +// modified is the new value, is always the users new config +func replacePatchFieldIfNotEqual(key string, original, modified interface{}, + patch map[string]interface{}, diffOptions DiffOptions) { + if diffOptions.IgnoreChangesAndAdditions { + // Ignoring changes - do nothing + return + } + if reflect.DeepEqual(original, modified) { + // Contents are identical - do nothing + return + } + // Create a patch to replace the old value with the new one + patch[key] = modified +} + +// updatePatchIfMissing iterates over `original` when ignoreDeletions is false. +// Clear the field whose key is not present in `modified`. +// original is the old value, maybe either the live cluster object or the last applied configuration +// modified is the new value, is always the users new config +func updatePatchIfMissing(original, modified, patch map[string]interface{}, diffOptions DiffOptions) { + if diffOptions.IgnoreDeletions { + // Ignoring deletion - do nothing + return + } + // Add nils for deleted values + for key := range original { + if _, found := modified[key]; !found { + patch[key] = nil + } + } +} + +// validateMergeKeyInLists checks if each map in the list has the mentryerge key. +func validateMergeKeyInLists(mergeKey string, lists ...[]interface{}) error { + for _, list := range lists { + for _, item := range list { + m, ok := item.(map[string]interface{}) + if !ok { + return mergepatch.ErrBadArgType(m, item) + } + if _, ok = m[mergeKey]; !ok { + return mergepatch.ErrNoMergeKey(m, mergeKey) + } + } + } + return nil +} + +// normalizeElementOrder sort `patch` list by `patchOrder` and sort `serverOnly` list by `serverOrder`. +// Then it merges the 2 sorted lists. +// It guarantee the relative order in the patch list and in the serverOnly list is kept. +// `patch` is a list of items in the patch, and `serverOnly` is a list of items in the live object. +// `patchOrder` is the order we want `patch` list to have and +// `serverOrder` is the order we want `serverOnly` list to have. +// kind is the kind of each item in the lists `patch` and `serverOnly`. +func normalizeElementOrder(patch, serverOnly, patchOrder, serverOrder []interface{}, mergeKey string, kind reflect.Kind) ([]interface{}, error) { + patch, err := normalizeSliceOrder(patch, patchOrder, mergeKey, kind) + if err != nil { + return nil, err + } + serverOnly, err = normalizeSliceOrder(serverOnly, serverOrder, mergeKey, kind) + if err != nil { + return nil, err + } + all := mergeSortedSlice(serverOnly, patch, serverOrder, mergeKey, kind) + + return all, nil +} + +// mergeSortedSlice merges the 2 sorted lists by serverOrder with best effort. +// It will insert each item in `left` list to `right` list. In most cases, the 2 lists will be interleaved. +// The relative order of left and right are guaranteed to be kept. +// They have higher precedence than the order in the live list. +// The place for a item in `left` is found by: +// scan from the place of last insertion in `right` to the end of `right`, +// the place is before the first item that is greater than the item we want to insert. +// example usage: using server-only items as left and patch items as right. We insert server-only items +// to patch list. We use the order of live object as record for comparison. +func mergeSortedSlice(left, right, serverOrder []interface{}, mergeKey string, kind reflect.Kind) []interface{} { + // Returns if l is less than r, and if both have been found. + // If l and r both present and l is in front of r, l is less than r. + less := func(l, r interface{}) (bool, bool) { + li := index(serverOrder, l, mergeKey, kind) + ri := index(serverOrder, r, mergeKey, kind) + if li >= 0 && ri >= 0 { + return li < ri, true + } else { + return false, false + } + } + + // left and right should be non-overlapping. + size := len(left) + len(right) + i, j := 0, 0 + s := make([]interface{}, size, size) + + for k := 0; k < size; k++ { + if i >= len(left) && j < len(right) { + // have items left in `right` list + s[k] = right[j] + j++ + } else if j >= len(right) && i < len(left) { + // have items left in `left` list + s[k] = left[i] + i++ + } else { + // compare them if i and j are both in bound + less, foundBoth := less(left[i], right[j]) + if foundBoth && less { + s[k] = left[i] + i++ + } else { + s[k] = right[j] + j++ + } + } + } + return s +} + +// index returns the index of the item in the given items, or -1 if it doesn't exist +// l must NOT be a slice of slices, this should be checked before calling. +func index(l []interface{}, valToLookUp interface{}, mergeKey string, kind reflect.Kind) int { + var getValFn func(interface{}) interface{} + // Get the correct `getValFn` based on item `kind`. + // It should return the value of merge key for maps and + // return the item for other kinds. + switch kind { + case reflect.Map: + getValFn = func(item interface{}) interface{} { + typedItem, ok := item.(map[string]interface{}) + if !ok { + return nil + } + val := typedItem[mergeKey] + return val + } + default: + getValFn = func(item interface{}) interface{} { + return item + } + } + + for i, v := range l { + if getValFn(valToLookUp) == getValFn(v) { + return i + } + } + return -1 +} + +// extractToDeleteItems takes a list and +// returns 2 lists: one contains items that should be kept and the other contains items to be deleted. +func extractToDeleteItems(l []interface{}) ([]interface{}, []interface{}, error) { + var nonDelete, toDelete []interface{} + for _, v := range l { + m, ok := v.(map[string]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(m, v) + } + + directive, foundDirective := m[directiveMarker] + if foundDirective && directive == deleteDirective { + toDelete = append(toDelete, v) + } else { + nonDelete = append(nonDelete, v) + } + } + return nonDelete, toDelete, nil +} + +// normalizeSliceOrder sort `toSort` list by `order` +func normalizeSliceOrder(toSort, order []interface{}, mergeKey string, kind reflect.Kind) ([]interface{}, error) { + var toDelete []interface{} + if kind == reflect.Map { + // make sure each item in toSort, order has merge key + err := validateMergeKeyInLists(mergeKey, toSort, order) + if err != nil { + return nil, err + } + toSort, toDelete, err = extractToDeleteItems(toSort) + if err != nil { + return nil, err + } + } + + sort.SliceStable(toSort, func(i, j int) bool { + if ii := index(order, toSort[i], mergeKey, kind); ii >= 0 { + if ij := index(order, toSort[j], mergeKey, kind); ij >= 0 { + return ii < ij + } + } + return true + }) + toSort = append(toSort, toDelete...) + return toSort, nil +} + +// Returns a (recursive) strategic merge patch, a parallel deletion list if necessary and +// another list to set the order of the list +// Only list of primitives with merge strategy will generate a parallel deletion list. +// These two lists should yield modified when applied to original, for lists with merge semantics. +func diffLists(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, []interface{}, error) { + if len(original) == 0 { + // Both slices are empty - do nothing + if len(modified) == 0 || diffOptions.IgnoreChangesAndAdditions { + return nil, nil, nil, nil + } + + // Old slice was empty - add all elements from the new slice + return modified, nil, nil, nil + } + + elementType, err := sliceElementType(original, modified) + if err != nil { + return nil, nil, nil, err + } + + var patchList, deleteList, setOrderList []interface{} + kind := elementType.Kind() + switch kind { + case reflect.Map: + patchList, deleteList, err = diffListsOfMaps(original, modified, schema, mergeKey, diffOptions) + if err != nil { + return nil, nil, nil, err + } + patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind) + if err != nil { + return nil, nil, nil, err + } + orderSame, err := isOrderSame(original, modified, mergeKey) + if err != nil { + return nil, nil, nil, err + } + // append the deletions to the end of the patch list. + patchList = append(patchList, deleteList...) + deleteList = nil + // generate the setElementOrder list when there are content changes or order changes + if diffOptions.SetElementOrder && + ((!diffOptions.IgnoreChangesAndAdditions && (len(patchList) > 0 || !orderSame)) || + (!diffOptions.IgnoreDeletions && len(patchList) > 0)) { + // Generate a list of maps that each item contains only the merge key. + setOrderList = make([]interface{}, len(modified)) + for i, v := range modified { + typedV := v.(map[string]interface{}) + setOrderList[i] = map[string]interface{}{ + mergeKey: typedV[mergeKey], + } + } + } + case reflect.Slice: + // Lists of Lists are not permitted by the api + return nil, nil, nil, mergepatch.ErrNoListOfLists + default: + patchList, deleteList, err = diffListsOfScalars(original, modified, diffOptions) + if err != nil { + return nil, nil, nil, err + } + patchList, err = normalizeSliceOrder(patchList, modified, mergeKey, kind) + // generate the setElementOrder list when there are content changes or order changes + if diffOptions.SetElementOrder && ((!diffOptions.IgnoreDeletions && len(deleteList) > 0) || + (!diffOptions.IgnoreChangesAndAdditions && !reflect.DeepEqual(original, modified))) { + setOrderList = modified + } + } + return patchList, deleteList, setOrderList, err +} + +// isOrderSame checks if the order in a list has changed +func isOrderSame(original, modified []interface{}, mergeKey string) (bool, error) { + if len(original) != len(modified) { + return false, nil + } + for i, modifiedItem := range modified { + equal, err := mergeKeyValueEqual(original[i], modifiedItem, mergeKey) + if err != nil || !equal { + return equal, err + } + } + return true, nil +} + +// diffListsOfScalars returns 2 lists, the first one is addList and the second one is deletionList. +// Argument diffOptions.IgnoreChangesAndAdditions controls if calculate addList. true means not calculate. +// Argument diffOptions.IgnoreDeletions controls if calculate deletionList. true means not calculate. +// original may be changed, but modified is guaranteed to not be changed +func diffListsOfScalars(original, modified []interface{}, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { + modifiedCopy := make([]interface{}, len(modified)) + copy(modifiedCopy, modified) + // Sort the scalars for easier calculating the diff + originalScalars := sortScalars(original) + modifiedScalars := sortScalars(modifiedCopy) + + originalIndex, modifiedIndex := 0, 0 + addList := []interface{}{} + deletionList := []interface{}{} + + for { + originalInBounds := originalIndex < len(originalScalars) + modifiedInBounds := modifiedIndex < len(modifiedScalars) + if !originalInBounds && !modifiedInBounds { + break + } + // we need to compare the string representation of the scalar, + // because the scalar is an interface which doesn't support either < or > + // And that's how func sortScalars compare scalars. + var originalString, modifiedString string + var originalValue, modifiedValue interface{} + if originalInBounds { + originalValue = originalScalars[originalIndex] + originalString = fmt.Sprintf("%v", originalValue) + } + if modifiedInBounds { + modifiedValue = modifiedScalars[modifiedIndex] + modifiedString = fmt.Sprintf("%v", modifiedValue) + } + + originalV, modifiedV := compareListValuesAtIndex(originalInBounds, modifiedInBounds, originalString, modifiedString) + switch { + case originalV == nil && modifiedV == nil: + originalIndex++ + modifiedIndex++ + case originalV != nil && modifiedV == nil: + if !diffOptions.IgnoreDeletions { + deletionList = append(deletionList, originalValue) + } + originalIndex++ + case originalV == nil && modifiedV != nil: + if !diffOptions.IgnoreChangesAndAdditions { + addList = append(addList, modifiedValue) + } + modifiedIndex++ + default: + return nil, nil, fmt.Errorf("Unexpected returned value from compareListValuesAtIndex: %v and %v", originalV, modifiedV) + } + } + + return addList, deduplicateScalars(deletionList), nil +} + +// If first return value is non-nil, list1 contains an element not present in list2 +// If second return value is non-nil, list2 contains an element not present in list1 +func compareListValuesAtIndex(list1Inbounds, list2Inbounds bool, list1Value, list2Value string) (interface{}, interface{}) { + bothInBounds := list1Inbounds && list2Inbounds + switch { + // scalars are identical + case bothInBounds && list1Value == list2Value: + return nil, nil + // only list2 is in bound + case !list1Inbounds: + fallthrough + // list2 has additional scalar + case bothInBounds && list1Value > list2Value: + return nil, list2Value + // only original is in bound + case !list2Inbounds: + fallthrough + // original has additional scalar + case bothInBounds && list1Value < list2Value: + return list1Value, nil + default: + return nil, nil + } +} + +// diffListsOfMaps takes a pair of lists and +// returns a (recursive) strategic merge patch list contains additions and changes and +// a deletion list contains deletions +func diffListsOfMaps(original, modified []interface{}, schema LookupPatchMeta, mergeKey string, diffOptions DiffOptions) ([]interface{}, []interface{}, error) { + patch := make([]interface{}, 0, len(modified)) + deletionList := make([]interface{}, 0, len(original)) + + originalSorted, err := sortMergeListsByNameArray(original, schema, mergeKey, false) + if err != nil { + return nil, nil, err + } + modifiedSorted, err := sortMergeListsByNameArray(modified, schema, mergeKey, false) + if err != nil { + return nil, nil, err + } + + originalIndex, modifiedIndex := 0, 0 + for { + originalInBounds := originalIndex < len(originalSorted) + modifiedInBounds := modifiedIndex < len(modifiedSorted) + bothInBounds := originalInBounds && modifiedInBounds + if !originalInBounds && !modifiedInBounds { + break + } + + var originalElementMergeKeyValueString, modifiedElementMergeKeyValueString string + var originalElementMergeKeyValue, modifiedElementMergeKeyValue interface{} + var originalElement, modifiedElement map[string]interface{} + if originalInBounds { + originalElement, originalElementMergeKeyValue, err = getMapAndMergeKeyValueByIndex(originalIndex, mergeKey, originalSorted) + if err != nil { + return nil, nil, err + } + originalElementMergeKeyValueString = fmt.Sprintf("%v", originalElementMergeKeyValue) + } + if modifiedInBounds { + modifiedElement, modifiedElementMergeKeyValue, err = getMapAndMergeKeyValueByIndex(modifiedIndex, mergeKey, modifiedSorted) + if err != nil { + return nil, nil, err + } + modifiedElementMergeKeyValueString = fmt.Sprintf("%v", modifiedElementMergeKeyValue) + } + + switch { + case bothInBounds && ItemMatchesOriginalAndModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString): + // Merge key values are equal, so recurse + patchValue, err := diffMaps(originalElement, modifiedElement, schema, diffOptions) + if err != nil { + return nil, nil, err + } + if len(patchValue) > 0 { + patchValue[mergeKey] = modifiedElementMergeKeyValue + patch = append(patch, patchValue) + } + originalIndex++ + modifiedIndex++ + // only modified is in bound + case !originalInBounds: + fallthrough + // modified has additional map + case bothInBounds && ItemAddedToModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString): + if !diffOptions.IgnoreChangesAndAdditions { + patch = append(patch, modifiedElement) + } + modifiedIndex++ + // only original is in bound + case !modifiedInBounds: + fallthrough + // original has additional map + case bothInBounds && ItemRemovedFromModifiedSlice(originalElementMergeKeyValueString, modifiedElementMergeKeyValueString): + if !diffOptions.IgnoreDeletions { + // Item was deleted, so add delete directive + deletionList = append(deletionList, CreateDeleteDirective(mergeKey, originalElementMergeKeyValue)) + } + originalIndex++ + } + } + + return patch, deletionList, nil +} + +// getMapAndMergeKeyValueByIndex return a map in the list and its merge key value given the index of the map. +func getMapAndMergeKeyValueByIndex(index int, mergeKey string, listOfMaps []interface{}) (map[string]interface{}, interface{}, error) { + m, ok := listOfMaps[index].(map[string]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(m, listOfMaps[index]) + } + + val, ok := m[mergeKey] + if !ok { + return nil, nil, mergepatch.ErrNoMergeKey(m, mergeKey) + } + return m, val, nil +} + +// StrategicMergePatch applies a strategic merge patch. The patch and the original document +// must be json encoded content. A patch can be created from an original and a modified document +// by calling CreateStrategicMergePatch. +func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte, error) { + schema, err := NewPatchMetaFromStruct(dataStruct) + if err != nil { + return nil, err + } + + return StrategicMergePatchUsingLookupPatchMeta(original, patch, schema) +} + +func StrategicMergePatchUsingLookupPatchMeta(original, patch []byte, schema LookupPatchMeta) ([]byte, error) { + originalMap, err := handleUnmarshal(original) + if err != nil { + return nil, err + } + patchMap, err := handleUnmarshal(patch) + if err != nil { + return nil, err + } + + result, err := StrategicMergeMapPatchUsingLookupPatchMeta(originalMap, patchMap, schema) + if err != nil { + return nil, err + } + + return json.Marshal(result) +} + +func handleUnmarshal(j []byte) (map[string]interface{}, error) { + if j == nil { + j = []byte("{}") + } + + m := map[string]interface{}{} + err := json.Unmarshal(j, &m) + if err != nil { + return nil, mergepatch.ErrBadJSONDoc + } + return m, nil +} + +// StrategicMergeMapPatch applies a strategic merge patch. The original and patch documents +// must be JSONMap. A patch can be created from an original and modified document by +// calling CreateTwoWayMergeMapPatch. +// Warning: the original and patch JSONMap objects are mutated by this function and should not be reused. +func StrategicMergeMapPatch(original, patch JSONMap, dataStruct interface{}) (JSONMap, error) { + schema, err := NewPatchMetaFromStruct(dataStruct) + if err != nil { + return nil, err + } + + // We need the go struct tags `patchMergeKey` and `patchStrategy` for fields that support a strategic merge patch. + // For native resources, we can easily figure out these tags since we know the fields. + + // Because custom resources are decoded as Unstructured and because we're missing the metadata about how to handle + // each field in a strategic merge patch, we can't find the go struct tags. Hence, we can't easily do a strategic merge + // for custom resources. So we should fail fast and return an error. + if _, ok := dataStruct.(*unstructured.Unstructured); ok { + return nil, mergepatch.ErrUnsupportedStrategicMergePatchFormat + } + + return StrategicMergeMapPatchUsingLookupPatchMeta(original, patch, schema) +} + +func StrategicMergeMapPatchUsingLookupPatchMeta(original, patch JSONMap, schema LookupPatchMeta) (JSONMap, error) { + mergeOptions := MergeOptions{ + MergeParallelList: true, + IgnoreUnmatchedNulls: true, + } + return mergeMap(original, patch, schema, mergeOptions) +} + +// MergeStrategicMergeMapPatchUsingLookupPatchMeta merges strategic merge +// patches retaining `null` fields and parallel lists. If 2 patches change the +// same fields and the latter one will override the former one. If you don't +// want that happen, you need to run func MergingMapsHaveConflicts before +// merging these patches. Applying the resulting merged merge patch to a JSONMap +// yields the same as merging each strategic merge patch to the JSONMap in +// succession. +func MergeStrategicMergeMapPatchUsingLookupPatchMeta(schema LookupPatchMeta, patches ...JSONMap) (JSONMap, error) { + mergeOptions := MergeOptions{ + MergeParallelList: false, + IgnoreUnmatchedNulls: false, + } + merged := JSONMap{} + var err error + for _, patch := range patches { + merged, err = mergeMap(merged, patch, schema, mergeOptions) + if err != nil { + return nil, err + } + } + return merged, nil +} + +// handleDirectiveInMergeMap handles the patch directive when merging 2 maps. +func handleDirectiveInMergeMap(directive interface{}, patch map[string]interface{}) (map[string]interface{}, error) { + if directive == replaceDirective { + // If the patch contains "$patch: replace", don't merge it, just use the + // patch directly. Later on, we can add a single level replace that only + // affects the map that the $patch is in. + delete(patch, directiveMarker) + return patch, nil + } + + if directive == deleteDirective { + // If the patch contains "$patch: delete", don't merge it, just return + // an empty map. + return map[string]interface{}{}, nil + } + + return nil, mergepatch.ErrBadPatchType(directive, patch) +} + +func containsDirectiveMarker(item interface{}) bool { + m, ok := item.(map[string]interface{}) + if ok { + if _, foundDirectiveMarker := m[directiveMarker]; foundDirectiveMarker { + return true + } + } + return false +} + +func mergeKeyValueEqual(left, right interface{}, mergeKey string) (bool, error) { + if len(mergeKey) == 0 { + return left == right, nil + } + typedLeft, ok := left.(map[string]interface{}) + if !ok { + return false, mergepatch.ErrBadArgType(typedLeft, left) + } + typedRight, ok := right.(map[string]interface{}) + if !ok { + return false, mergepatch.ErrBadArgType(typedRight, right) + } + mergeKeyLeft, ok := typedLeft[mergeKey] + if !ok { + return false, mergepatch.ErrNoMergeKey(typedLeft, mergeKey) + } + mergeKeyRight, ok := typedRight[mergeKey] + if !ok { + return false, mergepatch.ErrNoMergeKey(typedRight, mergeKey) + } + return mergeKeyLeft == mergeKeyRight, nil +} + +// extractKey trims the prefix and return the original key +func extractKey(s, prefix string) (string, error) { + substrings := strings.SplitN(s, "/", 2) + if len(substrings) <= 1 || substrings[0] != prefix { + switch prefix { + case deleteFromPrimitiveListDirectivePrefix: + return "", mergepatch.ErrBadPatchFormatForPrimitiveList + case setElementOrderDirectivePrefix: + return "", mergepatch.ErrBadPatchFormatForSetElementOrderList + default: + return "", fmt.Errorf("fail to find unknown prefix %q in %s\n", prefix, s) + } + } + return substrings[1], nil +} + +// validatePatchUsingSetOrderList verifies: +// the relative order of any two items in the setOrderList list matches that in the patch list. +// the items in the patch list must be a subset or the same as the $setElementOrder list (deletions are ignored). +func validatePatchWithSetOrderList(patchList, setOrderList interface{}, mergeKey string) error { + typedSetOrderList, ok := setOrderList.([]interface{}) + if !ok { + return mergepatch.ErrBadPatchFormatForSetElementOrderList + } + typedPatchList, ok := patchList.([]interface{}) + if !ok { + return mergepatch.ErrBadPatchFormatForSetElementOrderList + } + if len(typedSetOrderList) == 0 || len(typedPatchList) == 0 { + return nil + } + + var nonDeleteList []interface{} + var err error + if len(mergeKey) > 0 { + nonDeleteList, _, err = extractToDeleteItems(typedPatchList) + if err != nil { + return err + } + } else { + nonDeleteList = typedPatchList + } + + patchIndex, setOrderIndex := 0, 0 + for patchIndex < len(nonDeleteList) && setOrderIndex < len(typedSetOrderList) { + if containsDirectiveMarker(nonDeleteList[patchIndex]) { + patchIndex++ + continue + } + mergeKeyEqual, err := mergeKeyValueEqual(nonDeleteList[patchIndex], typedSetOrderList[setOrderIndex], mergeKey) + if err != nil { + return err + } + if mergeKeyEqual { + patchIndex++ + } + setOrderIndex++ + } + // If patchIndex is inbound but setOrderIndex if out of bound mean there are items mismatching between the patch list and setElementOrder list. + // the second check is a sanity check, and should always be true if the first is true. + if patchIndex < len(nonDeleteList) && setOrderIndex >= len(typedSetOrderList) { + return fmt.Errorf("The order in patch list:\n%v\n doesn't match %s list:\n%v\n", typedPatchList, setElementOrderDirectivePrefix, setOrderList) + } + return nil +} + +// preprocessDeletionListForMerging preprocesses the deletion list. +// it returns shouldContinue, isDeletionList, noPrefixKey +func preprocessDeletionListForMerging(key string, original map[string]interface{}, + patchVal interface{}, mergeDeletionList bool) (bool, bool, string, error) { + // If found a parallel list for deletion and we are going to merge the list, + // overwrite the key to the original key and set flag isDeleteList + foundParallelListPrefix := strings.HasPrefix(key, deleteFromPrimitiveListDirectivePrefix) + if foundParallelListPrefix { + if !mergeDeletionList { + original[key] = patchVal + return true, false, "", nil + } + originalKey, err := extractKey(key, deleteFromPrimitiveListDirectivePrefix) + return false, true, originalKey, err + } + return false, false, "", nil +} + +// applyRetainKeysDirective looks for a retainKeys directive and applies to original +// - if no directive exists do nothing +// - if directive is found, clear keys in original missing from the directive list +// - validate that all keys present in the patch are present in the retainKeys directive +// note: original may be another patch request, e.g. applying the add+modified patch to the deletions patch. In this case it may have directives +func applyRetainKeysDirective(original, patch map[string]interface{}, options MergeOptions) error { + retainKeysInPatch, foundInPatch := patch[retainKeysDirective] + if !foundInPatch { + return nil + } + // cleanup the directive + delete(patch, retainKeysDirective) + + if !options.MergeParallelList { + // If original is actually a patch, make sure the retainKeys directives are the same in both patches if present in both. + // If not present in the original patch, copy from the modified patch. + retainKeysInOriginal, foundInOriginal := original[retainKeysDirective] + if foundInOriginal { + if !reflect.DeepEqual(retainKeysInOriginal, retainKeysInPatch) { + // This error actually should never happen. + return fmt.Errorf("%v and %v are not deep equal: this may happen when calculating the 3-way diff patch", retainKeysInOriginal, retainKeysInPatch) + } + } else { + original[retainKeysDirective] = retainKeysInPatch + } + return nil + } + + retainKeysList, ok := retainKeysInPatch.([]interface{}) + if !ok { + return mergepatch.ErrBadPatchFormatForRetainKeys + } + + // validate patch to make sure all fields in the patch are present in the retainKeysList. + // The map is used only as a set, the value is never referenced + m := map[interface{}]struct{}{} + for _, v := range retainKeysList { + m[v] = struct{}{} + } + for k, v := range patch { + if v == nil || strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) || + strings.HasPrefix(k, setElementOrderDirectivePrefix) { + continue + } + // If there is an item present in the patch but not in the retainKeys list, + // the patch is invalid. + if _, found := m[k]; !found { + return mergepatch.ErrBadPatchFormatForRetainKeys + } + } + + // clear not present fields + for k := range original { + if _, found := m[k]; !found { + delete(original, k) + } + } + return nil +} + +// mergePatchIntoOriginal processes $setElementOrder list. +// When not merging the directive, it will make sure $setElementOrder list exist only in original. +// When merging the directive, it will try to find the $setElementOrder list and +// its corresponding patch list, validate it and merge it. +// Then, sort them by the relative order in setElementOrder, patch list and live list. +// The precedence is $setElementOrder > order in patch list > order in live list. +// This function will delete the item after merging it to prevent process it again in the future. +// Ref: https://git.k8s.io/design-proposals-archive/cli/preserve-order-in-strategic-merge-patch.md +func mergePatchIntoOriginal(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) error { + for key, patchV := range patch { + // Do nothing if there is no ordering directive + if !strings.HasPrefix(key, setElementOrderDirectivePrefix) { + continue + } + + setElementOrderInPatch := patchV + // Copies directive from the second patch (`patch`) to the first patch (`original`) + // and checks they are equal and delete the directive in the second patch + if !mergeOptions.MergeParallelList { + setElementOrderListInOriginal, ok := original[key] + if ok { + // check if the setElementOrder list in original and the one in patch matches + if !reflect.DeepEqual(setElementOrderListInOriginal, setElementOrderInPatch) { + return mergepatch.ErrBadPatchFormatForSetElementOrderList + } + } else { + // move the setElementOrder list from patch to original + original[key] = setElementOrderInPatch + } + } + delete(patch, key) + + var ( + ok bool + originalFieldValue, patchFieldValue, merged []interface{} + patchStrategy string + patchMeta PatchMeta + subschema LookupPatchMeta + ) + typedSetElementOrderList, ok := setElementOrderInPatch.([]interface{}) + if !ok { + return mergepatch.ErrBadArgType(typedSetElementOrderList, setElementOrderInPatch) + } + // Trim the setElementOrderDirectivePrefix to get the key of the list field in original. + originalKey, err := extractKey(key, setElementOrderDirectivePrefix) + if err != nil { + return err + } + // try to find the list with `originalKey` in `original` and `modified` and merge them. + originalList, foundOriginal := original[originalKey] + patchList, foundPatch := patch[originalKey] + if foundOriginal { + originalFieldValue, ok = originalList.([]interface{}) + if !ok { + return mergepatch.ErrBadArgType(originalFieldValue, originalList) + } + } + if foundPatch { + patchFieldValue, ok = patchList.([]interface{}) + if !ok { + return mergepatch.ErrBadArgType(patchFieldValue, patchList) + } + } + subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(originalKey) + if err != nil { + return err + } + _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) + if err != nil { + return err + } + // Check for consistency between the element order list and the field it applies to + err = validatePatchWithSetOrderList(patchFieldValue, typedSetElementOrderList, patchMeta.GetPatchMergeKey()) + if err != nil { + return err + } + + switch { + case foundOriginal && !foundPatch: + // no change to list contents + merged = originalFieldValue + case !foundOriginal && foundPatch: + // list was added + v, keep := removeDirectives(patchFieldValue) + if !keep { + // Shouldn't be possible since patchFieldValue is a slice + continue + } + + merged = v.([]interface{}) + case foundOriginal && foundPatch: + merged, err = mergeSliceHandler(originalList, patchList, subschema, + patchStrategy, patchMeta.GetPatchMergeKey(), false, mergeOptions) + if err != nil { + return err + } + case !foundOriginal && !foundPatch: + continue + } + + // Split all items into patch items and server-only items and then enforce the order. + var patchItems, serverOnlyItems []interface{} + if len(patchMeta.GetPatchMergeKey()) == 0 { + // Primitives doesn't need merge key to do partitioning. + patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, typedSetElementOrderList) + + } else { + // Maps need merge key to do partitioning. + patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, typedSetElementOrderList, patchMeta.GetPatchMergeKey()) + if err != nil { + return err + } + } + + elementType, err := sliceElementType(originalFieldValue, patchFieldValue) + if err != nil { + return err + } + kind := elementType.Kind() + // normalize merged list + // typedSetElementOrderList contains all the relative order in typedPatchList, + // so don't need to use typedPatchList + both, err := normalizeElementOrder(patchItems, serverOnlyItems, typedSetElementOrderList, originalFieldValue, patchMeta.GetPatchMergeKey(), kind) + if err != nil { + return err + } + original[originalKey] = both + // delete patch list from patch to prevent process again in the future + delete(patch, originalKey) + } + return nil +} + +// partitionPrimitivesByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not. +func partitionPrimitivesByPresentInList(original, partitionBy []interface{}) ([]interface{}, []interface{}) { + patch := make([]interface{}, 0, len(original)) + serverOnly := make([]interface{}, 0, len(original)) + inPatch := map[interface{}]bool{} + for _, v := range partitionBy { + inPatch[v] = true + } + for _, v := range original { + if !inPatch[v] { + serverOnly = append(serverOnly, v) + } else { + patch = append(patch, v) + } + } + return patch, serverOnly +} + +// partitionMapsByPresentInList partitions elements into 2 slices, the first containing items present in partitionBy, the other not. +func partitionMapsByPresentInList(original, partitionBy []interface{}, mergeKey string) ([]interface{}, []interface{}, error) { + patch := make([]interface{}, 0, len(original)) + serverOnly := make([]interface{}, 0, len(original)) + for _, v := range original { + typedV, ok := v.(map[string]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(typedV, v) + } + mergeKeyValue, foundMergeKey := typedV[mergeKey] + if !foundMergeKey { + return nil, nil, mergepatch.ErrNoMergeKey(typedV, mergeKey) + } + _, _, found, err := findMapInSliceBasedOnKeyValue(partitionBy, mergeKey, mergeKeyValue) + if err != nil { + return nil, nil, err + } + if !found { + serverOnly = append(serverOnly, v) + } else { + patch = append(patch, v) + } + } + return patch, serverOnly, nil +} + +// Removes directives from an object and returns value to use instead and whether +// or not the field/index should even be kept +// May modify input +func removeDirectives(obj interface{}) (interface{}, bool) { + if obj == nil { + return obj, true + } else if typedV, ok := obj.(map[string]interface{}); ok { + if _, hasDirective := typedV[directiveMarker]; hasDirective { + return nil, false + } + + for k, v := range typedV { + var keep bool + typedV[k], keep = removeDirectives(v) + if !keep { + delete(typedV, k) + } + } + return typedV, true + } else if typedV, ok := obj.([]interface{}); ok { + var res []interface{} + if typedV != nil { + // Make sure res is non-nil if patch is non-nil + res = []interface{}{} + } + for _, v := range typedV { + if newV, keep := removeDirectives(v); keep { + res = append(res, newV) + } + } + return res, true + } else { + return obj, true + } +} + +// Merge fields from a patch map into the original map. Note: This may modify +// both the original map and the patch because getting a deep copy of a map in +// golang is highly non-trivial. +// flag mergeOptions.MergeParallelList controls if using the parallel list to delete or keeping the list. +// If patch contains any null field (e.g. field_1: null) that is not +// present in original, then to propagate it to the end result use +// mergeOptions.IgnoreUnmatchedNulls == false. +func mergeMap(original, patch map[string]interface{}, schema LookupPatchMeta, mergeOptions MergeOptions) (map[string]interface{}, error) { + if v, ok := patch[directiveMarker]; ok { + return handleDirectiveInMergeMap(v, patch) + } + + // nil is an accepted value for original to simplify logic in other places. + // If original is nil, replace it with an empty map and then apply the patch. + if original == nil { + original = map[string]interface{}{} + } + + err := applyRetainKeysDirective(original, patch, mergeOptions) + if err != nil { + return nil, err + } + + // Process $setElementOrder list and other lists sharing the same key. + // When not merging the directive, it will make sure $setElementOrder list exist only in original. + // When merging the directive, it will process $setElementOrder and its patch list together. + // This function will delete the merged elements from patch so they will not be reprocessed + err = mergePatchIntoOriginal(original, patch, schema, mergeOptions) + if err != nil { + return nil, err + } + + // Start merging the patch into the original. + for k, patchV := range patch { + skipProcessing, isDeleteList, noPrefixKey, err := preprocessDeletionListForMerging(k, original, patchV, mergeOptions.MergeParallelList) + if err != nil { + return nil, err + } + if skipProcessing { + continue + } + if len(noPrefixKey) > 0 { + k = noPrefixKey + } + + // If the value of this key is null, delete the key if it exists in the + // original. Otherwise, check if we want to preserve it or skip it. + // Preserving the null value is useful when we want to send an explicit + // delete to the API server. + // In some cases, this may lead to inconsistent behavior with create. + // ref: https://github.com/kubernetes/kubernetes/issues/123304 + // To avoid breaking compatibility, + // we made corresponding changes on the client side to ensure that the create and patch behaviors are idempotent. + if patchV == nil { + delete(original, k) + if mergeOptions.IgnoreUnmatchedNulls { + continue + } + } + + _, ok := original[k] + if !ok { + if !isDeleteList { + // If it's not in the original document, just take the patch value. + if mergeOptions.IgnoreUnmatchedNulls { + discardNullValuesFromPatch(patchV) + } + original[k], ok = removeDirectives(patchV) + if !ok { + delete(original, k) + } + } + continue + } + + originalType := reflect.TypeOf(original[k]) + patchType := reflect.TypeOf(patchV) + if originalType != patchType { + if !isDeleteList { + if mergeOptions.IgnoreUnmatchedNulls { + discardNullValuesFromPatch(patchV) + } + original[k], ok = removeDirectives(patchV) + if !ok { + delete(original, k) + } + } + continue + } + // If they're both maps or lists, recurse into the value. + switch originalType.Kind() { + case reflect.Map: + subschema, patchMeta, err2 := schema.LookupPatchMetadataForStruct(k) + if err2 != nil { + return nil, err2 + } + _, patchStrategy, err2 := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) + if err2 != nil { + return nil, err2 + } + original[k], err = mergeMapHandler(original[k], patchV, subschema, patchStrategy, mergeOptions) + case reflect.Slice: + subschema, patchMeta, err2 := schema.LookupPatchMetadataForSlice(k) + if err2 != nil { + return nil, err2 + } + _, patchStrategy, err2 := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) + if err2 != nil { + return nil, err2 + } + original[k], err = mergeSliceHandler(original[k], patchV, subschema, patchStrategy, patchMeta.GetPatchMergeKey(), isDeleteList, mergeOptions) + default: + original[k], ok = removeDirectives(patchV) + if !ok { + // if patchV itself is a directive, then don't keep it + delete(original, k) + } + } + if err != nil { + return nil, err + } + } + return original, nil +} + +// discardNullValuesFromPatch discards all null property values from patch. +// It traverses all slices and map types. +func discardNullValuesFromPatch(patchV interface{}) { + switch patchV := patchV.(type) { + case map[string]interface{}: + for k, v := range patchV { + if v == nil { + delete(patchV, k) + } else { + discardNullValuesFromPatch(v) + } + } + case []interface{}: + for _, v := range patchV { + discardNullValuesFromPatch(v) + } + } +} + +// mergeMapHandler handles how to merge `patchV` whose key is `key` with `original` respecting +// fieldPatchStrategy and mergeOptions. +func mergeMapHandler(original, patch interface{}, schema LookupPatchMeta, + fieldPatchStrategy string, mergeOptions MergeOptions) (map[string]interface{}, error) { + typedOriginal, typedPatch, err := mapTypeAssertion(original, patch) + if err != nil { + return nil, err + } + + if fieldPatchStrategy != replaceDirective { + return mergeMap(typedOriginal, typedPatch, schema, mergeOptions) + } else { + return typedPatch, nil + } +} + +// mergeSliceHandler handles how to merge `patchV` whose key is `key` with `original` respecting +// fieldPatchStrategy, fieldPatchMergeKey, isDeleteList and mergeOptions. +func mergeSliceHandler(original, patch interface{}, schema LookupPatchMeta, + fieldPatchStrategy, fieldPatchMergeKey string, isDeleteList bool, mergeOptions MergeOptions) ([]interface{}, error) { + typedOriginal, typedPatch, err := sliceTypeAssertion(original, patch) + if err != nil { + return nil, err + } + + // Delete lists are handled the same way regardless of what the field's patch strategy is + if fieldPatchStrategy == mergeDirective || isDeleteList { + return mergeSlice(typedOriginal, typedPatch, schema, fieldPatchMergeKey, mergeOptions, isDeleteList) + } else { + return typedPatch, nil + } +} + +// Merge two slices together. Note: This may modify both the original slice and +// the patch because getting a deep copy of a slice in golang is highly +// non-trivial. +func mergeSlice(original, patch []interface{}, schema LookupPatchMeta, mergeKey string, mergeOptions MergeOptions, isDeleteList bool) ([]interface{}, error) { + if len(original) == 0 && len(patch) == 0 { + return original, nil + } + + // All the values must be of the same type, but not a list. + t, err := sliceElementType(original, patch) + if err != nil { + return nil, err + } + + var merged []interface{} + kind := t.Kind() + // If the elements are not maps, merge the slices of scalars. + if kind != reflect.Map { + if mergeOptions.MergeParallelList && isDeleteList { + return deleteFromSlice(original, patch), nil + } + // Maybe in the future add a "concat" mode that doesn't + // deduplicate. + both := append(original, patch...) + merged = deduplicateScalars(both) + + } else { + if mergeKey == "" { + return nil, fmt.Errorf("cannot merge lists without merge key for %s", schema.Name()) + } + + original, patch, err = mergeSliceWithSpecialElements(original, patch, mergeKey) + if err != nil { + return nil, err + } + + merged, err = mergeSliceWithoutSpecialElements(original, patch, mergeKey, schema, mergeOptions) + if err != nil { + return nil, err + } + } + + // enforce the order + var patchItems, serverOnlyItems []interface{} + if len(mergeKey) == 0 { + patchItems, serverOnlyItems = partitionPrimitivesByPresentInList(merged, patch) + } else { + patchItems, serverOnlyItems, err = partitionMapsByPresentInList(merged, patch, mergeKey) + if err != nil { + return nil, err + } + } + return normalizeElementOrder(patchItems, serverOnlyItems, patch, original, mergeKey, kind) +} + +// mergeSliceWithSpecialElements handles special elements with directiveMarker +// before merging the slices. It returns a updated `original` and a patch without special elements. +// original and patch must be slices of maps, they should be checked before calling this function. +func mergeSliceWithSpecialElements(original, patch []interface{}, mergeKey string) ([]interface{}, []interface{}, error) { + patchWithoutSpecialElements := []interface{}{} + replace := false + for _, v := range patch { + typedV := v.(map[string]interface{}) + patchType, ok := typedV[directiveMarker] + if !ok { + patchWithoutSpecialElements = append(patchWithoutSpecialElements, v) + } else { + switch patchType { + case deleteDirective: + mergeValue, ok := typedV[mergeKey] + if ok { + var err error + original, err = deleteMatchingEntries(original, mergeKey, mergeValue) + if err != nil { + return nil, nil, err + } + } else { + return nil, nil, mergepatch.ErrNoMergeKey(typedV, mergeKey) + } + case replaceDirective: + replace = true + // Continue iterating through the array to prune any other $patch elements. + case mergeDirective: + return nil, nil, fmt.Errorf("merging lists cannot yet be specified in the patch") + default: + return nil, nil, mergepatch.ErrBadPatchType(patchType, typedV) + } + } + } + if replace { + return patchWithoutSpecialElements, nil, nil + } + return original, patchWithoutSpecialElements, nil +} + +// delete all matching entries (based on merge key) from a merging list +func deleteMatchingEntries(original []interface{}, mergeKey string, mergeValue interface{}) ([]interface{}, error) { + for { + _, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue) + if err != nil { + return nil, err + } + + if !found { + break + } + // Delete the element at originalKey. + original = append(original[:originalKey], original[originalKey+1:]...) + } + return original, nil +} + +// mergeSliceWithoutSpecialElements merges slices with non-special elements. +// original and patch must be slices of maps, they should be checked before calling this function. +func mergeSliceWithoutSpecialElements(original, patch []interface{}, mergeKey string, schema LookupPatchMeta, mergeOptions MergeOptions) ([]interface{}, error) { + for _, v := range patch { + typedV := v.(map[string]interface{}) + mergeValue, ok := typedV[mergeKey] + if !ok { + return nil, mergepatch.ErrNoMergeKey(typedV, mergeKey) + } + + // If we find a value with this merge key value in original, merge the + // maps. Otherwise append onto original. + originalMap, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue) + if err != nil { + return nil, err + } + + if found { + var mergedMaps interface{} + var err error + // Merge into original. + mergedMaps, err = mergeMap(originalMap, typedV, schema, mergeOptions) + if err != nil { + return nil, err + } + + original[originalKey] = mergedMaps + } else { + original = append(original, v) + } + } + return original, nil +} + +// deleteFromSlice uses the parallel list to delete the items in a list of scalars +func deleteFromSlice(current, toDelete []interface{}) []interface{} { + toDeleteMap := map[interface{}]interface{}{} + processed := make([]interface{}, 0, len(current)) + for _, v := range toDelete { + toDeleteMap[v] = true + } + for _, v := range current { + if _, found := toDeleteMap[v]; !found { + processed = append(processed, v) + } + } + return processed +} + +// This method no longer panics if any element of the slice is not a map. +func findMapInSliceBasedOnKeyValue(m []interface{}, key string, value interface{}) (map[string]interface{}, int, bool, error) { + for k, v := range m { + typedV, ok := v.(map[string]interface{}) + if !ok { + return nil, 0, false, fmt.Errorf("value for key %v is not a map", k) + } + + valueToMatch, ok := typedV[key] + if ok && valueToMatch == value { + return typedV, k, true, nil + } + } + + return nil, 0, false, nil +} + +// This function takes a JSON map and sorts all the lists that should be merged +// by key. This is needed by tests because in JSON, list order is significant, +// but in Strategic Merge Patch, merge lists do not have significant order. +// Sorting the lists allows for order-insensitive comparison of patched maps. +func sortMergeListsByName(mapJSON []byte, schema LookupPatchMeta) ([]byte, error) { + var m map[string]interface{} + err := json.Unmarshal(mapJSON, &m) + if err != nil { + return nil, mergepatch.ErrBadJSONDoc + } + + newM, err := sortMergeListsByNameMap(m, schema) + if err != nil { + return nil, err + } + + return json.Marshal(newM) +} + +// Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in a map. +func sortMergeListsByNameMap(s map[string]interface{}, schema LookupPatchMeta) (map[string]interface{}, error) { + newS := map[string]interface{}{} + for k, v := range s { + if k == retainKeysDirective { + typedV, ok := v.([]interface{}) + if !ok { + return nil, mergepatch.ErrBadPatchFormatForRetainKeys + } + v = sortScalars(typedV) + } else if strings.HasPrefix(k, deleteFromPrimitiveListDirectivePrefix) { + typedV, ok := v.([]interface{}) + if !ok { + return nil, mergepatch.ErrBadPatchFormatForPrimitiveList + } + v = sortScalars(typedV) + } else if strings.HasPrefix(k, setElementOrderDirectivePrefix) { + _, ok := v.([]interface{}) + if !ok { + return nil, mergepatch.ErrBadPatchFormatForSetElementOrderList + } + } else if k != directiveMarker { + // recurse for map and slice. + switch typedV := v.(type) { + case map[string]interface{}: + subschema, _, err := schema.LookupPatchMetadataForStruct(k) + if err != nil { + return nil, err + } + v, err = sortMergeListsByNameMap(typedV, subschema) + if err != nil { + return nil, err + } + case []interface{}: + subschema, patchMeta, err := schema.LookupPatchMetadataForSlice(k) + if err != nil { + return nil, err + } + _, patchStrategy, err := extractRetainKeysPatchStrategy(patchMeta.GetPatchStrategies()) + if err != nil { + return nil, err + } + if patchStrategy == mergeDirective { + var err error + v, err = sortMergeListsByNameArray(typedV, subschema, patchMeta.GetPatchMergeKey(), true) + if err != nil { + return nil, err + } + } + } + } + + newS[k] = v + } + + return newS, nil +} + +// Function sortMergeListsByNameMap recursively sorts the merge lists by its mergeKey in an array. +func sortMergeListsByNameArray(s []interface{}, schema LookupPatchMeta, mergeKey string, recurse bool) ([]interface{}, error) { + if len(s) == 0 { + return s, nil + } + + // We don't support lists of lists yet. + t, err := sliceElementType(s) + if err != nil { + return nil, err + } + + // If the elements are not maps... + if t.Kind() != reflect.Map { + // Sort the elements, because they may have been merged out of order. + return deduplicateAndSortScalars(s), nil + } + + // Elements are maps - if one of the keys of the map is a map or a + // list, we may need to recurse into it. + newS := []interface{}{} + for _, elem := range s { + if recurse { + typedElem := elem.(map[string]interface{}) + newElem, err := sortMergeListsByNameMap(typedElem, schema) + if err != nil { + return nil, err + } + + newS = append(newS, newElem) + } else { + newS = append(newS, elem) + } + } + + // Sort the maps. + newS = sortMapsBasedOnField(newS, mergeKey) + return newS, nil +} + +func sortMapsBasedOnField(m []interface{}, fieldName string) []interface{} { + mapM := mapSliceFromSlice(m) + ss := SortableSliceOfMaps{mapM, fieldName} + sort.Sort(ss) + newS := sliceFromMapSlice(ss.s) + return newS +} + +func mapSliceFromSlice(m []interface{}) []map[string]interface{} { + newM := []map[string]interface{}{} + for _, v := range m { + vt := v.(map[string]interface{}) + newM = append(newM, vt) + } + + return newM +} + +func sliceFromMapSlice(s []map[string]interface{}) []interface{} { + newS := []interface{}{} + for _, v := range s { + newS = append(newS, v) + } + + return newS +} + +type SortableSliceOfMaps struct { + s []map[string]interface{} + k string // key to sort on +} + +func (ss SortableSliceOfMaps) Len() int { + return len(ss.s) +} + +func (ss SortableSliceOfMaps) Less(i, j int) bool { + iStr := fmt.Sprintf("%v", ss.s[i][ss.k]) + jStr := fmt.Sprintf("%v", ss.s[j][ss.k]) + return sort.StringsAreSorted([]string{iStr, jStr}) +} + +func (ss SortableSliceOfMaps) Swap(i, j int) { + ss.s[i], ss.s[j] = ss.s[j], ss.s[i] +} + +func deduplicateAndSortScalars(s []interface{}) []interface{} { + s = deduplicateScalars(s) + return sortScalars(s) +} + +func sortScalars(s []interface{}) []interface{} { + ss := SortableSliceOfScalars{s} + sort.Sort(ss) + return ss.s +} + +func deduplicateScalars(s []interface{}) []interface{} { + // Clever algorithm to deduplicate. + length := len(s) - 1 + for i := 0; i < length; i++ { + for j := i + 1; j <= length; j++ { + if s[i] == s[j] { + s[j] = s[length] + s = s[0:length] + length-- + j-- + } + } + } + + return s +} + +type SortableSliceOfScalars struct { + s []interface{} +} + +func (ss SortableSliceOfScalars) Len() int { + return len(ss.s) +} + +func (ss SortableSliceOfScalars) Less(i, j int) bool { + iStr := fmt.Sprintf("%v", ss.s[i]) + jStr := fmt.Sprintf("%v", ss.s[j]) + return sort.StringsAreSorted([]string{iStr, jStr}) +} + +func (ss SortableSliceOfScalars) Swap(i, j int) { + ss.s[i], ss.s[j] = ss.s[j], ss.s[i] +} + +// Returns the type of the elements of N slice(s). If the type is different, +// another slice or undefined, returns an error. +func sliceElementType(slices ...[]interface{}) (reflect.Type, error) { + var prevType reflect.Type + for _, s := range slices { + // Go through elements of all given slices and make sure they are all the same type. + for _, v := range s { + currentType := reflect.TypeOf(v) + if prevType == nil { + prevType = currentType + // We don't support lists of lists yet. + if prevType.Kind() == reflect.Slice { + return nil, mergepatch.ErrNoListOfLists + } + } else { + if prevType != currentType { + return nil, fmt.Errorf("list element types are not identical: %v", fmt.Sprint(slices)) + } + prevType = currentType + } + } + } + + if prevType == nil { + return nil, fmt.Errorf("no elements in any of the given slices") + } + + return prevType, nil +} + +// MergingMapsHaveConflicts returns true if the left and right JSON interface +// objects overlap with different values in any key. All keys are required to be +// strings. Since patches of the same Type have congruent keys, this is valid +// for multiple patch types. This method supports strategic merge patch semantics. +func MergingMapsHaveConflicts(left, right map[string]interface{}, schema LookupPatchMeta) (bool, error) { + return mergingMapFieldsHaveConflicts(left, right, schema, "", "") +} + +func mergingMapFieldsHaveConflicts( + left, right interface{}, + schema LookupPatchMeta, + fieldPatchStrategy, fieldPatchMergeKey string, +) (bool, error) { + switch leftType := left.(type) { + case map[string]interface{}: + rightType, ok := right.(map[string]interface{}) + if !ok { + return true, nil + } + leftMarker, okLeft := leftType[directiveMarker] + rightMarker, okRight := rightType[directiveMarker] + // if one or the other has a directive marker, + // then we need to consider that before looking at the individual keys, + // since a directive operates on the whole map. + if okLeft || okRight { + // if one has a directive marker and the other doesn't, + // then we have a conflict, since one is deleting or replacing the whole map, + // and the other is doing things to individual keys. + if okLeft != okRight { + return true, nil + } + // if they both have markers, but they are not the same directive, + // then we have a conflict because they're doing different things to the map. + if leftMarker != rightMarker { + return true, nil + } + } + if fieldPatchStrategy == replaceDirective { + return false, nil + } + // Check the individual keys. + return mapsHaveConflicts(leftType, rightType, schema) + + case []interface{}: + rightType, ok := right.([]interface{}) + if !ok { + return true, nil + } + return slicesHaveConflicts(leftType, rightType, schema, fieldPatchStrategy, fieldPatchMergeKey) + case string, float64, bool, int64, nil: + return !reflect.DeepEqual(left, right), nil + default: + return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left)) + } +} + +func mapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) { + for key, leftValue := range typedLeft { + if key != directiveMarker && key != retainKeysDirective { + if rightValue, ok := typedRight[key]; ok { + var subschema LookupPatchMeta + var patchMeta PatchMeta + var patchStrategy string + var err error + switch leftValue.(type) { + case []interface{}: + subschema, patchMeta, err = schema.LookupPatchMetadataForSlice(key) + if err != nil { + return true, err + } + _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies) + if err != nil { + return true, err + } + case map[string]interface{}: + subschema, patchMeta, err = schema.LookupPatchMetadataForStruct(key) + if err != nil { + return true, err + } + _, patchStrategy, err = extractRetainKeysPatchStrategy(patchMeta.patchStrategies) + if err != nil { + return true, err + } + } + + if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, + subschema, patchStrategy, patchMeta.GetPatchMergeKey()); hasConflicts { + return true, err + } + } + } + } + + return false, nil +} + +func slicesHaveConflicts( + typedLeft, typedRight []interface{}, + schema LookupPatchMeta, + fieldPatchStrategy, fieldPatchMergeKey string, +) (bool, error) { + elementType, err := sliceElementType(typedLeft, typedRight) + if err != nil { + return true, err + } + + if fieldPatchStrategy == mergeDirective { + // Merging lists of scalars have no conflicts by definition + // So we only need to check further if the elements are maps + if elementType.Kind() != reflect.Map { + return false, nil + } + + // Build a map for each slice and then compare the two maps + leftMap, err := sliceOfMapsToMapOfMaps(typedLeft, fieldPatchMergeKey) + if err != nil { + return true, err + } + + rightMap, err := sliceOfMapsToMapOfMaps(typedRight, fieldPatchMergeKey) + if err != nil { + return true, err + } + + return mapsOfMapsHaveConflicts(leftMap, rightMap, schema) + } + + // Either we don't have type information, or these are non-merging lists + if len(typedLeft) != len(typedRight) { + return true, nil + } + + // Sort scalar slices to prevent ordering issues + // We have no way to sort non-merging lists of maps + if elementType.Kind() != reflect.Map { + typedLeft = deduplicateAndSortScalars(typedLeft) + typedRight = deduplicateAndSortScalars(typedRight) + } + + // Compare the slices element by element in order + // This test will fail if the slices are not sorted + for i := range typedLeft { + if hasConflicts, err := mergingMapFieldsHaveConflicts(typedLeft[i], typedRight[i], schema, "", ""); hasConflicts { + return true, err + } + } + + return false, nil +} + +func sliceOfMapsToMapOfMaps(slice []interface{}, mergeKey string) (map[string]interface{}, error) { + result := make(map[string]interface{}, len(slice)) + for _, value := range slice { + typedValue, ok := value.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("invalid element type in merging list:%v", slice) + } + + mergeValue, ok := typedValue[mergeKey] + if !ok { + return nil, fmt.Errorf("cannot find merge key `%s` in merging list element:%v", mergeKey, typedValue) + } + + result[fmt.Sprintf("%s", mergeValue)] = typedValue + } + + return result, nil +} + +func mapsOfMapsHaveConflicts(typedLeft, typedRight map[string]interface{}, schema LookupPatchMeta) (bool, error) { + for key, leftValue := range typedLeft { + if rightValue, ok := typedRight[key]; ok { + if hasConflicts, err := mergingMapFieldsHaveConflicts(leftValue, rightValue, schema, "", ""); hasConflicts { + return true, err + } + } + } + + return false, nil +} + +// CreateThreeWayMergePatch reconciles a modified configuration with an original configuration, +// while preserving any changes or deletions made to the original configuration in the interim, +// and not overridden by the current configuration. All three documents must be passed to the +// method as json encoded content. It will return a strategic merge patch, or an error if any +// of the documents is invalid, or if there are any preconditions that fail against the modified +// configuration, or, if overwrite is false and there are conflicts between the modified and current +// configurations. Conflicts are defined as keys changed differently from original to modified +// than from original to current. In other words, a conflict occurs if modified changes any key +// in a way that is different from how it is changed in current (e.g., deleting it, changing its +// value). We also propagate values fields that do not exist in original but are explicitly +// defined in modified. +func CreateThreeWayMergePatch(original, modified, current []byte, schema LookupPatchMeta, overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) { + originalMap := map[string]interface{}{} + if len(original) > 0 { + if err := json.Unmarshal(original, &originalMap); err != nil { + return nil, mergepatch.ErrBadJSONDoc + } + } + + modifiedMap := map[string]interface{}{} + if len(modified) > 0 { + if err := json.Unmarshal(modified, &modifiedMap); err != nil { + return nil, mergepatch.ErrBadJSONDoc + } + } + + currentMap := map[string]interface{}{} + if len(current) > 0 { + if err := json.Unmarshal(current, ¤tMap); err != nil { + return nil, mergepatch.ErrBadJSONDoc + } + } + + // The patch is the difference from current to modified without deletions, plus deletions + // from original to modified. To find it, we compute deletions, which are the deletions from + // original to modified, and delta, which is the difference from current to modified without + // deletions, and then apply delta to deletions as a patch, which should be strictly additive. + deltaMapDiffOptions := DiffOptions{ + IgnoreDeletions: true, + SetElementOrder: true, + } + deltaMap, err := diffMaps(currentMap, modifiedMap, schema, deltaMapDiffOptions) + if err != nil { + return nil, err + } + deletionsMapDiffOptions := DiffOptions{ + SetElementOrder: true, + IgnoreChangesAndAdditions: true, + } + deletionsMap, err := diffMaps(originalMap, modifiedMap, schema, deletionsMapDiffOptions) + if err != nil { + return nil, err + } + + mergeOptions := MergeOptions{} + patchMap, err := mergeMap(deletionsMap, deltaMap, schema, mergeOptions) + if err != nil { + return nil, err + } + + // Apply the preconditions to the patch, and return an error if any of them fail. + for _, fn := range fns { + if !fn(patchMap) { + return nil, mergepatch.NewErrPreconditionFailed(patchMap) + } + } + + // If overwrite is false, and the patch contains any keys that were changed differently, + // then return a conflict error. + if !overwrite { + changeMapDiffOptions := DiffOptions{} + changedMap, err := diffMaps(originalMap, currentMap, schema, changeMapDiffOptions) + if err != nil { + return nil, err + } + + hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, schema) + if err != nil { + return nil, err + } + + if hasConflicts { + return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(patchMap), mergepatch.ToYAMLOrError(changedMap)) + } + } + + return json.Marshal(patchMap) +} + +func ItemAddedToModifiedSlice(original, modified string) bool { return original > modified } + +func ItemRemovedFromModifiedSlice(original, modified string) bool { return original < modified } + +func ItemMatchesOriginalAndModifiedSlice(original, modified string) bool { return original == modified } + +func CreateDeleteDirective(mergeKey string, mergeKeyValue interface{}) map[string]interface{} { + return map[string]interface{}{mergeKey: mergeKeyValue, directiveMarker: deleteDirective} +} + +func mapTypeAssertion(original, patch interface{}) (map[string]interface{}, map[string]interface{}, error) { + typedOriginal, ok := original.(map[string]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(typedOriginal, original) + } + typedPatch, ok := patch.(map[string]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(typedPatch, patch) + } + return typedOriginal, typedPatch, nil +} + +func sliceTypeAssertion(original, patch interface{}) ([]interface{}, []interface{}, error) { + typedOriginal, ok := original.([]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(typedOriginal, original) + } + typedPatch, ok := patch.([]interface{}) + if !ok { + return nil, nil, mergepatch.ErrBadArgType(typedPatch, patch) + } + return typedOriginal, typedPatch, nil +} + +// extractRetainKeysPatchStrategy process patch strategy, which is a string may contains multiple +// patch strategies separated by ",". It returns a boolean var indicating if it has +// retainKeys strategies and a string for the other strategy. +func extractRetainKeysPatchStrategy(strategies []string) (bool, string, error) { + switch len(strategies) { + case 0: + return false, "", nil + case 1: + singleStrategy := strategies[0] + switch singleStrategy { + case retainKeysStrategy: + return true, "", nil + default: + return false, singleStrategy, nil + } + case 2: + switch { + case strategies[0] == retainKeysStrategy: + return true, strategies[1], nil + case strategies[1] == retainKeysStrategy: + return true, strategies[0], nil + default: + return false, "", fmt.Errorf("unexpected patch strategy: %v", strategies) + } + default: + return false, "", fmt.Errorf("unexpected patch strategy: %v", strategies) + } +} + +// hasAdditionalNewField returns if original map has additional key with non-nil value than modified. +func hasAdditionalNewField(original, modified map[string]interface{}) bool { + for k, v := range original { + if v == nil { + continue + } + if _, found := modified[k]; !found { + return true + } + } + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go new file mode 100644 index 0000000000..fe88937c57 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go @@ -0,0 +1,7061 @@ +/* +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 strategicpatch + +import ( + "fmt" + "path/filepath" + "reflect" + "strings" + "testing" + + "sigs.k8s.io/yaml" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/apimachinery/pkg/util/mergepatch" + "k8s.io/apimachinery/pkg/util/sets" + sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing" + "k8s.io/utils/dump" +) + +var ( + fakeMergeItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-merge-item.json")} + fakePrecisionItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-precision-item.json")} + + fakeMergeItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-merge-item-v3.json")} + fakePrecisionItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-precision-item-v3.json")} +) + +type SortMergeListTestCases struct { + TestCases []SortMergeListTestCase +} + +type SortMergeListTestCase struct { + Description string + Original map[string]interface{} + Sorted map[string]interface{} +} + +type StrategicMergePatchTestCases struct { + TestCases []StrategicMergePatchTestCase +} + +type StrategicMergePatchTestCase struct { + Description string + StrategicMergePatchTestCaseData +} + +type StrategicMergePatchRawTestCase struct { + Description string + StrategicMergePatchRawTestCaseData +} + +type StrategicMergePatchTestCaseData struct { + // Original is the original object (last-applied config in annotation) + Original map[string]interface{} + // Modified is the modified object (new config we want) + Modified map[string]interface{} + // Current is the current object (live config in the server) + Current map[string]interface{} + // TwoWay is the expected two-way merge patch diff between original and modified + TwoWay map[string]interface{} + // ThreeWay is the expected three-way merge patch + ThreeWay map[string]interface{} + // Result is the expected object after applying the three-way patch on current object. + Result map[string]interface{} + // TwoWayResult is the expected object after applying the two-way patch on current object. + // If nil, Modified is used. + TwoWayResult map[string]interface{} +} + +// The meaning of each field is the same as StrategicMergePatchTestCaseData's. +// The difference is that all the fields in StrategicMergePatchRawTestCaseData are json-encoded data. +type StrategicMergePatchRawTestCaseData struct { + Original []byte + Modified []byte + Current []byte + TwoWay []byte + ThreeWay []byte + Result []byte + TwoWayResult []byte + ExpectedError string +} + +type MergeItem struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Other string `json:"other,omitempty"` + MergingList []MergeItem `json:"mergingList,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + NonMergingList []MergeItem `json:"nonMergingList,omitempty"` + MergingIntList []int `json:"mergingIntList,omitempty" patchStrategy:"merge"` + NonMergingIntList []int `json:"nonMergingIntList,omitempty"` + MergeItemPtr *MergeItem `json:"mergeItemPtr,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + SimpleMap map[string]string `json:"simpleMap,omitempty"` + ReplacingItem runtime.RawExtension `json:"replacingItem,omitempty" patchStrategy:"replace"` + JSONItem struct{ Raw []byte } `json:"jsonItem,omitempty"` + RetainKeysMap RetainKeysMergeItem `json:"retainKeysMap,omitempty" patchStrategy:"retainKeys"` + RetainKeysMergingList []MergeItem `json:"retainKeysMergingList,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` +} + +type RetainKeysMergeItem struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Other string `json:"other,omitempty"` + SimpleMap map[string]string `json:"simpleMap,omitempty"` + MergingIntList []int `json:"mergingIntList,omitempty" patchStrategy:"merge"` + MergingList []MergeItem `json:"mergingList,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + NonMergingList []MergeItem `json:"nonMergingList,omitempty"` +} + +var ( + mergeItem MergeItem + mergeItemStructSchema = PatchMetaFromStruct{T: GetTagStructTypeOrDie(mergeItem)} +) + +// These are test cases for SortMergeList, used to assert that it (recursively) +// sorts both merging and non merging lists correctly. +var sortMergeListTestCaseData = []byte(` +testCases: + - description: sort one list of maps + original: + mergingList: + - name: 1 + - name: 3 + - name: 2 + sorted: + mergingList: + - name: 1 + - name: 2 + - name: 3 + - description: sort lists of maps but not nested lists of maps + original: + mergingList: + - name: 2 + nonMergingList: + - name: 1 + - name: 3 + - name: 2 + - name: 1 + nonMergingList: + - name: 2 + - name: 1 + sorted: + mergingList: + - name: 1 + nonMergingList: + - name: 2 + - name: 1 + - name: 2 + nonMergingList: + - name: 1 + - name: 3 + - name: 2 + - description: sort lists of maps and nested lists of maps + original: + mergingList: + - name: 2 + mergingList: + - name: 1 + - name: 3 + - name: 2 + - name: 1 + mergingList: + - name: 2 + - name: 1 + sorted: + mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + - name: 2 + mergingList: + - name: 1 + - name: 2 + - name: 3 + - description: merging list should NOT sort when nested in non merging list + original: + nonMergingList: + - name: 2 + mergingList: + - name: 1 + - name: 3 + - name: 2 + - name: 1 + mergingList: + - name: 2 + - name: 1 + sorted: + nonMergingList: + - name: 2 + mergingList: + - name: 1 + - name: 3 + - name: 2 + - name: 1 + mergingList: + - name: 2 + - name: 1 + - description: sort very nested list of maps + fieldTypes: + original: + mergingList: + - mergingList: + - mergingList: + - name: 2 + - name: 1 + sorted: + mergingList: + - mergingList: + - mergingList: + - name: 1 + - name: 2 + - description: sort nested lists of ints + original: + mergingList: + - name: 2 + mergingIntList: + - 1 + - 3 + - 2 + - name: 1 + mergingIntList: + - 2 + - 1 + sorted: + mergingList: + - name: 1 + mergingIntList: + - 1 + - 2 + - name: 2 + mergingIntList: + - 1 + - 2 + - 3 + - description: sort nested pointers of ints + original: + mergeItemPtr: + - name: 2 + mergingIntList: + - 1 + - 3 + - 2 + - name: 1 + mergingIntList: + - 2 + - 1 + sorted: + mergeItemPtr: + - name: 1 + mergingIntList: + - 1 + - 2 + - name: 2 + mergingIntList: + - 1 + - 2 + - 3 + - description: sort merging list by pointer + original: + mergeItemPtr: + - name: 1 + - name: 3 + - name: 2 + sorted: + mergeItemPtr: + - name: 1 + - name: 2 + - name: 3 +`) + +func TestSortMergeLists(t *testing.T) { + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), + } + mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ + SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, + Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + mergeItemOpenapiV3Schema, + } + + tc := SortMergeListTestCases{} + err := yaml.Unmarshal(sortMergeListTestCaseData, &tc) + if err != nil { + t.Errorf("can't unmarshal test cases: %s\n", err) + return + } + + for _, schema := range schemas { + for _, c := range tc.TestCases { + temp := testObjectToJSONOrFail(t, c.Original) + got := sortJsonOrFail(t, temp, c.Description, schema) + expected := testObjectToJSONOrFail(t, c.Sorted) + if !reflect.DeepEqual(got, expected) { + t.Errorf("using %s error in test case: %s\ncannot sort object:\n%s\nexpected:\n%s\ngot:\n%s\n", + getSchemaType(schema), c.Description, mergepatch.ToYAMLOrError(c.Original), mergepatch.ToYAMLOrError(c.Sorted), jsonToYAMLOrError(got)) + } + } + } +} + +// These are test cases for StrategicMergePatch that cannot be generated using +// CreateTwoWayMergePatch because it may be one of the following cases: +// - not use the replace directive. +// - generate duplicate integers for a merging list patch. +// - generate empty merging lists. +// - use patch format from an old client. +var customStrategicMergePatchTestCaseData = []byte(` +testCases: + - description: unique scalars when merging lists + original: + mergingIntList: + - 1 + - 2 + twoWay: + mergingIntList: + - 2 + - 3 + modified: + mergingIntList: + - 1 + - 2 + - 3 + - description: delete map from nested map + original: + simpleMap: + key1: 1 + key2: 1 + twoWay: + simpleMap: + $patch: delete + modified: + simpleMap: + {} + - description: delete all items from merging list + original: + mergingList: + - name: 1 + - name: 2 + twoWay: + mergingList: + - $patch: replace + modified: + mergingList: [] + - description: merge empty merging lists + original: + mergingList: [] + twoWay: + mergingList: [] + modified: + mergingList: [] + - description: delete all keys from map + original: + name: 1 + value: 1 + twoWay: + $patch: replace + modified: {} + - description: add key and delete all keys from map + original: + name: 1 + value: 1 + twoWay: + other: a + $patch: replace + modified: + other: a + - description: delete all duplicate entries in a merging list + original: + mergingList: + - name: 1 + - name: 1 + - name: 2 + value: a + - name: 3 + - name: 3 + twoWay: + mergingList: + - name: 1 + $patch: delete + - name: 3 + $patch: delete + modified: + mergingList: + - name: 2 + value: a + - description: retainKeys map can add a field when no retainKeys directive present + original: + retainKeysMap: + name: foo + twoWay: + retainKeysMap: + value: bar + modified: + retainKeysMap: + name: foo + value: bar + - description: retainKeys map can change a field when no retainKeys directive present + original: + retainKeysMap: + name: foo + value: a + twoWay: + retainKeysMap: + value: b + modified: + retainKeysMap: + name: foo + value: b + - description: retainKeys map can delete a field when no retainKeys directive present + original: + retainKeysMap: + name: foo + value: a + twoWay: + retainKeysMap: + value: null + modified: + retainKeysMap: + name: foo + - description: retainKeys map merge an empty map + original: + retainKeysMap: + name: foo + value: a + twoWay: + retainKeysMap: {} + modified: + retainKeysMap: + name: foo + value: a + - description: retainKeys list can add a field when no retainKeys directive present + original: + retainKeysMergingList: + - name: bar + - name: foo + twoWay: + retainKeysMergingList: + - name: foo + value: a + modified: + retainKeysMergingList: + - name: bar + - name: foo + value: a + - description: retainKeys list can change a field when no retainKeys directive present + original: + retainKeysMergingList: + - name: bar + - name: foo + value: a + twoWay: + retainKeysMergingList: + - name: foo + value: b + modified: + retainKeysMergingList: + - name: bar + - name: foo + value: b + - description: retainKeys list can delete a field when no retainKeys directive present + original: + retainKeysMergingList: + - name: bar + - name: foo + value: a + twoWay: + retainKeysMergingList: + - name: foo + value: null + modified: + retainKeysMergingList: + - name: bar + - name: foo + - description: preserve the order from the patch in a merging list + original: + mergingList: + - name: 1 + - name: 2 + value: b + - name: 3 + twoWay: + mergingList: + - name: 3 + value: c + - name: 1 + value: a + - name: 2 + other: x + modified: + mergingList: + - name: 3 + value: c + - name: 1 + value: a + - name: 2 + value: b + other: x + - description: preserve the order from the patch in a merging list 2 + original: + mergingList: + - name: 1 + - name: 2 + value: b + - name: 3 + twoWay: + mergingList: + - name: 3 + value: c + - name: 1 + value: a + modified: + mergingList: + - name: 2 + value: b + - name: 3 + value: c + - name: 1 + value: a + - description: preserve the order from the patch in a merging int list + original: + mergingIntList: + - 1 + - 2 + - 3 + twoWay: + mergingIntList: + - 3 + - 1 + - 2 + modified: + mergingIntList: + - 3 + - 1 + - 2 + - description: preserve the order from the patch in a merging int list + original: + mergingIntList: + - 1 + - 2 + - 3 + twoWay: + mergingIntList: + - 3 + - 1 + modified: + mergingIntList: + - 2 + - 3 + - 1 +`) + +var customStrategicMergePatchRawTestCases = []StrategicMergePatchRawTestCase{ + { + Description: "$setElementOrder contains item that is not present in the list to be merged", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 3 + - name: 2 + - name: 1 +mergingList: + - name: 3 + value: 3 + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 3 + value: 3 + - name: 1 + value: 1 +`), + }, + }, + { + Description: "$setElementOrder contains item that is not present in the int list to be merged", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 3 + - 2 + - 1 +`), + Modified: []byte(` +mergingIntList: + - 3 + - 1 +`), + }, + }, + { + Description: "should check if order in $setElementOrder and patch list match", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 3 + value: 3 + - name: 1 + value: 1 +`), + ExpectedError: "doesn't match", + }, + }, + { + Description: "$setElementOrder contains item that is not present in the int list to be merged", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 + - 3 +mergingIntList: + - 3 + - 1 +`), + ExpectedError: "doesn't match", + }, + }, + { + Description: "missing merge key should error out", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: a +`), + TwoWay: []byte(` +mergingList: + - value: b +`), + ExpectedError: "does not contain declared merge key", + }, + }, + { + Description: "$deleteFromPrimitiveList of nonexistent item in primitive list should not add the item to the list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 +`), + TwoWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 +`), + }, + }, + { + Description: "$deleteFromPrimitiveList on empty primitive list should not add the item to the list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +`), + TwoWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: +`), + }, + }, + { + Description: "$deleteFromPrimitiveList on nonexistent primitive list should not add the primitive list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +foo: + - bar +`), + TwoWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +foo: + - bar +`), + }, + }, + { + Description: "$deleteFromPrimitiveList should delete item from a list with merge patch strategy", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + TwoWay: []byte(` +$deleteFromPrimitiveList/mergingIntList: + - 2 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 3 +`), + }, + }, + { + Description: "$deleteFromPrimitiveList should delete item from a list without merge patch strategy", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +nonMergingIntList: + - 1 + - 2 + - 3 +`), + TwoWay: []byte(` +$deleteFromPrimitiveList/nonMergingIntList: + - 2 +`), + Modified: []byte(` +nonMergingIntList: + - 1 + - 3 +`), + }, + }, +} + +func TestCustomStrategicMergePatch(t *testing.T) { + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), + } + mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ + SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, + Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + mergeItemOpenapiV3Schema, + } + + tc := StrategicMergePatchTestCases{} + err := yaml.Unmarshal(customStrategicMergePatchTestCaseData, &tc) + if err != nil { + t.Errorf("can't unmarshal test cases: %v\n", err) + return + } + + for _, c := range tc.TestCases { + t.Run(c.Description, func(t *testing.T) { + for _, schema := range schemas { + t.Run(schema.Name(), func(t *testing.T) { + original, expectedTwoWayPatch, _, expectedResult := twoWayTestCaseToJSONOrFail(t, c, schema) + testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, "", schema) + }) + + for _, c := range customStrategicMergePatchRawTestCases { + original, expectedTwoWayPatch, _, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) + testPatchApplication(t, original, expectedTwoWayPatch, expectedResult, c.Description, c.ExpectedError, schema) + } + } + }) + } +} + +// These are test cases for StrategicMergePatch, to assert that applying a patch +// yields the correct outcome. They are also test cases for CreateTwoWayMergePatch +// and CreateThreeWayMergePatch, to assert that they both generate the correct patch +// for the given set of input documents. +var createStrategicMergePatchTestCaseData = []byte(` +testCases: + - description: nil original + twoWay: + name: 1 + value: 1 + modified: + name: 1 + value: 1 + current: + name: 1 + other: a + threeWay: + value: 1 + result: + name: 1 + value: 1 + other: a + - description: nil patch + original: + name: 1 + twoWay: + {} + modified: + name: 1 + current: + name: 1 + threeWay: + {} + result: + name: 1 + - description: add field to map + original: + name: 1 + twoWay: + value: 1 + modified: + name: 1 + value: 1 + current: + name: 1 + other: a + threeWay: + value: 1 + result: + name: 1 + value: 1 + other: a + - description: add field to map with conflict + original: + name: 1 + twoWay: + value: 1 + modified: + name: 1 + value: 1 + current: + name: a + other: a + threeWay: + name: 1 + value: 1 + result: + name: 1 + value: 1 + other: a + - description: add field and delete field from map + original: + name: 1 + twoWay: + name: null + value: 1 + modified: + value: 1 + current: + name: 1 + other: a + threeWay: + name: null + value: 1 + result: + value: 1 + other: a + - description: add field and delete field from map with conflict + original: + name: 1 + twoWay: + name: null + value: 1 + modified: + value: 1 + current: + name: a + other: a + threeWay: + name: null + value: 1 + result: + value: 1 + other: a + - description: delete field from nested map + original: + simpleMap: + key1: 1 + key2: 1 + twoWay: + simpleMap: + key2: null + modified: + simpleMap: + key1: 1 + current: + simpleMap: + key1: 1 + key2: 1 + other: a + threeWay: + simpleMap: + key2: null + result: + simpleMap: + key1: 1 + other: a + - description: delete field from nested map with conflict + original: + simpleMap: + key1: 1 + key2: 1 + twoWay: + simpleMap: + key2: null + modified: + simpleMap: + key1: 1 + current: + simpleMap: + key1: a + key2: 1 + other: a + threeWay: + simpleMap: + key1: 1 + key2: null + result: + simpleMap: + key1: 1 + other: a + - description: delete all fields from map + original: + name: 1 + value: 1 + twoWay: + name: null + value: null + modified: {} + current: + name: 1 + value: 1 + other: a + threeWay: + name: null + value: null + result: + other: a + - description: delete all fields from map with conflict + original: + name: 1 + value: 1 + twoWay: + name: null + value: null + modified: {} + current: + name: 1 + value: a + other: a + threeWay: + name: null + value: null + result: + other: a + - description: add field and delete all fields from map + original: + name: 1 + value: 1 + twoWay: + name: null + value: null + other: a + modified: + other: a + current: + name: 1 + value: 1 + other: a + threeWay: + name: null + value: null + result: + other: a + - description: add field and delete all fields from map with conflict + original: + name: 1 + value: 1 + twoWay: + name: null + value: null + other: a + modified: + other: a + current: + name: 1 + value: 1 + other: b + threeWay: + name: null + value: null + other: a + result: + other: a + - description: replace list of scalars + original: + nonMergingIntList: + - 1 + - 2 + twoWay: + nonMergingIntList: + - 2 + - 3 + modified: + nonMergingIntList: + - 2 + - 3 + current: + nonMergingIntList: + - 1 + - 2 + threeWay: + nonMergingIntList: + - 2 + - 3 + result: + nonMergingIntList: + - 2 + - 3 + - description: replace list of scalars with conflict + original: + nonMergingIntList: + - 1 + - 2 + twoWay: + nonMergingIntList: + - 2 + - 3 + modified: + nonMergingIntList: + - 2 + - 3 + current: + nonMergingIntList: + - 1 + - 4 + threeWay: + nonMergingIntList: + - 2 + - 3 + result: + nonMergingIntList: + - 2 + - 3 + - description: delete all maps from merging list + original: + mergingList: + - name: 1 + - name: 2 + twoWay: + mergingList: + - name: 1 + $patch: delete + - name: 2 + $patch: delete + modified: + mergingList: [] + current: + mergingList: + - name: 1 + - name: 2 + threeWay: + mergingList: + - name: 1 + $patch: delete + - name: 2 + $patch: delete + result: + mergingList: [] + - description: delete all maps from merging list with conflict + original: + mergingList: + - name: 1 + - name: 2 + twoWay: + mergingList: + - name: 1 + $patch: delete + - name: 2 + $patch: delete + modified: + mergingList: [] + current: + mergingList: + - name: 1 + other: a + - name: 2 + other: b + threeWay: + mergingList: + - name: 1 + $patch: delete + - name: 2 + $patch: delete + result: + mergingList: [] + - description: delete all maps from empty merging list + original: + mergingList: + - name: 1 + - name: 2 + twoWay: + mergingList: + - name: 1 + $patch: delete + - name: 2 + $patch: delete + modified: + mergingList: [] + current: + mergingList: [] + threeWay: + mergingList: + - name: 1 + $patch: delete + - name: 2 + $patch: delete + result: + mergingList: [] + - description: merge empty merging lists + original: + mergingList: [] + twoWay: + {} + modified: + mergingList: [] + current: + mergingList: [] + threeWay: + {} + result: + mergingList: [] + - description: defined null values should propagate overwrite current fields (with conflict) + original: + name: 2 + twoWay: + name: 1 + value: 1 + other: null + twoWayResult: + name: 1 + value: 1 + modified: + name: 1 + value: 1 + other: null + current: + name: a + other: a + threeWay: + name: 1 + value: 1 + other: null + result: + name: 1 + value: 1 + - description: defined null values should propagate removing original fields + original: + name: original-name + value: original-value + current: + name: original-name + value: original-value + other: current-other + modified: + name: modified-name + value: null + twoWay: + name: modified-name + value: null + twoWayResult: + name: modified-name + threeWay: + name: modified-name + value: null + result: + name: modified-name + other: current-other + - description: nil patch with retainKeys map + original: + name: a + retainKeysMap: + name: foo + current: + name: a + value: b + retainKeysMap: + name: foo + modified: + name: a + retainKeysMap: + name: foo + twoWay: {} + threeWay: {} + result: + name: a + value: b + retainKeysMap: + name: foo + - description: retainKeys map with no change should not be present + original: + name: a + retainKeysMap: + name: foo + current: + name: a + other: c + retainKeysMap: + name: foo + modified: + name: a + value: b + retainKeysMap: + name: foo + twoWay: + value: b + threeWay: + value: b + result: + name: a + value: b + other: c + retainKeysMap: + name: foo +`) + +var strategicMergePatchRawTestCases = []StrategicMergePatchRawTestCase{ + { + Description: "nested patch merge with empty list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +name: hi +`), + Current: []byte(` +name: hi +mergingList: +- name: hello2 +`), + Modified: []byte(` +name: hi +mergingList: +- name: hello +- $patch: delete + name: doesntexist +`), + TwoWay: []byte(` +mergingList: +- name: hello +- $patch: delete + name: doesntexist +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: +- name: hello +- name: doesntexist +mergingList: +- name: hello +`), + TwoWayResult: []byte(` +name: hi +mergingList: +- name: hello +`), + Result: []byte(` +name: hi +mergingList: +- name: hello +- name: hello2 +`), + }, + }, + { + Description: "delete items in lists of scalars", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 1 + - 2 + - 4 +`), + }, + }, + { + Description: "delete all duplicate items in lists of scalars", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 3 + - 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 1 + - 2 + - 4 +`), + }, + }, + { + Description: "add and delete items in lists of scalars", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 + - 4 +$deleteFromPrimitiveList/mergingIntList: + - 3 +mergingIntList: + - 4 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 + - 4 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 + - 4 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 1 + - 2 + - 4 +`), + }, + }, + { + Description: "merge lists of maps", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Modified: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 1 + - name: 2 + value: 2 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Result: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + }, + }, + { + Description: "merge lists of maps with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 3 + value: 3 +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 3 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 2 + value: 2 + - name: 3 + value: 3 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + }, + }, + { + Description: "add field to map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 3 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + - name: 3 + value: 2 + other: b +`), + }, + }, + { + Description: "add duplicate field to map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(`{}`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "add an item that already exists in current object in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: a + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 + - name: 3 +mergingList: + - name: 3 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: a + - name: 2 + - name: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + value: a + other: x + - name: 2 + - name: 3 +`), + ThreeWay: []byte(`{}`), + Result: []byte(` +mergingList: + - name: 1 + value: a + other: x + - name: 2 + - name: 3 +`), + }, + }, + { + Description: "add duplicate field to map in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 3 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 2 + value: 2 +`), + Result: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "replace map field value in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: a +`), + Modified: []byte(` +mergingList: + - name: 1 + value: a + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: a +`), + Result: []byte(` +mergingList: + - name: 1 + value: a + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "replace map field value in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: a +`), + Modified: []byte(` +mergingList: + - name: 1 + value: a + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 3 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: a +`), + Result: []byte(` +mergingList: + - name: 1 + value: a + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "delete map from merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + other: b +`), + }, + }, + { + Description: "delete map from merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + other: b +`), + }, + }, + { + Description: "delete missing map from merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + other: b +`), + }, + }, + { + Description: "delete missing map from merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 3 + other: a +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 +mergingList: + - name: 2 + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + - name: 3 + other: a +`), + }, + }, + { + Description: "add map and delete map from merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 +mergingList: + - name: 3 + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 + - name: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + - name: 2 + other: b + - name: 4 + other: c +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 +mergingList: + - name: 3 + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + other: b + - name: 4 + other: c + - name: 3 +`), + }, + }, + { + Description: "add map and delete map from merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 +mergingList: + - name: 3 + - name: 1 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 + - name: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 4 + other: c +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 +mergingList: + - name: 2 + - name: 3 + - name: 1 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 4 + other: c + - name: 2 + - name: 3 +`), + }, + }, + { + Description: "delete field from map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "delete field from map in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + value: a + other: a + - name: 2 + value: 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 +`), + }, + }, + { + Description: "delete missing field from map in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "delete missing field from map in merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + value: null + - name: 2 + value: 2 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "replace non merging list nested in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "replace non merging list nested in merging list with value conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: c + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "replace non merging list nested in merging list with deletion conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 2 + value: 2 + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 1 + nonMergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + nonMergingList: + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list nested in merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list nested in merging list with value conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + value: a + other: c + - name: 2 + value: b + other: d + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + value: 1 + other: c + - name: 2 + value: 2 + other: d + - name: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list nested in merging list with deletion conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 2 + value: 2 + other: d + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 1 + - name: 2 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 1 + value: 1 + - name: 2 + value: 2 + other: d + - name: 2 + other: b +`), + }, + }, + + { + Description: "add field to map in merging list nested in merging list with deletion conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 2 + - name: 1 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergingList: + - name: 1 + mergingList: + - name: 2 + value: 2 + - name: 1 + value: 1 + - name: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 2 + value: 2 + other: d + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - $setElementOrder/mergingList: + - name: 2 + - name: 1 + name: 1 + mergingList: + - name: 1 + value: 1 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + mergingList: + - name: 2 + value: 2 + other: d + - name: 1 + value: 1 + - name: 2 + other: b +`), + }, + }, + { + Description: "add map to merging list by pointer", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergeItemPtr: + - name: 1 +`), + TwoWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - name: 2 +`), + Modified: []byte(` +mergeItemPtr: + - name: 1 + - name: 2 +`), + Current: []byte(` +mergeItemPtr: + - name: 1 + other: a + - name: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - name: 2 +`), + Result: []byte(` +mergeItemPtr: + - name: 1 + other: a + - name: 2 + - name: 3 +`), + }, + }, + { + Description: "add map to merging list by pointer with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergeItemPtr: + - name: 1 +`), + TwoWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - name: 2 +`), + Modified: []byte(` +mergeItemPtr: + - name: 1 + - name: 2 +`), + Current: []byte(` +mergeItemPtr: + - name: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - name: 1 + - name: 2 +`), + Result: []byte(` +mergeItemPtr: + - name: 1 + - name: 2 + - name: 3 +`), + }, + }, + { + Description: "add field to map in merging list by pointer", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergeItemPtr: + - name: 1 + mergeItemPtr: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - $setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 + name: 1 + mergeItemPtr: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergeItemPtr: + - name: 1 + mergeItemPtr: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergeItemPtr: + - name: 1 + other: a + mergeItemPtr: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - $setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 + name: 1 + mergeItemPtr: + - name: 1 + value: 1 +`), + Result: []byte(` +mergeItemPtr: + - name: 1 + other: a + mergeItemPtr: + - name: 1 + value: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 2 + other: b +`), + }, + }, + { + Description: "add field to map in merging list by pointer with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergeItemPtr: + - name: 1 + mergeItemPtr: + - name: 1 + - name: 2 + value: 2 + - name: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - $setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 + name: 1 + mergeItemPtr: + - name: 1 + value: 1 +`), + Modified: []byte(` +mergeItemPtr: + - name: 1 + mergeItemPtr: + - name: 1 + value: 1 + - name: 2 + value: 2 + - name: 2 +`), + Current: []byte(` +mergeItemPtr: + - name: 1 + other: a + mergeItemPtr: + - name: 1 + value: a + - name: 2 + value: 2 + other: b + - name: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 +mergeItemPtr: + - $setElementOrder/mergeItemPtr: + - name: 1 + - name: 2 + name: 1 + mergeItemPtr: + - name: 1 + value: 1 +`), + Result: []byte(` +mergeItemPtr: + - name: 1 + other: a + mergeItemPtr: + - name: 1 + value: 1 + - name: 2 + value: 2 + other: b + - name: 2 + other: b +`), + }, + }, + { + Description: "merge lists of scalars", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +- 1 +- 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: +- 1 +- 2 +- 3 +mergingIntList: +- 3 +`), + Modified: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +`), + Current: []byte(` +mergingIntList: +- 1 +- 2 +- 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: +- 1 +- 2 +- 3 +mergingIntList: +- 3 +`), + Result: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +- 4 +`), + }, + }, + { + Description: "add duplicate field to map in merging int list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 2 + - 3 +mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + ThreeWay: []byte(`{}`), + Result: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + }, + }, + // test case for setElementOrder + { + Description: "add an item in a list of primitives and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +- 1 +- 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: +- 3 +- 1 +- 2 +mergingIntList: +- 3 +`), + Modified: []byte(` +mergingIntList: +- 3 +- 1 +- 2 +`), + Current: []byte(` +mergingIntList: +- 1 +- 4 +- 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: +- 3 +- 1 +- 2 +mergingIntList: +- 3 +`), + Result: []byte(` +mergingIntList: +- 3 +- 1 +- 4 +- 2 +`), + }, + }, + { + Description: "delete an item in a list of primitives and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: +- 2 +- 1 +$deleteFromPrimitiveList/mergingIntList: +- 3 +`), + Modified: []byte(` +mergingIntList: +- 2 +- 1 +`), + Current: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: +- 2 +- 1 +$deleteFromPrimitiveList/mergingIntList: +- 3 +`), + Result: []byte(` +mergingIntList: +- 2 +- 1 +`), + }, + }, + { + Description: "add an item in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 3 + - name: 1 + - name: 2 +mergingList: + - name: 3 + value: 3 +`), + Modified: []byte(` +mergingList: + - name: 3 + value: 3 + - name: 1 + - name: 2 + value: 2 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 3 + - name: 1 + - name: 2 +mergingList: + - name: 3 + value: 3 +`), + Result: []byte(` +mergingList: + - name: 3 + value: 3 + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + }, + }, + { + Description: "add multiple items in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 4 + - name: 2 + - name: 3 +mergingList: + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Modified: []byte(` +mergingList: + - name: 1 + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 4 + - name: 2 + - name: 3 +mergingList: + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Result: []byte(` +mergingList: + - name: 1 + other: a + - name: 4 + value: 4 + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + }, + }, + { + Description: "delete an item in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 +mergingList: + - name: 3 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 +mergingList: + - name: 3 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 2 + value: 2 + other: b + - name: 1 + other: a +`), + }, + }, + { + Description: "change an item in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 + - name: 1 +mergingList: + - name: 3 + value: x +`), + Modified: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 3 + value: x + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 3 + - name: 1 +mergingList: + - name: 3 + value: x +`), + Result: []byte(` +mergingList: + - name: 2 + value: 2 + other: b + - name: 3 + value: x + - name: 1 + other: a +`), + }, + }, + { + Description: "add and delete an item in a list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 1 +mergingList: + - name: 4 + value: 4 + - name: 3 + $patch: delete +`), + Modified: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + other: b + - name: 3 + value: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 1 +mergingList: + - name: 4 + value: 4 + - name: 3 + $patch: delete +`), + Result: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + other: b + - name: 1 + other: a +`), + }, + }, + { + Description: "set elements order in a list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 4 + value: 4 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 3 + - name: 1 +`), + Modified: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 3 + value: 3 + - name: 4 + value: 4 + - name: 2 + value: 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 3 + - name: 1 +`), + Result: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 1 + other: a +`), + }, + }, + { + Description: "set elements order in a list with server-only items", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 3 + value: 3 + - name: 4 + value: 4 + - name: 2 + value: 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 3 + - name: 1 +`), + Modified: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 1 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 3 + value: 3 + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 9 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 4 + - name: 2 + - name: 3 + - name: 1 +`), + Result: []byte(` +mergingList: + - name: 4 + value: 4 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 1 + other: a + - name: 9 +`), + }, + }, + { + Description: "set elements order in a list with server-only items 2", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 4 + value: 4 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 + - name: 4 + - name: 3 +`), + Modified: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + - name: 9 + - name: 3 + value: 3 + - name: 4 + value: 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 + - name: 4 + - name: 3 +`), + Result: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 + other: a + - name: 9 + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + }, + }, + { + Description: "set elements order in a list with server-only items 3", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: + - name: 1 + - name: 2 + value: 2 + - name: 3 + value: 3 + - name: 4 + value: 4 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 + - name: 4 + - name: 3 +`), + Modified: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + Current: []byte(` +mergingList: + - name: 1 + other: a + - name: 2 + value: 2 + - name: 7 + - name: 9 + - name: 8 + - name: 3 + value: 3 + - name: 4 + value: 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 2 + - name: 1 + - name: 4 + - name: 3 +`), + Result: []byte(` +mergingList: + - name: 2 + value: 2 + - name: 1 + other: a + - name: 7 + - name: 9 + - name: 8 + - name: 4 + value: 4 + - name: 3 + value: 3 +`), + }, + }, + { + Description: "add an item in a int list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 3 + - 1 + - 2 +mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 3 + - 1 + - 2 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 3 + - 1 + - 2 +mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 3 + - 1 + - 2 +`), + }, + }, + { + Description: "add multiple items in a int list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 4 + - 2 + - 3 +mergingIntList: + - 4 + - 3 +`), + Modified: []byte(` +mergingIntList: + - 1 + - 4 + - 2 + - 3 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 1 + - 4 + - 2 + - 3 +mergingIntList: + - 4 + - 3 +`), + Result: []byte(` +mergingIntList: + - 1 + - 4 + - 2 + - 3 +`), + }, + }, + { + Description: "delete an item in a int list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 2 + - 1 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 2 + - 1 +`), + }, + }, + { + Description: "add and delete an item in a int list and preserve order", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 1 +mergingIntList: + - 4 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Modified: []byte(` +mergingIntList: + - 4 + - 2 + - 1 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 1 +mergingIntList: + - 4 +$deleteFromPrimitiveList/mergingIntList: + - 3 +`), + Result: []byte(` +mergingIntList: + - 4 + - 2 + - 1 +`), + }, + }, + { + Description: "set elements order in a int list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 4 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Modified: []byte(` +mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Current: []byte(` +mergingIntList: + - 1 + - 3 + - 4 + - 2 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Result: []byte(` +mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + }, + }, + { + Description: "set elements order in a int list with server-only items", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 3 + - 4 + - 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Modified: []byte(` +mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Current: []byte(` +mergingIntList: + - 1 + - 3 + - 4 + - 2 + - 9 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 4 + - 2 + - 3 + - 1 +`), + Result: []byte(` +mergingIntList: + - 4 + - 2 + - 3 + - 1 + - 9 +`), + }, + }, + { + Description: "set elements order in a int list with server-only items 2", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Modified: []byte(` +mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 9 + - 3 + - 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Result: []byte(` +mergingIntList: + - 2 + - 1 + - 9 + - 4 + - 3 +`), + }, + }, + { + Description: "set elements order in a int list with server-only items 3", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Modified: []byte(` +mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Current: []byte(` +mergingIntList: + - 1 + - 2 + - 7 + - 9 + - 8 + - 3 + - 4 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: + - 2 + - 1 + - 4 + - 3 +`), + Result: []byte(` +mergingIntList: + - 2 + - 1 + - 7 + - 9 + - 8 + - 4 + - 3 +`), + }, + }, + { + // This test case is used just to demonstrate the behavior when dealing with a list with duplicate + Description: "behavior of set element order for a merging list with duplicate", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: +- name: 1 +- name: 2 + value: dup1 +- name: 3 +- name: 2 + value: dup2 +- name: 4 +`), + Current: []byte(` +mergingList: +- name: 1 +- name: 2 + value: dup1 +- name: 3 +- name: 2 + value: dup2 +- name: 4 +`), + Modified: []byte(` +mergingList: +- name: 2 + value: dup1 +- name: 1 +- name: 4 +- name: 3 +- name: 2 + value: dup2 +`), + TwoWay: []byte(` +$setElementOrder/mergingList: +- name: 2 +- name: 1 +- name: 4 +- name: 3 +- name: 2 +`), + TwoWayResult: []byte(` +mergingList: +- name: 2 + value: dup1 +- name: 2 + value: dup2 +- name: 1 +- name: 4 +- name: 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: +- name: 2 +- name: 1 +- name: 4 +- name: 3 +- name: 2 +`), + Result: []byte(` +mergingList: +- name: 2 + value: dup1 +- name: 2 + value: dup2 +- name: 1 +- name: 4 +- name: 3 +`), + }, + }, + { + // This test case is used just to demonstrate the behavior when dealing with a list with duplicate + Description: "behavior of set element order for a merging int list with duplicate", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +- 2 +- 4 +`), + Current: []byte(` +mergingIntList: +- 1 +- 2 +- 3 +- 2 +- 4 +`), + Modified: []byte(` +mergingIntList: +- 2 +- 1 +- 4 +- 3 +- 2 +`), + TwoWay: []byte(` +$setElementOrder/mergingIntList: +- 2 +- 1 +- 4 +- 3 +- 2 +`), + TwoWayResult: []byte(` +mergingIntList: +- 2 +- 2 +- 1 +- 4 +- 3 +`), + ThreeWay: []byte(` +$setElementOrder/mergingIntList: +- 2 +- 1 +- 4 +- 3 +- 2 +`), + Result: []byte(` +mergingIntList: +- 2 +- 2 +- 1 +- 4 +- 3 +`), + }, + }, + { + Description: "retainKeys map should clear defaulted field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(`{}`), + Current: []byte(` +retainKeysMap: + value: foo +`), + Modified: []byte(` +retainKeysMap: + other: bar +`), + TwoWay: []byte(` +retainKeysMap: + other: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - other + other: bar +`), + Result: []byte(` +retainKeysMap: + other: bar +`), + }, + }, + { + Description: "retainKeys map should clear defaulted field with conflict (discriminated union)", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(`{}`), + Current: []byte(` +retainKeysMap: + name: type1 + value: foo +`), + Modified: []byte(` +retainKeysMap: + name: type2 + other: bar +`), + TwoWay: []byte(` +retainKeysMap: + name: type2 + other: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - other + name: type2 + other: bar +`), + Result: []byte(` +retainKeysMap: + name: type2 + other: bar +`), + }, + }, + { + Description: "retainKeys map adds a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo +`), + Current: []byte(` +retainKeysMap: + name: foo +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar +`), + }, + }, + { + Description: "retainKeys map adds a field and clear a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo +`), + Current: []byte(` +retainKeysMap: + name: foo + other: a +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar +`), + }, + }, + { + Description: "retainKeys map deletes a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + value: bar +`), + Current: []byte(` +retainKeysMap: + name: foo + value: bar +`), + Modified: []byte(` +retainKeysMap: + name: foo +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + value: null +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + value: null +`), + Result: []byte(` +retainKeysMap: + name: foo +`), + }, + }, + { + Description: "retainKeys map deletes a field and clears a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + value: bar +`), + Current: []byte(` +retainKeysMap: + name: foo + value: bar + other: a +`), + Modified: []byte(` +retainKeysMap: + name: foo +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + value: null +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + value: null +`), + Result: []byte(` +retainKeysMap: + name: foo +`), + }, + }, + { + Description: "retainKeys map clears a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + value: bar +`), + Current: []byte(` +retainKeysMap: + name: foo + value: bar + other: a +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar +`), + TwoWay: []byte(`{}`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar +`), + }, + }, + { + Description: "retainKeys map nested map with no change", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + simpleMap: + key1: a +`), + Current: []byte(` +retainKeysMap: + name: foo + simpleMap: + key1: a +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + value: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + value: bar +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a +`), + }, + }, + { + Description: "retainKeys map adds a field in a nested map", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a +`), + Current: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a + key3: c +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a + key2: b +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + simpleMap: + key2: b +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + simpleMap: + key2: b +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a + key2: b + key3: c +`), + }, + }, + { + Description: "retainKeys map deletes a field in a nested map", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a + key2: b +`), + Current: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a + key2: b + key3: c +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + simpleMap: + key2: null +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + simpleMap: + key2: null +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a + key3: c +`), + }, + }, + { + Description: "retainKeys map changes a field in a nested map", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a + key2: b +`), + Current: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: a + key2: b + key3: c +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: x + key2: b +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + simpleMap: + key1: x +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + simpleMap: + key1: x +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: x + key2: b + key3: c +`), + }, + }, + { + Description: "retainKeys map changes a field in a nested map with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: old + key2: b +`), + Current: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: new + key2: b + key3: c +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: modified + key2: b +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + simpleMap: + key1: modified +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - simpleMap + - value + simpleMap: + key1: modified +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + simpleMap: + key1: modified + key2: b + key3: c +`), + }, + }, + { + Description: "retainKeys map replaces non-merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + value: bar + nonMergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + value: bar + nonMergingList: + - name: a + - name: b +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + nonMergingList: + - name: a + - name: c + - name: b +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - nonMergingList + - value + nonMergingList: + - name: a + - name: c + - name: b +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - nonMergingList + - value + nonMergingList: + - name: a + - name: c + - name: b +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + nonMergingList: + - name: a + - name: c + - name: b +`), + }, + }, + { + Description: "retainKeys map nested non-merging list with no change", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + nonMergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + nonMergingList: + - name: a + - name: b +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + nonMergingList: + - name: a + - name: b +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - nonMergingList + - value + value: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - nonMergingList + - value + value: bar +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + nonMergingList: + - name: a + - name: b +`), + }, + }, + { + Description: "retainKeys map nested non-merging list with no change with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + nonMergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + nonMergingList: + - name: a + - name: b + - name: c +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + nonMergingList: + - name: a + - name: b +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - nonMergingList + - value + value: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - nonMergingList + - value + value: bar + nonMergingList: + - name: a + - name: b +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + nonMergingList: + - name: a + - name: b +`), + }, + }, + { + Description: "retainKeys map deletes nested non-merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + nonMergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + nonMergingList: + - name: a + - name: b +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar + nonMergingList: null +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar + nonMergingList: null +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar +`), + }, + }, + { + Description: "retainKeys map delete nested non-merging list with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + nonMergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + nonMergingList: + - name: a + - name: b + - name: c +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar + nonMergingList: null +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar + nonMergingList: null +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar +`), + }, + }, + { + Description: "retainKeys map nested merging int list with no change", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 3 +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + mergingIntList: + - 1 + - 2 +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingIntList + - name + - value + value: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingIntList + - name + - value + $setElementOrder/mergingIntList: + - 1 + - 2 + value: bar +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + mergingIntList: + - 1 + - 2 + - 3 +`), + }, + }, + { + Description: "retainKeys map adds an item in nested merging int list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 3 +`), + Modified: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 4 +`), + TwoWay: []byte(` +retainKeysMap: + $setElementOrder/mergingIntList: + - 1 + - 2 + - 4 + $retainKeys: + - mergingIntList + - name + mergingIntList: + - 4 +`), + ThreeWay: []byte(` +retainKeysMap: + $setElementOrder/mergingIntList: + - 1 + - 2 + - 4 + $retainKeys: + - mergingIntList + - name + mergingIntList: + - 4 +`), + Result: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 4 + - 3 +`), + }, + }, + { + Description: "retainKeys map deletes an item in nested merging int list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 3 +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + Modified: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 3 +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingIntList + - name + $deleteFromPrimitiveList/mergingIntList: + - 2 + $setElementOrder/mergingIntList: + - 1 + - 3 +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingIntList + - name + $deleteFromPrimitiveList/mergingIntList: + - 2 + $setElementOrder/mergingIntList: + - 1 + - 3 +`), + Result: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 3 + - 4 +`), + }, + }, + { + Description: "retainKeys map adds an item and deletes an item in nested merging int list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 3 +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 3 + - 4 +`), + Modified: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 3 + - 5 +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingIntList + - name + mergingIntList: + - 5 + $deleteFromPrimitiveList/mergingIntList: + - 2 + $setElementOrder/mergingIntList: + - 1 + - 3 + - 5 +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingIntList + - name + mergingIntList: + - 5 + $deleteFromPrimitiveList/mergingIntList: + - 2 + $setElementOrder/mergingIntList: + - 1 + - 3 + - 5 +`), + Result: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 3 + - 5 + - 4 +`), + }, + }, + { + Description: "retainKeys map deletes nested merging int list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 3 +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingIntList: + - 1 + - 2 + - 3 +`), + Modified: []byte(` +retainKeysMap: + name: foo +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + mergingIntList: null +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + mergingIntList: null +`), + Result: []byte(` +retainKeysMap: + name: foo +`), + }, + }, + { + Description: "retainKeys map nested merging list with no change", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + - name: c +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar + mergingList: + - name: a + - name: b +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingList + - name + - value + value: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingList + - name + - value + $setElementOrder/mergingList: + - name: a + - name: b + value: bar +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar + mergingList: + - name: a + - name: b + - name: c +`), + }, + }, + { + Description: "retainKeys map adds an item in nested merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + - name: x +`), + Modified: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + - name: c +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingList + - name + $setElementOrder/mergingList: + - name: a + - name: b + - name: c + mergingList: + - name: c +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingList + - name + $setElementOrder/mergingList: + - name: a + - name: b + - name: c + mergingList: + - name: c +`), + Result: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + - name: c + - name: x +`), + }, + }, + { + Description: "retainKeys map changes an item in nested merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + value: foo +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + value: foo + - name: x +`), + Modified: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + value: bar +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingList + - name + $setElementOrder/mergingList: + - name: a + - name: b + mergingList: + - name: b + value: bar +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingList + - name + $setElementOrder/mergingList: + - name: a + - name: b + mergingList: + - name: b + value: bar +`), + Result: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + value: bar + - name: x +`), + }, + }, + { + Description: "retainKeys map deletes nested merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b +`), + Modified: []byte(` +retainKeysMap: + name: foo + value: bar +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar + mergingList: null +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - name + - value + value: bar + mergingList: null +`), + Result: []byte(` +retainKeysMap: + name: foo + value: bar +`), + }, + }, + { + Description: "retainKeys map deletes an item in nested merging list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b +`), + Current: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: b + - name: x +`), + Modified: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a +`), + TwoWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingList + - name + $setElementOrder/mergingList: + - name: a + mergingList: + - name: b + $patch: delete +`), + ThreeWay: []byte(` +retainKeysMap: + $retainKeys: + - mergingList + - name + $setElementOrder/mergingList: + - name: a + mergingList: + - name: b + $patch: delete +`), + Result: []byte(` +retainKeysMap: + name: foo + mergingList: + - name: a + - name: x +`), + }, + }, + { + Description: "retainKeys list of maps clears a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + Current: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a + other: x +`), + Modified: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + TwoWay: []byte(`{}`), + ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo +`), + Result: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + }, + }, + { + Description: "retainKeys list of maps clears a field with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: old +`), + Current: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: new + other: x +`), + Modified: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: modified +`), + TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: modified +`), + ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: modified +`), + Result: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: modified +`), + }, + }, + { + Description: "retainKeys list of maps changes a field and clear a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: old +`), + Current: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: old + other: x +`), + Modified: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: new +`), + TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: new +`), + ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: new +`), + Result: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: new +`), + }, + }, + { + Description: "retainKeys list of maps changes a field and clear a field with conflict", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: old +`), + Current: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: modified + other: x +`), + Modified: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: new +`), + TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: new +`), + ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: new +`), + Result: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: new +`), + }, + }, + { + Description: "retainKeys list of maps adds a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMergingList: +- name: bar +- name: foo +`), + Current: []byte(` +retainKeysMergingList: +- name: bar +- name: foo +`), + Modified: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: a +`), + ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: a +`), + Result: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + }, + }, + { + Description: "retainKeys list of maps adds a field and clear a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMergingList: +- name: bar +- name: foo +`), + Current: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + other: x +`), + Modified: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: a +`), + ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + - value + name: foo + value: a +`), + Result: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + }, + }, + { + Description: "retainKeys list of maps deletes a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + Current: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + Modified: []byte(` +retainKeysMergingList: +- name: bar +- name: foo +`), + TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + name: foo + value: null +`), + ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + name: foo + value: null +`), + Result: []byte(` +retainKeysMergingList: +- name: bar +- name: foo +`), + }, + }, + { + Description: "retainKeys list of maps deletes a field and clear a field", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a +`), + Current: []byte(` +retainKeysMergingList: +- name: bar +- name: foo + value: a + other: x +`), + Modified: []byte(` +retainKeysMergingList: +- name: bar +- name: foo +`), + TwoWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + name: foo + value: null +`), + ThreeWay: []byte(` +$setElementOrder/retainKeysMergingList: + - name: bar + - name: foo +retainKeysMergingList: +- $retainKeys: + - name + name: foo + value: null +`), + Result: []byte(` +retainKeysMergingList: +- name: bar +- name: foo +`), + }, + }, + { + Description: "delete and reorder in one list, reorder in another", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +mergingList: +- name: a + value: a +- name: b + value: b +mergeItemPtr: +- name: c + value: c +- name: d + value: d +`), + Current: []byte(` +mergingList: +- name: a + value: a +- name: b + value: b +mergeItemPtr: +- name: c + value: c +- name: d + value: d +`), + Modified: []byte(` +mergingList: +- name: b + value: b +mergeItemPtr: +- name: d + value: d +- name: c + value: c +`), + TwoWay: []byte(` +$setElementOrder/mergingList: +- name: b +$setElementOrder/mergeItemPtr: +- name: d +- name: c +mergingList: +- $patch: delete + name: a +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: +- name: b +$setElementOrder/mergeItemPtr: +- name: d +- name: c +mergingList: +- $patch: delete + name: a +`), + Result: []byte(` +mergingList: +- name: b + value: b +mergeItemPtr: +- name: d + value: d +- name: c + value: c +`), + }, + }, +} + +func TestStrategicMergePatch(t *testing.T) { + testStrategicMergePatchWithCustomArgumentsUsingStruct(t, "bad struct", + "{}", "{}", []byte(""), mergepatch.ErrBadArgKind(struct{}{}, []byte{})) + + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), + } + mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ + SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, + Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + mergeItemOpenapiV3Schema, + } + + tc := StrategicMergePatchTestCases{} + err := yaml.Unmarshal(createStrategicMergePatchTestCaseData, &tc) + if err != nil { + t.Errorf("can't unmarshal test cases: %s\n", err) + return + } + + for _, schema := range schemas { + t.Run(schema.Name(), func(t *testing.T) { + + testStrategicMergePatchWithCustomArguments(t, "bad original", + "", "{}", schema, mergepatch.ErrBadJSONDoc) + testStrategicMergePatchWithCustomArguments(t, "bad patch", + "{}", "", schema, mergepatch.ErrBadJSONDoc) + testStrategicMergePatchWithCustomArguments(t, "nil struct", + "{}", "{}", nil, mergepatch.ErrBadArgKind(struct{}{}, nil)) + + for _, c := range tc.TestCases { + t.Run(c.Description+"/TwoWay", func(t *testing.T) { + testTwoWayPatch(t, c, schema) + }) + t.Run(c.Description+"/ThreeWay", func(t *testing.T) { + testThreeWayPatch(t, c, schema) + }) + } + }) + + // run multiple times to exercise different map traversal orders + for i := 0; i < 10; i++ { + for _, c := range strategicMergePatchRawTestCases { + t.Run(c.Description+"/TwoWay", func(t *testing.T) { + testTwoWayPatchForRawTestCase(t, c, schema) + }) + t.Run(c.Description+"/ThreeWay", func(t *testing.T) { + testThreeWayPatchForRawTestCase(t, c, schema) + }) + } + } + } +} + +func testStrategicMergePatchWithCustomArgumentsUsingStruct(t *testing.T, description, original, patch string, dataStruct interface{}, expected error) { + schema, actual := NewPatchMetaFromStruct(dataStruct) + // If actual is not nil, check error. If errors match, return. + if actual != nil { + checkErrorsEqual(t, description, expected, actual, schema) + return + } + testStrategicMergePatchWithCustomArguments(t, description, original, patch, schema, expected) +} + +func testStrategicMergePatchWithCustomArguments(t *testing.T, description, original, patch string, schema LookupPatchMeta, expected error) { + _, actual := StrategicMergePatch([]byte(original), []byte(patch), schema) + checkErrorsEqual(t, description, expected, actual, schema) +} + +func checkErrorsEqual(t *testing.T, description string, expected, actual error, schema LookupPatchMeta) { + if actual != expected { + if actual == nil { + t.Errorf("using %s expected error: %s\ndid not occur in test case: %s", getSchemaType(schema), expected, description) + return + } + + if expected == nil || actual.Error() != expected.Error() { + t.Errorf("using %s unexpected error: %s\noccurred in test case: %s", getSchemaType(schema), actual, description) + return + } + } +} + +func testTwoWayPatch(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) { + original, expectedPatch, modified, expectedResult := twoWayTestCaseToJSONOrFail(t, c, schema) + + actualPatch, err := CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema) + if err != nil { + t.Errorf("using %s error: %s\nin test case: %s\ncannot create two way patch: %s:\n%s\n", + getSchemaType(schema), err, c.Description, original, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + return + } + + testPatchCreation(t, expectedPatch, actualPatch, c.Description) + testPatchApplication(t, original, actualPatch, expectedResult, c.Description, "", schema) +} + +func testTwoWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase, schema LookupPatchMeta) { + original, expectedPatch, modified, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) + + actualPatch, err := CreateTwoWayMergePatchUsingLookupPatchMeta(original, modified, schema) + if err != nil { + t.Errorf("error: %s\nin test case: %s\ncannot create two way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + return + } + + testPatchCreation(t, expectedPatch, actualPatch, c.Description) + testPatchApplication(t, original, actualPatch, expectedResult, c.Description, c.ExpectedError, schema) +} + +func twoWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) ([]byte, []byte, []byte, []byte) { + expectedResult := c.TwoWayResult + if expectedResult == nil { + expectedResult = c.Modified + } + return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.TwoWay), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, expectedResult), c.Description, schema) +} + +func twoWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte) { + expectedResult := c.TwoWayResult + if expectedResult == nil { + expectedResult = c.Modified + } + return yamlToJSONOrError(t, c.Original), + yamlToJSONOrError(t, c.TwoWay), + yamlToJSONOrError(t, c.Modified), + yamlToJSONOrError(t, expectedResult) +} + +func testThreeWayPatch(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) { + original, modified, current, expected, result := threeWayTestCaseToJSONOrFail(t, c, schema) + actual, err := CreateThreeWayMergePatch(original, modified, current, schema, false) + if err != nil { + if !mergepatch.IsConflict(err) { + t.Errorf("using %s error: %s\nin test case: %s\ncannot create three way patch:\n%s\n", + getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + return + } + + if !strings.Contains(c.Description, "conflict") { + t.Errorf("using %s unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\n%s\n", + getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + return + } + + if len(c.Result) > 0 { + actual, err := CreateThreeWayMergePatch(original, modified, current, schema, true) + if err != nil { + t.Errorf("using %s error: %s\nin test case: %s\ncannot force three way patch application:\n%s\n", + getSchemaType(schema), err, c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + return + } + + testPatchCreation(t, expected, actual, c.Description) + testPatchApplication(t, current, actual, result, c.Description, "", schema) + } + + return + } + + if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 { + t.Errorf("using %s error in test case: %s\nexpected conflict did not occur:\n%s\n", + getSchemaType(schema), c.Description, mergepatch.ToYAMLOrError(c.StrategicMergePatchTestCaseData)) + return + } + + testPatchCreation(t, expected, actual, c.Description) + testPatchApplication(t, current, actual, result, c.Description, "", schema) +} + +func testThreeWayPatchForRawTestCase(t *testing.T, c StrategicMergePatchRawTestCase, schema LookupPatchMeta) { + original, modified, current, expected, result := threeWayRawTestCaseToJSONOrFail(t, c) + actual, err := CreateThreeWayMergePatch(original, modified, current, schema, false) + if err != nil { + if !mergepatch.IsConflict(err) { + t.Errorf("using %s error: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + return + } + + if !strings.Contains(c.Description, "conflict") { + t.Errorf("using %s unexpected conflict: %s\nin test case: %s\ncannot create three way patch:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + return + } + + if len(c.Result) > 0 { + actual, err := CreateThreeWayMergePatch(original, modified, current, schema, true) + if err != nil { + t.Errorf("using %s error: %s\nin test case: %s\ncannot force three way patch application:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + return + } + + testPatchCreation(t, expected, actual, c.Description) + testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError, schema) + } + + return + } + + if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 { + t.Errorf("using %s error: %s\nin test case: %s\nexpected conflict did not occur:\noriginal:%s\ntwoWay:%s\nmodified:%s\ncurrent:%s\nthreeWay:%s\nresult:%s\n", + getSchemaType(schema), err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) + return + } + + testPatchCreation(t, expected, actual, c.Description) + testPatchApplication(t, current, actual, result, c.Description, c.ExpectedError, schema) +} + +func threeWayTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase, schema LookupPatchMeta) ([]byte, []byte, []byte, []byte, []byte) { + return sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Original), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Modified), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Current), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.ThreeWay), c.Description, schema), + sortJsonOrFail(t, testObjectToJSONOrFail(t, c.Result), c.Description, schema) +} + +func threeWayRawTestCaseToJSONOrFail(t *testing.T, c StrategicMergePatchRawTestCase) ([]byte, []byte, []byte, []byte, []byte) { + return yamlToJSONOrError(t, c.Original), + yamlToJSONOrError(t, c.Modified), + yamlToJSONOrError(t, c.Current), + yamlToJSONOrError(t, c.ThreeWay), + yamlToJSONOrError(t, c.Result) +} + +func testPatchCreation(t *testing.T, expected, actual []byte, description string) { + if !reflect.DeepEqual(actual, expected) { + t.Errorf("error in test case: %s\nexpected patch:\n%s\ngot:\n%s\n", + description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual)) + return + } +} + +func testPatchApplication(t *testing.T, original, patch, expected []byte, description, expectedError string, schema LookupPatchMeta) { + result, err := StrategicMergePatchUsingLookupPatchMeta(original, patch, schema) + if len(expectedError) != 0 { + if err != nil && strings.Contains(err.Error(), expectedError) { + return + } + t.Errorf("using %s expected error should contain:\n%s\nin test case: %s\nbut got:\n%s\n", getSchemaType(schema), expectedError, description, err) + } + if err != nil { + t.Errorf("using %s error: %s\nin test case: %s\ncannot apply patch:\n%s\nto original:\n%s\n", + getSchemaType(schema), err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) + return + } + + if !reflect.DeepEqual(result, expected) { + format := "using error in test case: %s\npatch application failed:\noriginal:\n%s\npatch:\n%s\nexpected:\n%s\ngot:\n%s\n" + t.Errorf(format, description, + jsonToYAMLOrError(original), jsonToYAMLOrError(patch), + jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) + return + } +} + +func testObjectToJSONOrFail(t *testing.T, o map[string]interface{}) []byte { + if o == nil { + return nil + } + + j, err := toJSON(o) + if err != nil { + t.Error(err) + } + return j +} + +func sortJsonOrFail(t *testing.T, j []byte, description string, schema LookupPatchMeta) []byte { + if j == nil { + return nil + } + r, err := sortMergeListsByName(j, schema) + if err != nil { + t.Errorf("using %s error: %s\n in test case: %s\ncannot sort object:\n%s\n", getSchemaType(schema), err, description, j) + return nil + } + + return r +} + +func getSchemaType(schema LookupPatchMeta) string { + return reflect.TypeOf(schema).String() +} + +func jsonToYAMLOrError(j []byte) string { + y, err := jsonToYAML(j) + if err != nil { + return err.Error() + } + + return string(y) +} + +func toJSON(v interface{}) ([]byte, error) { + j, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("json marshal failed: %v\n%v\n", err, dump.Pretty(v)) + } + + return j, nil +} + +func jsonToYAML(j []byte) ([]byte, error) { + y, err := yaml.JSONToYAML(j) + if err != nil { + return nil, fmt.Errorf("json to yaml failed: %v\n%v\n", err, j) + } + + return y, nil +} + +func yamlToJSON(y []byte) ([]byte, error) { + j, err := yaml.YAMLToJSON(y) + if err != nil { + return nil, fmt.Errorf("yaml to json failed: %v\n%v\n", err, y) + } + + return j, nil +} + +func yamlToJSONOrError(t *testing.T, y []byte) []byte { + j, err := yamlToJSON(y) + if err != nil { + t.Errorf("%v", err) + } + + return j +} + +type PrecisionItem struct { + Name string `json:"name,omitempty"` + Int32 int32 `json:"int32,omitempty"` + Int64 int64 `json:"int64,omitempty"` + Float32 float32 `json:"float32,omitempty"` + Float64 float64 `json:"float64,omitempty"` +} + +var ( + precisionItem PrecisionItem + precisionItemStructSchema = PatchMetaFromStruct{T: GetTagStructTypeOrDie(precisionItem)} +) + +func TestNumberConversion(t *testing.T) { + testcases := map[string]struct { + Old string + New string + ExpectedPatch string + ExpectedResult string + }{ + "empty": { + Old: `{}`, + New: `{}`, + ExpectedPatch: `{}`, + ExpectedResult: `{}`, + }, + "int32 medium": { + Old: `{"int32":1000000}`, + New: `{"int32":1000000,"name":"newname"}`, + ExpectedPatch: `{"name":"newname"}`, + ExpectedResult: `{"int32":1000000,"name":"newname"}`, + }, + "int32 max": { + Old: `{"int32":2147483647}`, + New: `{"int32":2147483647,"name":"newname"}`, + ExpectedPatch: `{"name":"newname"}`, + ExpectedResult: `{"int32":2147483647,"name":"newname"}`, + }, + "int64 medium": { + Old: `{"int64":1000000}`, + New: `{"int64":1000000,"name":"newname"}`, + ExpectedPatch: `{"name":"newname"}`, + ExpectedResult: `{"int64":1000000,"name":"newname"}`, + }, + "int64 max": { + Old: `{"int64":9223372036854775807}`, + New: `{"int64":9223372036854775807,"name":"newname"}`, + ExpectedPatch: `{"name":"newname"}`, + ExpectedResult: `{"int64":9223372036854775807,"name":"newname"}`, + }, + "float32 max": { + Old: `{"float32":3.4028234663852886e+38}`, + New: `{"float32":3.4028234663852886e+38,"name":"newname"}`, + ExpectedPatch: `{"name":"newname"}`, + ExpectedResult: `{"float32":3.4028234663852886e+38,"name":"newname"}`, + }, + "float64 max": { + Old: `{"float64":1.7976931348623157e+308}`, + New: `{"float64":1.7976931348623157e+308,"name":"newname"}`, + ExpectedPatch: `{"name":"newname"}`, + ExpectedResult: `{"float64":1.7976931348623157e+308,"name":"newname"}`, + }, + } + + precisionItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(&fakePrecisionItemSchema, "precisionItem"), + } + precisionItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ + SchemaList: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas, + Schema: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas["precisionItem"], + } + precisionItemSchemas := []LookupPatchMeta{ + precisionItemStructSchema, + precisionItemOpenapiSchema, + precisionItemOpenapiV3Schema, + } + + for _, schema := range precisionItemSchemas { + for k, tc := range testcases { + patch, err := CreateTwoWayMergePatchUsingLookupPatchMeta([]byte(tc.Old), []byte(tc.New), schema) + if err != nil { + t.Errorf("using %s in testcase %s: unexpected error %v", getSchemaType(schema), k, err) + continue + } + if tc.ExpectedPatch != string(patch) { + t.Errorf("using %s in testcase %s: expected %s, got %s", getSchemaType(schema), k, tc.ExpectedPatch, string(patch)) + continue + } + + result, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Old), patch, schema) + if err != nil { + t.Errorf("using %s in testcase %s: unexpected error %v", getSchemaType(schema), k, err) + continue + } + if tc.ExpectedResult != string(result) { + t.Errorf("using %s in testcase %s: expected %s, got %s", getSchemaType(schema), k, tc.ExpectedResult, string(result)) + continue + } + } + } +} + +var replaceRawExtensionPatchTestCases = []StrategicMergePatchRawTestCase{ + { + Description: "replace RawExtension field, rest unchanched", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +name: my-object +value: some-value +other: current-other +replacingItem: + Some: Generic + Yaml: Inside + The: RawExtension + Field: Period +`), + Current: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 + - name: 2 + - name: 3 +replacingItem: + Some: Generic + Yaml: Inside + The: RawExtension + Field: Period +`), + Modified: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 + - name: 2 + - name: 3 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + TwoWay: []byte(` +mergingList: + - name: 1 + - name: 2 + - name: 3 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + TwoWayResult: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 + - name: 2 + - name: 3 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + ThreeWay: []byte(` +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + Result: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 + - name: 2 + - name: 3 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + }, + }, + { + Description: "replace RawExtension field and merge list", + StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ + Original: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 +replacingItem: + Some: Generic + Yaml: Inside + The: RawExtension + Field: Period +`), + Current: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 + - name: 3 +replacingItem: + Some: Generic + Yaml: Inside + The: RawExtension + Field: Period +`), + Modified: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 + - name: 2 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + TwoWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 2 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + TwoWayResult: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 + - name: 2 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + ThreeWay: []byte(` +$setElementOrder/mergingList: + - name: 1 + - name: 2 +mergingList: + - name: 2 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + Result: []byte(` +name: my-object +value: some-value +other: current-other +mergingList: + - name: 1 + - name: 2 + - name: 3 +replacingItem: + Newly: Modified + Yaml: Inside + The: RawExtension +`), + }, + }, +} + +func TestReplaceWithRawExtension(t *testing.T) { + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), + } + mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ + SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, + Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + mergeItemOpenapiV3Schema, + } + + for _, schema := range schemas { + for _, c := range replaceRawExtensionPatchTestCases { + testTwoWayPatchForRawTestCase(t, c, schema) + testThreeWayPatchForRawTestCase(t, c, schema) + } + } +} + +func TestUnknownField(t *testing.T) { + testcases := map[string]struct { + Original string + Current string + Modified string + + ExpectedTwoWay string + ExpectedTwoWayErr string + ExpectedTwoWayResult string + ExpectedThreeWay string + ExpectedThreeWayErr string + ExpectedThreeWayResult string + }{ + // cases we can successfully strategically merge + "no diff": { + Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Modified: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + + ExpectedTwoWay: `{}`, + ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + ExpectedThreeWay: `{}`, + ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + }, + "no diff even if modified null": { + Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Modified: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{"key":null},"name":"foo","scalar":true}`, + + ExpectedTwoWay: `{"complex_nullable":{"key":null}}`, + ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{},"name":"foo","scalar":true}`, + ExpectedThreeWay: `{"complex_nullable":{"key":null}}`, + ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{},"name":"foo","scalar":true}`, + }, + "discard nulls in nested and adds not nulls": { + Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Modified: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{"key":{"keynotnull":"value","keynull":null}},"name":"foo","scalar":true}`, + + ExpectedTwoWay: `{"complex_nullable":{"key":{"keynotnull":"value","keynull":null}}}`, + ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{"key":{"keynotnull":"value"}},"name":"foo","scalar":true}`, + ExpectedThreeWay: `{"complex_nullable":{"key":{"keynotnull":"value","keynull":null}}}`, + ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":{"key":{"keynotnull":"value"}},"name":"foo","scalar":true}`, + }, + "discard if modified all nulls": { + Original: `{}`, + Current: `{}`, + Modified: `{"complex":{"nested":null}}`, + + ExpectedTwoWay: `{"complex":{"nested":null}}`, + ExpectedTwoWayResult: `{"complex":{}}`, + ExpectedThreeWay: `{"complex":{"nested":null}}`, + ExpectedThreeWayResult: `{"complex":{}}`, + }, + "add only not nulls": { + Original: `{}`, + Current: `{}`, + Modified: `{"complex":{"nested":null,"nested2":"foo"}}`, + + ExpectedTwoWay: `{"complex":{"nested":null,"nested2":"foo"}}`, + ExpectedTwoWayResult: `{"complex":{"nested2":"foo"}}`, + ExpectedThreeWay: `{"complex":{"nested":null,"nested2":"foo"}}`, + ExpectedThreeWayResult: `{"complex":{"nested2":"foo"}}`, + }, + "null values in original are preserved": { + Original: `{"thing":null}`, + Current: `{"thing":null}`, + Modified: `{"nested":{"value":5},"thing":null}`, + + ExpectedTwoWay: `{"nested":{"value":5}}`, + ExpectedTwoWayResult: `{"nested":{"value":5},"thing":null}`, + ExpectedThreeWay: `{"nested":{"value":5}}`, + ExpectedThreeWayResult: `{"nested":{"value":5},"thing":null}`, + }, + "nested null values in original are preserved": { + Original: `{"complex":{"key":null},"thing":null}`, + Current: `{"complex":{"key":null},"thing":null}`, + Modified: `{"complex":{"key":null},"nested":{"value":5},"thing":null}`, + + ExpectedTwoWay: `{"nested":{"value":5}}`, + ExpectedTwoWayResult: `{"complex":{"key":null},"nested":{"value":5},"thing":null}`, + ExpectedThreeWay: `{"nested":{"value":5}}`, + ExpectedThreeWayResult: `{"complex":{"key":null},"nested":{"value":5},"thing":null}`, + }, + "add empty slices": { + Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Modified: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":[],"name":"foo","scalar":true}`, + + ExpectedTwoWay: `{"complex_nullable":[]}`, + ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":[],"name":"foo","scalar":true}`, + ExpectedThreeWay: `{"complex_nullable":[]}`, + ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"complex_nullable":[],"name":"foo","scalar":true}`, + }, + "filter nulls from nested slices": { + Original: `{}`, + Current: `{}`, + Modified: `{"complex_nullable":[{"inner_one":{"key_one":"foo","key_two":null}}]}`, + + ExpectedTwoWay: `{"complex_nullable":[{"inner_one":{"key_one":"foo","key_two":null}}]}`, + ExpectedTwoWayResult: `{"complex_nullable":[{"inner_one":{"key_one":"foo"}}]}`, + ExpectedThreeWay: `{"complex_nullable":[{"inner_one":{"key_one":"foo","key_two":null}}]}`, + ExpectedThreeWayResult: `{"complex_nullable":[{"inner_one":{"key_one":"foo"}}]}`, + }, + "filter if slice is all empty": { + Original: `{}`, + Current: `{}`, + Modified: `{"complex_nullable":[{"inner_one":{"key_one":null,"key_two":null}}]}`, + + ExpectedTwoWay: `{"complex_nullable":[{"inner_one":{"key_one":null,"key_two":null}}]}`, + ExpectedTwoWayResult: `{"complex_nullable":[{"inner_one":{}}]}`, + ExpectedThreeWay: `{"complex_nullable":[{"inner_one":{"key_one":null,"key_two":null}}]}`, + ExpectedThreeWayResult: `{"complex_nullable":[{"inner_one":{}}]}`, + }, + "not filter nulls from non-associative slice": { + Original: `{}`, + Current: `{}`, + Modified: `{"complex_nullable":["key1",null,"key2"]}`, + + ExpectedTwoWay: `{"complex_nullable":["key1",null,"key2"]}`, + ExpectedTwoWayResult: `{"complex_nullable":["key1",null,"key2"]}`, + ExpectedThreeWay: `{"complex_nullable":["key1",null,"key2"]}`, + ExpectedThreeWayResult: `{"complex_nullable":["key1",null,"key2"]}`, + }, + "added only": { + Original: `{"name":"foo"}`, + Current: `{"name":"foo"}`, + Modified: `{"name":"foo","scalar":true,"complex":{"nested":true},"array":[1,2,3]}`, + + ExpectedTwoWay: `{"array":[1,2,3],"complex":{"nested":true},"scalar":true}`, + ExpectedTwoWayResult: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + ExpectedThreeWay: `{"array":[1,2,3],"complex":{"nested":true},"scalar":true}`, + ExpectedThreeWayResult: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + }, + "removed only": { + Original: `{"name":"foo","scalar":true,"complex":{"nested":true}}`, + Current: `{"name":"foo","scalar":true,"complex":{"nested":true},"array":[1,2,3]}`, + Modified: `{"name":"foo"}`, + + ExpectedTwoWay: `{"complex":null,"scalar":null}`, + ExpectedTwoWayResult: `{"name":"foo"}`, + ExpectedThreeWay: `{"complex":null,"scalar":null}`, + ExpectedThreeWayResult: `{"array":[1,2,3],"name":"foo"}`, + }, + + // cases we cannot successfully strategically merge (expect errors) + "diff": { + Original: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Current: `{"array":[1,2,3],"complex":{"nested":true},"name":"foo","scalar":true}`, + Modified: `{"array":[1,2,3],"complex":{"nested":false},"name":"foo","scalar":true}`, + + ExpectedTwoWayErr: `unable to find api field`, + ExpectedThreeWayErr: `unable to find api field`, + }, + + "json": { + Original: `{"name":"foo","jsonItem":{"nested":{"nested2":{"nested3":{}}}}}`, + Current: `{"name":"foo","jsonItem":{"nested":{"nested2":{"nested3":{}}}}}`, + Modified: `{"name":"foo","jsonItem":{"nested":{"nested2":{"nested3":{"a":"b"}}}}}`, + + ExpectedTwoWay: `{"jsonItem":{"nested":{"nested2":{"nested3":{"a":"b"}}}}}`, + ExpectedTwoWayResult: `{"jsonItem":{"nested":{"nested2":{"nested3":{"a":"b"}}}},"name":"foo"}`, + ExpectedThreeWay: `{"jsonItem":{"nested":{"nested2":{"nested3":{"a":"b"}}}}}`, + ExpectedThreeWayResult: `{"jsonItem":{"nested":{"nested2":{"nested3":{"a":"b"}}}},"name":"foo"}`, + ExpectedTwoWayErr: `unable to find api field`, + ExpectedThreeWayErr: `unable to find api field`, + }, + } + + mergeItemOpenapiSchema := PatchMetaFromOpenAPI{ + Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"), + } + mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{ + SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas, + Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"], + } + schemas := []LookupPatchMeta{ + mergeItemStructSchema, + mergeItemOpenapiSchema, + mergeItemOpenapiV3Schema, + } + + for _, k := range sets.StringKeySet(testcases).List() { + t.Run(k, func(t *testing.T) { + tc := testcases[k] + for _, schema := range schemas { + t.Run(schema.Name()+"/TwoWay", func(t *testing.T) { + twoWay, err := CreateTwoWayMergePatchUsingLookupPatchMeta([]byte(tc.Original), []byte(tc.Modified), schema) + if err != nil { + if len(tc.ExpectedTwoWayErr) == 0 { + t.Errorf("using %s in testcase %s: error making two-way patch: %v", getSchemaType(schema), k, err) + } + if !strings.Contains(err.Error(), tc.ExpectedTwoWayErr) { + t.Errorf("using %s in testcase %s: expected error making two-way patch to contain '%s', got %s", getSchemaType(schema), k, tc.ExpectedTwoWayErr, err) + } + return + } + + if string(twoWay) != tc.ExpectedTwoWay { + t.Errorf("using %s in testcase %s: expected two-way patch:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedTwoWay), string(twoWay)) + return + } + + twoWayResult, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Original), twoWay, schema) + if err != nil { + t.Errorf("using %s in testcase %s: error applying two-way patch: %v", getSchemaType(schema), k, err) + return + } + if string(twoWayResult) != tc.ExpectedTwoWayResult { + t.Errorf("using %s in testcase %s: expected two-way result:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedTwoWayResult), string(twoWayResult)) + return + } + }) + + t.Run(schema.Name()+"/ThreeWay", func(t *testing.T) { + threeWay, err := CreateThreeWayMergePatch([]byte(tc.Original), []byte(tc.Modified), []byte(tc.Current), schema, false) + if err != nil { + if len(tc.ExpectedThreeWayErr) == 0 { + t.Errorf("using %s in testcase %s: error making three-way patch: %v", getSchemaType(schema), k, err) + } else if !strings.Contains(err.Error(), tc.ExpectedThreeWayErr) { + t.Errorf("using %s in testcase %s: expected error making three-way patch to contain '%s', got %s", getSchemaType(schema), k, tc.ExpectedThreeWayErr, err) + } + return + } + + if string(threeWay) != tc.ExpectedThreeWay { + t.Errorf("using %s in testcase %s: expected three-way patch:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedThreeWay), string(threeWay)) + return + } + + threeWayResult, err := StrategicMergePatchUsingLookupPatchMeta([]byte(tc.Current), threeWay, schema) + if err != nil { + t.Errorf("using %s in testcase %s: error applying three-way patch: %v", getSchemaType(schema), k, err) + return + } else if string(threeWayResult) != tc.ExpectedThreeWayResult { + t.Errorf("using %s in testcase %s: expected three-way result:\n\t%s\ngot\n\t%s", getSchemaType(schema), k, string(tc.ExpectedThreeWayResult), string(threeWayResult)) + return + } + }) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item-v3.json b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item-v3.json new file mode 100644 index 0000000000..f5178156e2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item-v3.json @@ -0,0 +1,183 @@ +{ + "openapi": "3.0", + "info": { + "title": "StrategicMergePatchTestingMergeItem", + "version": "v3.0" + }, + "paths": {}, + "components": { + "schemas": { + "mergeItem": { + "description": "MergeItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "value": { + "description": "Value field.", + "type": "string" + }, + "other": { + "description": "Other field.", + "type": "string" + }, + "mergingList": { + "description": "MergingList field.", + "type": "array", + "items": { + "default": {}, + "allOf": [ + {"$ref": "#/components/schemas/mergeItem"} + ] + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingList": { + "description": "NonMergingList field.", + "type": "array", + "items": { + "$ref": "#/components/schemas/mergeItem" + } + }, + "mergingIntList": { + "description": "MergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingIntList": { + "description": "NonMergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "mergeItemPtr": { + "description": "MergeItemPtr field.", + "allOf": [ + {"$ref": "#/components/schemas/mergeItem"} + ], + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "simpleMap": { + "description": "SimpleMap field.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "replacingItem": { + "description": "ReplacingItem field.", + "allOf": [ + {"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension"} + ], + "x-kubernetes-patch-strategy": "replace" + }, + "retainKeysMap": { + "description": "RetainKeysMap field.", + "allOf": [ + {"$ref": "#/components/schemas/retainKeysMergeItem"} + ], + "x-kubernetes-patch-strategy": "retainKeys" + }, + "retainKeysMergingList": { + "description": "RetainKeysMergingList field.", + "type": "array", + "items": { + "$ref": "#/components/schemas/mergeItem" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + }, + "jsonItem": { + "description": "Values are the chart values." + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "mergeItem", + "version": "some-version" + } + ] + }, + "retainKeysMergeItem": { + "description": "RetainKeysMergeItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "value": { + "description": "Value field.", + "type": "string" + }, + "other": { + "description": "Other field.", + "type": "string" + }, + "simpleMap": { + "description": "SimpleMap field.", + "items": { + "type": "string" + } + }, + "mergingList": { + "description": "MergingList field.", + "type": "array", + "items": { + "$ref": "#/components/schemas/mergeItem" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingList": { + "description": "NonMergingList field.", + "type": "array", + "items": { + "$ref": "#/components/schemas/mergeItem" + } + }, + "mergingIntList": { + "description": "MergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "x-kubernetes-patch-strategy": "merge" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "retainKeysMergeItem", + "version": "some-version" + } + ] + }, + "io.k8s.apimachinery.pkg.runtime.RawExtension": { + "description": "RawExtension is used to hold extensions in external versions.", + "required": [ + "Raw" + ], + "properties": { + "Raw": { + "description": "Raw is the underlying serialization of this object.", + "type": "string", + "format": "byte" + } + } + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item.json b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item.json new file mode 100644 index 0000000000..c5abe757ec --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item.json @@ -0,0 +1,173 @@ +{ + "swagger": "2.0", + "info": { + "title": "StrategicMergePatchTestingMergeItem", + "version": "v1.9.0" + }, + "paths": {}, + "definitions": { + "mergeItem": { + "description": "MergeItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "value": { + "description": "Value field.", + "type": "string" + }, + "other": { + "description": "Other field.", + "type": "string" + }, + "mergingList": { + "description": "MergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingList": { + "description": "NonMergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + } + }, + "mergingIntList": { + "description": "MergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingIntList": { + "description": "NonMergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "mergeItemPtr": { + "description": "MergeItemPtr field.", + "$ref": "#/definitions/mergeItem", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "simpleMap": { + "description": "SimpleMap field.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "replacingItem": { + "description": "ReplacingItem field.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.runtime.RawExtension", + "x-kubernetes-patch-strategy": "replace" + }, + "retainKeysMap": { + "description": "RetainKeysMap field.", + "$ref": "#/definitions/retainKeysMergeItem", + "x-kubernetes-patch-strategy": "retainKeys" + }, + "retainKeysMergingList": { + "description": "RetainKeysMergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys" + }, + "jsonItem": { + "description": "JSON field", + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "mergeItem", + "version": "some-version" + } + ] + }, + "retainKeysMergeItem": { + "description": "RetainKeysMergeItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "value": { + "description": "Value field.", + "type": "string" + }, + "other": { + "description": "Other field.", + "type": "string" + }, + "simpleMap": { + "description": "SimpleMap field.", + "additionalProperties": "object", + "items": { + "type": "string" + } + }, + "mergingList": { + "description": "MergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + }, + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "nonMergingList": { + "description": "NonMergingList field.", + "type": "array", + "items": { + "$ref": "#/definitions/mergeItem" + } + }, + "mergingIntList": { + "description": "MergingIntList field.", + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "x-kubernetes-patch-strategy": "merge" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "retainKeysMergeItem", + "version": "some-version" + } + ] + }, + "io.k8s.apimachinery.pkg.runtime.RawExtension": { + "description": "RawExtension is used to hold extensions in external versions.", + "required": [ + "Raw" + ], + "properties": { + "Raw": { + "description": "Raw is the underlying serialization of this object.", + "type": "string", + "format": "byte" + } + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item-v3.json b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item-v3.json new file mode 100644 index 0000000000..0101c41028 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item-v3.json @@ -0,0 +1,49 @@ +{ + "openapi": "3.0", + "info": { + "title": "StrategicMergePatchTestingPrecisionItem", + "version": "v1.9.0" + }, + "paths": {}, + "components": { + "schemas": { + "precisionItem": { + "description": "PrecisionItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "int32": { + "description": "Int32 field.", + "type": "integer", + "format": "int32" + }, + "int64": { + "description": "Int64 field.", + "type": "integer", + "format": "int64" + }, + "float32": { + "description": "Float32 field.", + "type": "number", + "format": "float32" + }, + "float64": { + "description": "Float64 field.", + "type": "number", + "format": "float64" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "precisionItem", + "version": "some-version" + } + ] + } + } + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item.json b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item.json new file mode 100644 index 0000000000..a35ae31f65 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item.json @@ -0,0 +1,47 @@ +{ + "swagger": "2.0", + "info": { + "title": "StrategicMergePatchTestingPrecisionItem", + "version": "v1.9.0" + }, + "paths": {}, + "definitions": { + "precisionItem": { + "description": "PrecisionItem is type definition for testing strategic merge.", + "required": [], + "properties": { + "name": { + "description": "Name field.", + "type": "string" + }, + "int32": { + "description": "Int32 field.", + "type": "integer", + "format": "int32" + }, + "int64": { + "description": "Int64 field.", + "type": "integer", + "format": "int64" + }, + "float32": { + "description": "Float32 field.", + "type": "number", + "format": "float32" + }, + "float64": { + "description": "Float64 field.", + "type": "number", + "format": "float64" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "fake-group", + "kind": "precisionItem", + "version": "some-version" + } + ] + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi.go new file mode 100644 index 0000000000..ae8fbfafac --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi.go @@ -0,0 +1,74 @@ +/* +Copyright 2017 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 testing + +import ( + "os" + "sync" + + openapi_v2 "github.com/google/gnostic-models/openapiv2" + openapi "k8s.io/kube-openapi/pkg/util/proto" +) + +// Fake opens and returns a openapi swagger from a file Path. It will +// parse only once and then return the same copy everytime. +type Fake struct { + Path string + + once sync.Once + document *openapi_v2.Document + err error +} + +// OpenAPISchema returns the openapi document and a potential error. +func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) { + f.once.Do(func() { + _, err := os.Stat(f.Path) + if err != nil { + f.err = err + return + } + spec, err := os.ReadFile(f.Path) + if err != nil { + f.err = err + return + } + f.document, f.err = openapi_v2.ParseDocument(spec) + }) + return f.document, f.err +} + +func getSchema(f *Fake, model string) (openapi.Schema, error) { + s, err := f.OpenAPISchema() + if err != nil { + return nil, err + } + m, err := openapi.NewOpenAPIData(s) + if err != nil { + return nil, err + } + return m.LookupModel(model), nil +} + +// GetSchemaOrDie returns the openapi schema. +func GetSchemaOrDie(f *Fake, model string) openapi.Schema { + s, err := getSchema(f, model) + if err != nil { + panic(err) + } + return s +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi3.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi3.go new file mode 100644 index 0000000000..895522b31a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/testing/openapi3.go @@ -0,0 +1,65 @@ +/* +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 testing + +import ( + "os" + "sync" + + "k8s.io/kube-openapi/pkg/spec3" +) + +type OpenAPIV3Getter struct { + Path string + once sync.Once + bytes []byte + openapiv3 spec3.OpenAPI +} + +func (f *OpenAPIV3Getter) SchemaBytesOrDie() []byte { + f.once.Do(func() { + _, err := os.Stat(f.Path) + if err != nil { + panic(err) + } + spec, err := os.ReadFile(f.Path) + if err != nil { + panic(err) + } + f.bytes = spec + }) + return f.bytes +} + +func (f *OpenAPIV3Getter) SchemaOrDie() *spec3.OpenAPI { + f.once.Do(func() { + _, err := os.Stat(f.Path) + if err != nil { + panic(err) + } + spec, err := os.ReadFile(f.Path) + if err != nil { + panic(err) + } + + err = f.openapiv3.UnmarshalJSON(spec) + if err != nil { + panic(err) + } + }) + return &f.openapiv3 +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/types.go new file mode 100644 index 0000000000..f84d65aacb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/strategicpatch/types.go @@ -0,0 +1,193 @@ +/* +Copyright 2017 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 strategicpatch + +import ( + "errors" + "strings" + + "k8s.io/apimachinery/pkg/util/mergepatch" + openapi "k8s.io/kube-openapi/pkg/util/proto" +) + +const ( + patchStrategyOpenapiextensionKey = "x-kubernetes-patch-strategy" + patchMergeKeyOpenapiextensionKey = "x-kubernetes-patch-merge-key" +) + +type LookupPatchItem interface { + openapi.SchemaVisitor + + Error() error + Path() *openapi.Path +} + +type kindItem struct { + key string + path *openapi.Path + err error + patchmeta PatchMeta + subschema openapi.Schema + hasVisitKind bool +} + +func NewKindItem(key string, path *openapi.Path) *kindItem { + return &kindItem{ + key: key, + path: path, + } +} + +var _ LookupPatchItem = &kindItem{} + +func (item *kindItem) Error() error { + return item.err +} + +func (item *kindItem) Path() *openapi.Path { + return item.path +} + +func (item *kindItem) VisitPrimitive(schema *openapi.Primitive) { + item.err = errors.New("expected kind, but got primitive") +} + +func (item *kindItem) VisitArray(schema *openapi.Array) { + item.err = errors.New("expected kind, but got slice") +} + +func (item *kindItem) VisitMap(schema *openapi.Map) { + item.err = errors.New("expected kind, but got map") +} + +func (item *kindItem) VisitReference(schema openapi.Reference) { + if !item.hasVisitKind { + schema.SubSchema().Accept(item) + } +} + +func (item *kindItem) VisitKind(schema *openapi.Kind) { + subschema, ok := schema.Fields[item.key] + if !ok { + item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key} + return + } + + mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions()) + if err != nil { + item.err = err + return + } + item.patchmeta = PatchMeta{ + patchStrategies: patchStrategies, + patchMergeKey: mergeKey, + } + item.subschema = subschema +} + +type sliceItem struct { + key string + path *openapi.Path + err error + patchmeta PatchMeta + subschema openapi.Schema + hasVisitKind bool +} + +func NewSliceItem(key string, path *openapi.Path) *sliceItem { + return &sliceItem{ + key: key, + path: path, + } +} + +var _ LookupPatchItem = &sliceItem{} + +func (item *sliceItem) Error() error { + return item.err +} + +func (item *sliceItem) Path() *openapi.Path { + return item.path +} + +func (item *sliceItem) VisitPrimitive(schema *openapi.Primitive) { + item.err = errors.New("expected slice, but got primitive") +} + +func (item *sliceItem) VisitArray(schema *openapi.Array) { + if !item.hasVisitKind { + item.err = errors.New("expected visit kind first, then visit array") + } + subschema := schema.SubType + item.subschema = subschema +} + +func (item *sliceItem) VisitMap(schema *openapi.Map) { + item.err = errors.New("expected slice, but got map") +} + +func (item *sliceItem) VisitReference(schema openapi.Reference) { + if !item.hasVisitKind { + schema.SubSchema().Accept(item) + } else { + item.subschema = schema.SubSchema() + } +} + +func (item *sliceItem) VisitKind(schema *openapi.Kind) { + subschema, ok := schema.Fields[item.key] + if !ok { + item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key} + return + } + + mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions()) + if err != nil { + item.err = err + return + } + item.patchmeta = PatchMeta{ + patchStrategies: patchStrategies, + patchMergeKey: mergeKey, + } + item.hasVisitKind = true + subschema.Accept(item) +} + +func parsePatchMetadata(extensions map[string]interface{}) (string, []string, error) { + ps, foundPS := extensions[patchStrategyOpenapiextensionKey] + var patchStrategies []string + var mergeKey, patchStrategy string + var ok bool + if foundPS { + patchStrategy, ok = ps.(string) + if ok { + patchStrategies = strings.Split(patchStrategy, ",") + } else { + return "", nil, mergepatch.ErrBadArgType(patchStrategy, ps) + } + } + mk, foundMK := extensions[patchMergeKeyOpenapiextensionKey] + if foundMK { + mergeKey, ok = mk.(string) + if !ok { + return "", nil, mergepatch.ErrBadArgType(mergeKey, mk) + } + } + return mergeKey, patchStrategies, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/uuid/uuid.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/uuid/uuid.go new file mode 100644 index 0000000000..1fa351aab6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/uuid/uuid.go @@ -0,0 +1,27 @@ +/* +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 uuid + +import ( + "github.com/google/uuid" + + "k8s.io/apimachinery/pkg/types" +) + +func NewUUID() types.UID { + return types.UID(uuid.New().String()) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/OWNERS new file mode 100644 index 0000000000..4023732476 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/OWNERS @@ -0,0 +1,11 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +# Disable inheritance as this is an api owners file +options: + no_parent_owners: true +approvers: + - api-approvers +reviewers: + - api-reviewers +labels: + - kind/api-change diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go new file mode 100644 index 0000000000..1a18b27666 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/error_matcher.go @@ -0,0 +1,397 @@ +/* +Copyright 2025 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 field + +import ( + "fmt" + "reflect" + "regexp" + "strings" +) + +// NormalizationRule holds a pre-compiled regular expression and its replacement string +// for normalizing field paths. +type NormalizationRule struct { + Regexp *regexp.Regexp + Replacement string +} + +// ErrorMatcher is a helper for comparing Error objects. +type ErrorMatcher struct { + // TODO(thockin): consider whether type is ever NOT required, maybe just + // assume it. + matchType bool + // TODO(thockin): consider whether field could be assumed - if the + // "want" error has a nil field, don't match on field. + matchField bool + // TODO(thockin): consider whether value could be assumed - if the + // "want" error has a nil value, don't match on value. + matchValue bool + matchOrigin bool + matchDetail func(want, got string) bool + requireOriginWhenInvalid bool + matchValidationStabilityLevel bool + matchSource bool + matchAncestorShortCircuit bool + matchShortCircuit bool + // normalizationRules holds the pre-compiled regex patterns for path normalization. + normalizationRules []NormalizationRule +} + +// isChildPath returns true if child is a descendant path of parent. +// It avoids false positives like "spec.containers" being a child of "spec.container" +// by explicitly checking for '.' or '[' path separators. +func isChildPath(parent, child string) bool { + // "" as parent path not supported. It can un-intentionally match with any path. + // theoretically system errors can be matched with any path errors. + if len(parent) == 0 { + return false + } + if len(child) <= len(parent) { + return false + } + if child[:len(parent)] != parent { + return false + } + sep := child[len(parent)] + return sep == '.' || sep == '[' +} + +// Matches returns true if the two Error objects match according to the +// configured criteria. When field normalization is configured, only the +// "got" error's field path is normalized (to bring older API versions up +// to the internal/latest format), while "want" is assumed to already be +// in the canonical internal API format. +func (m ErrorMatcher) Matches(want, got *Error) bool { + gotField := got.Field + if want.Field != gotField { + gotField = m.normalizePath(gotField) + } + if m.matchAncestorShortCircuit { + if got.ShortCircuit && (isChildPath(gotField, want.Field) || isChildPath(got.Field, want.Field)) { + return true + } + } + + if m.matchType && want.Type != got.Type { + return false + } + if m.matchField && want.Field != gotField { + return false + } + if m.matchValue && !reflect.DeepEqual(want.BadValue, got.BadValue) { + return false + } + + if m.matchOrigin { + if want.Origin != got.Origin { + return false + } + if m.requireOriginWhenInvalid && want.Type == ErrorTypeInvalid { + if want.Origin == "" || got.Origin == "" { + return false + } + } + } + if m.matchDetail != nil && !m.matchDetail(want.Detail, got.Detail) { + return false + } + if m.matchValidationStabilityLevel && want.ValidationStabilityLevel != got.ValidationStabilityLevel { + return false + } + if m.matchSource && want.FromImperative != got.FromImperative { + return false + } + if m.matchShortCircuit && want.ShortCircuit != got.ShortCircuit { + return false + } + + return true +} + +// normalizePath applies configured path normalization rules. +func (m ErrorMatcher) normalizePath(path string) string { + for _, rule := range m.normalizationRules { + normalized := rule.Regexp.ReplaceAllString(path, rule.Replacement) + if normalized != path { + // Only apply the first matching rule. + return normalized + } + } + return path +} + +// Render returns a string representation of the specified Error object, +// according to the criteria configured in the ErrorMatcher. +func (m ErrorMatcher) Render(e *Error) string { + buf := strings.Builder{} + + comma := func() { + if buf.Len() > 0 { + buf.WriteString(", ") + } + } + + if m.matchType { + comma() + fmt.Fprintf(&buf, "Type=%q", e.Type) + } + if m.matchField { + comma() + if normalized := m.normalizePath(e.Field); normalized != e.Field { + fmt.Fprintf(&buf, "Field=%q (aka %q)", normalized, e.Field) + } else { + fmt.Fprintf(&buf, "Field=%q", e.Field) + } + } + if m.matchValue { + comma() + if s, ok := e.BadValue.(string); ok { + fmt.Fprintf(&buf, "Value=%q", s) + } else { + rv := reflect.ValueOf(e.BadValue) + if rv.Kind() == reflect.Pointer && !rv.IsNil() { + rv = rv.Elem() + } + if rv.IsValid() && rv.CanInterface() { + fmt.Fprintf(&buf, "Value=%v", rv.Interface()) + } else { + fmt.Fprintf(&buf, "Value=%v", e.BadValue) + } + } + } + if m.matchOrigin || m.requireOriginWhenInvalid && e.Type == ErrorTypeInvalid { + comma() + fmt.Fprintf(&buf, "Origin=%q", e.Origin) + } + if m.matchDetail != nil { + comma() + fmt.Fprintf(&buf, "Detail=%q", e.Detail) + } + if m.matchValidationStabilityLevel { + comma() + fmt.Fprintf(&buf, "ValidationStabilityLevel=%s", e.ValidationStabilityLevel) + } + if m.matchSource { + comma() + fmt.Fprintf(&buf, "FromImperative=%t", e.FromImperative) + } + if m.matchShortCircuit { + comma() + fmt.Fprintf(&buf, "ShortCircuit=%t", e.ShortCircuit) + } + return "{" + buf.String() + "}" +} + +// Exactly returns a derived ErrorMatcher which matches all fields exactly. +func (m ErrorMatcher) Exactly() ErrorMatcher { + return m.ByType().ByField().ByValue().ByOrigin().ByDetailExact() +} + +// ByType returns a derived ErrorMatcher which also matches by type. +func (m ErrorMatcher) ByType() ErrorMatcher { + m.matchType = true + return m +} + +// ByField returns a derived ErrorMatcher which also matches by field path. +// If you need to mutate the field path (e.g. to normalize across versions), +// see ByFieldNormalized. +func (m ErrorMatcher) ByField() ErrorMatcher { + m.matchField = true + return m +} + +// ByFieldNormalized returns a derived ErrorMatcher which also matches by field path +// after applying normalization rules to the actual (got) error's field path. +// This allows matching field paths from older API versions against the canonical +// internal API format. +// +// The normalization rules are applied ONLY to the "got" error's field path, bringing +// older API version field paths up to the latest/internal format. The "want" error +// is assumed to always be in the internal API format (latest). +// +// The rules slice holds pre-compiled regular expressions and their replacement strings. +// +// Example: +// +// rules := []NormalizationRule{ +// { +// Regexp: regexp.MustCompile(`spec\.devices\.requests\[(\d+)\]\.allocationMode`), +// Replacement: "spec.devices.requests[$1].exactly.allocationMode", +// }, +// } +// matcher := ErrorMatcher{}.ByFieldNormalized(rules) +func (m ErrorMatcher) ByFieldNormalized(rules []NormalizationRule) ErrorMatcher { + m.matchField = true + m.normalizationRules = rules + return m +} + +// ByValue returns a derived ErrorMatcher which also matches by the errant +// value. +func (m ErrorMatcher) ByValue() ErrorMatcher { + m.matchValue = true + return m +} + +// ByOrigin returns a derived ErrorMatcher which also matches by the origin. +// When this is used and an origin is set in the error, the matcher will +// consider all expected errors with the same origin to be a match. The only +// expception to this is when it finds two errors which are exactly identical, +// which is too suspicious to ignore. This multi-matching allows tests to +// express a single expectation ("I set the X field to an invalid value, and I +// expect an error from origin Y") without having to know exactly how many +// errors might be returned, or in what order, or with what wording. +func (m ErrorMatcher) ByOrigin() ErrorMatcher { + m.matchOrigin = true + return m +} + +// RequireOriginWhenInvalid returns a derived ErrorMatcher which also requires +// the Origin field to be set when the Type is Invalid and the matcher is +// matching by Origin. +func (m ErrorMatcher) RequireOriginWhenInvalid() ErrorMatcher { + m.requireOriginWhenInvalid = true + return m +} + +// BySource returns a derived ErrorMatcher which also matches by the error origination +// value of field errors. +func (m ErrorMatcher) BySource() ErrorMatcher { + m.matchSource = true + return m +} + +// MatchAncestorShortCircuit returns a derived ErrorMatcher which also matches when the "got" error short-circuited at an ancestor of the "want" error's field path. +func (m ErrorMatcher) MatchAncestorShortCircuit() ErrorMatcher { + m.matchAncestorShortCircuit = true + return m +} + +// MatchShortCircuit returns a derived ErrorMatcher which also matches by the ShortCircuit value. +func (m ErrorMatcher) MatchShortCircuit() ErrorMatcher { + m.matchShortCircuit = true + return m +} + +// ByValidationStabilityLevel returns a derived ErrorMatcher which also matches by the validation stability level +// value of field errors. +func (m ErrorMatcher) ByValidationStabilityLevel() ErrorMatcher { + m.matchValidationStabilityLevel = true + return m +} + +// ByDetailExact returns a derived ErrorMatcher which also matches errors by +// the exact detail string. +func (m ErrorMatcher) ByDetailExact() ErrorMatcher { + m.matchDetail = func(want, got string) bool { + return got == want + } + return m +} + +// ByDetailSubstring returns a derived ErrorMatcher which also matches errors +// by a substring of the detail string. +func (m ErrorMatcher) ByDetailSubstring() ErrorMatcher { + m.matchDetail = func(want, got string) bool { + return strings.Contains(got, want) + } + return m +} + +// ByDetailRegexp returns a derived ErrorMatcher which also matches errors by a +// regular expression of the detail string, where the "want" string is assumed +// to be a valid regular expression. +func (m ErrorMatcher) ByDetailRegexp() ErrorMatcher { + m.matchDetail = func(want, got string) bool { + return regexp.MustCompile(want).MatchString(got) + } + return m +} + +// TestIntf lets users pass a testing.T while not coupling this package to Go's +// testing package. +type TestIntf interface { + Helper() + Errorf(format string, args ...any) +} + +// Test compares two ErrorLists by the criteria configured in this matcher, and +// fails the test if they don't match. The "want" errors are expected to be in +// the internal API format (latest), while "got" errors may be from any API version +// and will be normalized if field normalization rules are configured. +// +// If matching by origin is enabled and the error has a non-empty origin, a given +// "want" error can match multiple "got" errors, and they will all be consumed. +// The only exception to this is if the matcher got multiple identical (in every way, +// even those not being matched on) errors, which is likely to indicate a bug. +// This doesn't support matchAncestorShortCircuit as it it not needed to be used in the tests. +func (m ErrorMatcher) Test(tb TestIntf, want, got ErrorList) { + tb.Helper() + + if m.matchAncestorShortCircuit { + tb.Errorf("matchAncestorShortCircuit is not supported for test") + } + exactly := m.Exactly() // makes a copy + + // If we ever find an EXACT duplicate error, it's almost certainly a bug + // worth reporting. If we ever find a use-case where this is not a bug, we + // can revisit this assumption. + seen := map[string]bool{} + for _, g := range got { + key := exactly.Render(g) + if seen[key] { + tb.Errorf("exact duplicate error:\n%s", key) + } + seen[key] = true + } + + remaining := got + for _, w := range want { + tmp := make(ErrorList, 0, len(remaining)) + matched := false + for i, g := range remaining { + if m.Matches(w, g) { + matched = true + if m.matchOrigin && w.Origin != "" { + // When origin is included in the match, we allow multiple + // matches against the same wanted error, so that tests + // can be insulated from the exact number, order, and + // wording of cases that might return more than one error. + continue + } else { + // Single-match, save the rest of the "got" errors and move + // on to the next "want" error. + tmp = append(tmp, remaining[i+1:]...) + break + } + } else { + tmp = append(tmp, g) + } + } + if !matched { + tb.Errorf("expected an error matching:\n%s", m.Render(w)) + } + remaining = tmp + } + if len(remaining) > 0 { + for _, e := range remaining { + tb.Errorf("unmatched error:\n%s", exactly.Render(e)) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/error_matcher_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/error_matcher_test.go new file mode 100644 index 0000000000..bf7a94e3ae --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/error_matcher_test.go @@ -0,0 +1,789 @@ +/* +Copyright 2025 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 field + +import ( + "fmt" + "regexp" + "strings" + "testing" +) + +func TestErrorMatcher_Matches(t *testing.T) { + baseErr := func() *Error { + return &Error{ + Type: ErrorTypeInvalid, + Field: "field", + BadValue: "value", + Detail: "detail", + Origin: "origin", + } + } + + testCases := []struct { + name string + matcher ErrorMatcher + wantedErr func() *Error + actualErr *Error + matches bool + }{{ + name: "ByType: match", + matcher: ErrorMatcher{}.ByType(), + wantedErr: baseErr, + actualErr: &Error{Type: ErrorTypeInvalid}, + matches: true, + }, { + name: "ByType: no match", + matcher: ErrorMatcher{}.ByType(), + wantedErr: baseErr, + actualErr: &Error{Type: ErrorTypeRequired}, + matches: false, + }, { + name: "ByField: match", + matcher: ErrorMatcher{}.ByField(), + wantedErr: baseErr, + actualErr: &Error{Field: "field"}, + matches: true, + }, { + name: "ByField: no match", + matcher: ErrorMatcher{}.ByField(), + wantedErr: baseErr, + actualErr: &Error{Field: "other"}, + matches: false, + }, { + name: "ByFieldNormalized: older API to latest", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + wantedErr: func() *Error { + e := baseErr() + e.Field = "f[0].x.a" + return e + }, + actualErr: &Error{Field: "f[0].a"}, + matches: true, + }, { + name: "ByFieldNormalized: both latest format", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + wantedErr: func() *Error { + e := baseErr() + e.Field = "f[0].x.a" + return e + }, + actualErr: &Error{Field: "f[0].x.a"}, + matches: true, + }, { + name: "ByFieldNormalized: different index", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + wantedErr: func() *Error { + e := baseErr() + e.Field = "f[0].x.a" + return e + }, + actualErr: &Error{Field: "f[1].a"}, + matches: false, + }, { + name: "ByFieldNormalized: multiple patterns", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.b`), Replacement: "f[$1].x.b"}, + }), + wantedErr: func() *Error { + e := baseErr() + e.Field = "f[2].x.b" + return e + }, + actualErr: &Error{Field: "f[2].b"}, + matches: true, + }, { + name: "ByFieldNormalized: no normalization needed", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + wantedErr: func() *Error { + e := baseErr() + e.Field = "other.field" + return e + }, + actualErr: &Error{Field: "other.field"}, + matches: true, + }, { + name: "ByValue: match", + matcher: ErrorMatcher{}.ByValue(), + wantedErr: baseErr, + actualErr: &Error{BadValue: "value"}, + matches: true, + }, { + name: "ByValue: no match", + matcher: ErrorMatcher{}.ByValue(), + wantedErr: baseErr, + actualErr: &Error{BadValue: "other"}, + matches: false, + }, { + name: "ByOrigin: match", + matcher: ErrorMatcher{}.ByOrigin(), + wantedErr: baseErr, + actualErr: &Error{Origin: "origin"}, + matches: true, + }, { + name: "ByOrigin: no match", + matcher: ErrorMatcher{}.ByOrigin(), + wantedErr: baseErr, + actualErr: &Error{Origin: "other"}, + matches: false, + }, { + name: "ByDetailExact: match", + matcher: ErrorMatcher{}.ByDetailExact(), + wantedErr: baseErr, + actualErr: &Error{Detail: "detail"}, + matches: true, + }, { + name: "ByDetailExact: no match", + matcher: ErrorMatcher{}.ByDetailExact(), + wantedErr: baseErr, + actualErr: &Error{Detail: "other"}, + matches: false, + }, { + name: "ByDetailSubstring: match empty", + matcher: ErrorMatcher{}.ByDetailSubstring(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailSubstring: match full", + matcher: ErrorMatcher{}.ByDetailSubstring(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "is the" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailSubstring: match start", + matcher: ErrorMatcher{}.ByDetailSubstring(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "this is" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailSubstring: match middle", + matcher: ErrorMatcher{}.ByDetailSubstring(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "is the" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailSubstring: match end", + matcher: ErrorMatcher{}.ByDetailSubstring(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "the detail" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailSubstring: no match", + matcher: ErrorMatcher{}.ByDetailSubstring(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "is not the" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: false, + }, { + name: "ByDetailRegexp: match empty", + matcher: ErrorMatcher{}.ByDetailRegexp(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = ".*" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailRegexp: match full", + matcher: ErrorMatcher{}.ByDetailRegexp(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "^this is the detail$" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailRegexp: match start", + matcher: ErrorMatcher{}.ByDetailRegexp(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "^this is" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailRegexp: match middle", + matcher: ErrorMatcher{}.ByDetailRegexp(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "is the" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailRegexp: match end", + matcher: ErrorMatcher{}.ByDetailRegexp(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "the detail$" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailRegexp: match parts", + matcher: ErrorMatcher{}.ByDetailRegexp(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "^this .* .* detail$" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: true, + }, { + name: "ByDetailRegexp: no match", + matcher: ErrorMatcher{}.ByDetailRegexp(), + wantedErr: func() *Error { + e := baseErr() + e.Detail = "is not the" + return e + }, + actualErr: &Error{Detail: "this is the detail"}, + matches: false, + }, { + name: "Exactly: match", + matcher: ErrorMatcher{}.Exactly(), + wantedErr: baseErr, + actualErr: baseErr(), + matches: true, + }, { + name: "Exactly: no match (type)", + matcher: ErrorMatcher{}.Exactly(), + wantedErr: baseErr, + actualErr: &Error{Type: ErrorTypeRequired, Field: "field", BadValue: "value", Detail: "detail", Origin: "origin"}, + matches: false, + }, { + name: "RequireOriginWhenInvalid: match", + matcher: ErrorMatcher{}.ByOrigin().RequireOriginWhenInvalid(), + wantedErr: baseErr, + actualErr: &Error{Type: ErrorTypeInvalid, Origin: "origin"}, + matches: true, + }, { + name: "RequireOriginWhenInvalid: no match (missing origin)", + matcher: ErrorMatcher{}.ByOrigin().RequireOriginWhenInvalid(), + wantedErr: baseErr, + actualErr: &Error{Type: ErrorTypeInvalid}, + matches: false, + }, { + name: "BySource: match", + matcher: ErrorMatcher{}.BySource(), + wantedErr: func() *Error { + e := baseErr() + e.FromImperative = true + return e + }, + actualErr: &Error{FromImperative: true}, + matches: true, + }, { + name: "BySource: no match", + matcher: ErrorMatcher{}.BySource(), + wantedErr: func() *Error { + e := baseErr() + e.FromImperative = true + return e + }, + actualErr: &Error{FromImperative: false}, + matches: false, + }, { + name: "MatchShortCircuit: match", + matcher: ErrorMatcher{}.MatchShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.ShortCircuit = true + return e + }, + actualErr: &Error{ShortCircuit: true}, + matches: true, + }, { + name: "MatchShortCircuit: no match", + matcher: ErrorMatcher{}.MatchShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.ShortCircuit = true + return e + }, + actualErr: &Error{ShortCircuit: false}, + matches: false, + }, { + name: "MatchAncestorShortCircuit: child field match", + matcher: ErrorMatcher{}.ByType().ByField().MatchAncestorShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.Field = "field.child" + e.Type = ErrorTypeRequired // child error can be a different type + return e + }, + actualErr: &Error{Field: "field", Type: ErrorTypeInvalid, Origin: "immutable", ShortCircuit: true}, // parent error short-circuited + matches: true, + }, { + name: "MatchAncestorShortCircuit: array child match", + matcher: ErrorMatcher{}.ByType().ByField().MatchAncestorShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.Field = "field[0]" + e.Type = ErrorTypeRequired + return e + }, + actualErr: &Error{Field: "field", Type: ErrorTypeInvalid, Origin: "immutable", ShortCircuit: true}, + matches: true, + }, { + name: "MatchAncestorShortCircuit: not a child", + matcher: ErrorMatcher{}.ByType().ByField().MatchAncestorShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.Field = "other.child" + return e + }, + actualErr: &Error{Field: "field", Type: ErrorTypeInvalid, Origin: "immutable", ShortCircuit: true}, + matches: false, + }, { + name: "MatchAncestorShortCircuit: substring but not child", + matcher: ErrorMatcher{}.ByType().ByField().MatchAncestorShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.Field = "field_other" + return e + }, + actualErr: &Error{Field: "field", Type: ErrorTypeInvalid, Origin: "immutable", ShortCircuit: true}, + matches: false, + }, { + name: "MatchAncestorShortCircuit: parent path is empty string", + matcher: ErrorMatcher{}.ByType().ByField().MatchAncestorShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.Field = "field.child" + return e + }, + actualErr: &Error{Field: "", Type: ErrorTypeInvalid, Origin: "immutable", ShortCircuit: true}, + matches: false, + }, { + name: "MatchAncestorShortCircuit: child field match with normalized parent", + matcher: ErrorMatcher{}.ByType().ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }).MatchAncestorShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.Field = "f[0].x.a.child" + e.Type = ErrorTypeRequired + return e + }, + actualErr: &Error{Field: "f[0].a", Type: ErrorTypeInvalid, Origin: "immutable", ShortCircuit: true}, + matches: true, + }, { + name: "MatchAncestorShortCircuit: child field match with unnormalized parent", + matcher: ErrorMatcher{}.ByType().ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }).MatchAncestorShortCircuit(), + wantedErr: func() *Error { + e := baseErr() + e.Field = "f[0].a.child" + e.Type = ErrorTypeRequired + return e + }, + actualErr: &Error{Field: "f[0].a", Type: ErrorTypeInvalid, Origin: "immutable", ShortCircuit: true}, + matches: true, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if tc.matcher.Matches(tc.wantedErr(), tc.actualErr) != tc.matches { + t.Errorf("Matches() = %v, want %v", !tc.matches, tc.matches) + } + }) + } +} + +// fakeTestIntf is used to test the testing support. +type fakeTestIntf struct { + errs []string +} + +var _ TestIntf = &fakeTestIntf{} + +func (*fakeTestIntf) Helper() {} + +func (ft *fakeTestIntf) Errorf(format string, args ...any) { + ft.errs = append(ft.errs, fmt.Sprintf(format, args...)) +} + +func TestErrorMatcher_Test(t *testing.T) { + testCases := []struct { + name string + matcher ErrorMatcher + want ErrorList + got ErrorList + expectedErrors []string + expectedLogs []string + }{{ + name: "no origin: perfect match", + matcher: ErrorMatcher{}.ByField(), + want: ErrorList{Invalid(NewPath("f"), nil, "")}, + got: ErrorList{Invalid(NewPath("f"), "v", "d")}, + }, { + name: "no origin: got too few errors", + matcher: ErrorMatcher{}.ByField(), + want: ErrorList{Invalid(NewPath("f"), nil, "")}, + got: ErrorList{}, + expectedErrors: []string{"expected an error matching:"}, + }, { + name: "no origin: got too many errors", + matcher: ErrorMatcher{}.ByField(), + want: ErrorList{}, + got: ErrorList{Invalid(NewPath("f"), "v", "d")}, + expectedErrors: []string{"unmatched error:"}, + }, { + name: "no origin: got wrong errors", + matcher: ErrorMatcher{}.ByField(), + want: ErrorList{Invalid(NewPath("f1"), nil, "")}, + got: ErrorList{Invalid(NewPath("f2"), "v", "d")}, + expectedErrors: []string{"expected an error matching:", "unmatched error:"}, + }, { + name: "with normalization: older API to latest", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + want: ErrorList{Invalid(NewPath("f").Index(0).Child("x", "a"), nil, "")}, + got: ErrorList{Invalid(NewPath("f").Index(0).Child("a"), "v", "d")}, + }, { + name: "with normalization: both latest", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + want: ErrorList{Invalid(NewPath("f").Index(0).Child("x", "a"), nil, "")}, + got: ErrorList{Invalid(NewPath("f").Index(0).Child("x", "a"), "v", "d")}, + }, { + name: "with normalization: multiple", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.b`), Replacement: "f[$1].x.b"}, + }), + want: ErrorList{ + Invalid(NewPath("f").Index(0).Child("x", "a"), nil, ""), + Invalid(NewPath("f").Index(1).Child("x", "b"), nil, ""), + }, + got: ErrorList{ + Invalid(NewPath("f").Index(0).Child("a"), "v1", "d1"), + Invalid(NewPath("f").Index(1).Child("b"), "v2", "d2"), + }, + }, { + name: "with normalization: no match", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + want: ErrorList{Invalid(NewPath("f").Index(0).Child("x", "a"), nil, "")}, + got: ErrorList{Invalid(NewPath("f").Index(1).Child("a"), "v", "d")}, + expectedErrors: []string{"expected an error matching:", "unmatched error:"}, + }, { + name: "validation level: match", + matcher: ErrorMatcher{}.ByValidationStabilityLevel(), + want: ErrorList{{}}.MarkAlpha(), + got: ErrorList{{}}.MarkAlpha(), + }, { + name: "validation level: no match", + matcher: ErrorMatcher{}.ByValidationStabilityLevel(), + want: ErrorList{{}}.MarkAlpha(), + got: ErrorList{{}}.MarkBeta(), + expectedErrors: []string{"expected an error matching:", "unmatched error:"}, + }, { + name: "by source: match", + matcher: ErrorMatcher{}.BySource(), + want: ErrorList{{FromImperative: true}}, + got: ErrorList{{FromImperative: true}}, + }, { + name: "by source: no match", + matcher: ErrorMatcher{}.BySource(), + want: ErrorList{{FromImperative: true}}, + got: ErrorList{{FromImperative: false}}, + expectedErrors: []string{"expected an error matching:", "unmatched error:"}, + }, { + name: "with origin: single match", + matcher: ErrorMatcher{}.ByField().ByOrigin(), + want: ErrorList{Invalid(NewPath("f"), nil, "").WithOrigin("o")}, + got: ErrorList{Invalid(NewPath("f"), "v", "d").WithOrigin("o")}, + }, { + name: "with origin: multiple matches, different details", + matcher: ErrorMatcher{}.ByField().ByOrigin(), + want: ErrorList{ + Invalid(NewPath("f1"), nil, "").WithOrigin("o"), + Invalid(NewPath("f2"), nil, "").WithOrigin("o"), + }, + got: ErrorList{ + Invalid(NewPath("f1"), "v", "d1").WithOrigin("o"), + Invalid(NewPath("f2"), "v", "d1").WithOrigin("o"), + Invalid(NewPath("f1"), "v", "d2").WithOrigin("o"), + Invalid(NewPath("f2"), "v", "d2").WithOrigin("o"), + }, + }, { + name: "with origin: multiple matches, same exact error", + matcher: ErrorMatcher{}.ByField().ByOrigin(), + want: ErrorList{ + Invalid(NewPath("f1"), nil, "").WithOrigin("o"), + Invalid(NewPath("f2"), nil, "").WithOrigin("o"), + }, + got: ErrorList{ + Invalid(NewPath("f1"), "v", "d").WithOrigin("o"), + Invalid(NewPath("f1"), "v", "d").WithOrigin("o"), + Invalid(NewPath("f2"), "v", "d").WithOrigin("o"), + Invalid(NewPath("f2"), "v", "d").WithOrigin("o"), + }, + expectedErrors: []string{"exact duplicate error:", "exact duplicate error:"}, + }, { + name: "match short circuit: match", + matcher: ErrorMatcher{}.MatchShortCircuit(), + want: ErrorList{{ShortCircuit: true}}, + got: ErrorList{{ShortCircuit: true}}, + }, { + name: "match short circuit: no match", + matcher: ErrorMatcher{}.MatchShortCircuit(), + want: ErrorList{{ShortCircuit: true}}, + got: ErrorList{{ShortCircuit: false}}, + expectedErrors: []string{"expected an error matching:", "unmatched error:"}, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fakeT := &fakeTestIntf{} + tc.matcher.Test(fakeT, tc.want, tc.got) + if want, got := len(tc.expectedErrors), len(fakeT.errs); got != want { + if got == 0 { + t.Errorf("expected %d errors, got %d", want, got) + } else { + q := make([]string, len(fakeT.errs)) + for i, err := range fakeT.errs { + q[i] = fmt.Sprintf("%q", err) + } + t.Errorf("expected %d errors, got %d:\n%s", want, got, strings.Join(q, "\n")) + } + } else { + for i := range tc.expectedErrors { + if !strings.HasPrefix(fakeT.errs[i], tc.expectedErrors[i]) { + t.Errorf("error %d: expected prefix %q, got %q", i, tc.expectedErrors[i], fakeT.errs[i]) + } + } + } + }) + } +} + +func TestErrorMatcher_Render(t *testing.T) { + testCases := []struct { + name string + matcher ErrorMatcher + err *Error + expected string + }{ + { + name: "empty matcher", + matcher: ErrorMatcher{}, + err: Invalid(NewPath("field"), "value", "detail"), + expected: "{}", + }, + { + name: "single field - type", + matcher: ErrorMatcher{}.ByType(), + err: Invalid(NewPath("field"), "value", "detail"), + expected: `{Type="Invalid value"}`, + }, + { + name: "single field - value with string", + matcher: ErrorMatcher{}.ByValue(), + err: Invalid(NewPath("field"), "string_value", "detail"), + expected: `{Value="string_value"}`, + }, + { + name: "single field - value with nil", + matcher: ErrorMatcher{}.ByValue(), + err: Invalid(NewPath("field"), nil, "detail"), + expected: `{Value=}`, + }, + { + name: "multiple fields", + matcher: ErrorMatcher{}.ByType().ByField().ByValue(), + err: Invalid(NewPath("field"), "value", "detail"), + expected: `{Type="Invalid value", Field="field", Value="value"}`, + }, + { + name: "all fields", + matcher: ErrorMatcher{}.ByType().ByField().ByValue().ByOrigin().ByDetailExact(), + err: Invalid(NewPath("field"), "value", "detail").WithOrigin("origin"), + expected: `{Type="Invalid value", Field="field", Value="value", Origin="origin", Detail="detail"}`, + }, + { + name: "with normalization: normalized", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + err: Invalid(NewPath("f").Index(0).Child("a"), "value", "detail"), + expected: `{Field="f[0].x.a" (aka "f[0].a")}`, + }, + { + name: "with normalization: no normalization", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + err: Invalid(NewPath("other", "field"), "value", "detail"), + expected: `{Field="other.field"}`, + }, + { + name: "with normalization: already normalized", + matcher: ErrorMatcher{}.ByFieldNormalized([]NormalizationRule{ + {Regexp: regexp.MustCompile(`f\[(\d+)\]\.a`), Replacement: "f[$1].x.a"}, + }), + err: Invalid(NewPath("f").Index(0).Child("x", "a"), "value", "detail"), + expected: `{Field="f[0].x.a"}`, + }, + { + name: "requireOriginWhenInvalid with origin", + matcher: ErrorMatcher{}.ByOrigin().RequireOriginWhenInvalid(), + err: Invalid(NewPath("field"), "value", "detail").WithOrigin("origin"), + expected: `{Origin="origin"}`, + }, + { + name: "different error types", + matcher: ErrorMatcher{}.ByType().ByValue(), + err: Required(NewPath("field"), "detail"), + expected: `{Type="Required value", Value=""}`, + }, + { + name: "with from imperative", + matcher: ErrorMatcher{}.BySource(), + err: func() *Error { + e := Invalid(NewPath("field"), "value", "detail") + e.FromImperative = true + return e + }(), + expected: `{FromImperative=true}`, + }, + { + name: "all fields with from imperative", + matcher: ErrorMatcher{}.ByType().ByField().ByValue().ByOrigin().ByDetailExact().BySource(), + err: func() *Error { + e := Invalid(NewPath("field"), "value", "detail").WithOrigin("origin") + e.FromImperative = true + return e + }(), + expected: `{Type="Invalid value", Field="field", Value="value", Origin="origin", Detail="detail", FromImperative=true}`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := tc.matcher.Render(tc.err) + if result != tc.expected { + t.Errorf("Render() = %v, want %v", result, tc.expected) + } + }) + } +} + +func TestIsChildPath(t *testing.T) { + testCases := []struct { + name string + parent string + child string + expected bool + }{ + { + name: "child is a descendant array element", + parent: "spec.containers", + child: "spec.containers[0]", + expected: true, + }, + { + name: "child is a subfield", + parent: "spec.container", + child: "spec.container.name", + expected: true, + }, + { + name: "not a child, just common prefix", + parent: "spec.container", + child: "spec.containers", + expected: false, + }, + { + name: "parent is empty", + parent: "", + child: "spec.containers", + expected: false, + }, + { + name: "child is shorter than parent", + parent: "spec.containers", + child: "spec", + expected: false, + }, + { + name: "child is equal to parent", + parent: "spec.containers", + child: "spec.containers", + expected: false, + }, + { + name: "child does not match parent prefix", + parent: "spec.containers", + child: "status.conditions", + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := isChildPath(tc.parent, tc.child) + if result != tc.expected { + t.Errorf("isChildPath(%q, %q) = %v, want %v", tc.parent, tc.child, result, tc.expected) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/errors.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/errors.go new file mode 100644 index 0000000000..0d00acb42d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/errors.go @@ -0,0 +1,635 @@ +/* +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 field + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" +) + +// Error is an implementation of the 'error' interface, which represents a +// field-level validation error. +type Error struct { + Type ErrorType + Field string + BadValue interface{} + Detail string + + // Origin uniquely identifies where this error was generated from. It is used in testing to + // compare expected errors against actual errors without relying on exact detail string matching. + // This allows tests to verify the correct validation logic triggered the error + // regardless of how the error message might be formatted or localized. + // + // The value should be either: + // - A simple camelCase identifier (e.g., "maximum", "maxItems") + // - A structured format using "format=" for validation errors related to specific formats + // (e.g. "format=k8s-short-name") + // + // If the Origin corresponds to an existing declarative validation tag or JSON Schema keyword, + // use that same name for consistency. + // + // Origin should be set in the most deeply nested validation function that + // can still identify the unique source of the error. + Origin string + + // CoveredByDeclarative is true when this error is covered by declarative + // validation. This field is to identify errors from imperative validation + // that should also be caught by declarative validation. + CoveredByDeclarative bool + + // FromImperative denotes these errors are originating from the hand written validations. + FromImperative bool + + // ShortCircuit denotes that this error prevents further validation of the current field's children. + ShortCircuit bool + + // ShortCircuitedInDeclarative denotes this error is covered by declarative validation. But not returned by declarative validation due to a short circuiting behavior for the current input. + ShortCircuitedInDeclarative bool + + // ValidationStabilityLevel denotes the validation stability level of the declarative validation from this error is returned. This should be used in the declarative validations only. + ValidationStabilityLevel ValidationStabilityLevel +} + +// ValidationStabilityLevel denotes the stability level of a validation. +type ValidationStabilityLevel int + +const ( + stabilityLevelUnknown ValidationStabilityLevel = iota + stabilityLevelAlpha + stabilityLevelBeta +) + +func (v ValidationStabilityLevel) String() string { + switch v { + case stabilityLevelAlpha: + return "alpha" + case stabilityLevelBeta: + return "beta" + default: + return "unknown" + } +} + +var _ error = &Error{} + +// IsAlpha returns true if the error is an alpha validation error. +func (e *Error) IsAlpha() bool { + return e.ValidationStabilityLevel == stabilityLevelAlpha +} + +// IsBeta returns true if the error is a beta validation error. +func (e *Error) IsBeta() bool { + return e.ValidationStabilityLevel == stabilityLevelBeta +} + +// Error implements the error interface. +func (e *Error) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.ErrorBody()) +} + +type OmitValueType struct{} + +var omitValue = OmitValueType{} + +// ErrorBody returns the error message without the field name. This is useful +// for building nice-looking higher-level error reporting. +func (e *Error) ErrorBody() string { + var s string + switch e.Type { + case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeTooShort, ErrorTypeInternal: + s = e.Type.String() + case ErrorTypeInvalid, ErrorTypeTypeInvalid, ErrorTypeNotSupported, + ErrorTypeNotFound, ErrorTypeDuplicate, ErrorTypeTooMany, ErrorTypeTooFew: + if e.BadValue == omitValue { + s = e.Type.String() + break + } + switch t := e.BadValue.(type) { + case int64, int32, float64, float32, bool: + // use simple printer for simple types + s = fmt.Sprintf("%s: %v", e.Type, t) + case string: + s = fmt.Sprintf("%s: %q", e.Type, t) + default: + // use more complex techniques to render more complex types + valstr := "" + jb, err := json.Marshal(e.BadValue) + if err == nil { + // best case + valstr = string(jb) + } else if stringer, ok := e.BadValue.(fmt.Stringer); ok { + // anything that defines String() is better than raw struct + valstr = stringer.String() + } else { + // worst case - fallback to raw struct + // TODO: internal types have panic guards against json.Marshalling to prevent + // accidental use of internal types in external serialized form. For now, use + // %#v, although it would be better to show a more expressive output in the future + valstr = fmt.Sprintf("%#v", e.BadValue) + } + s = fmt.Sprintf("%s: %s", e.Type, valstr) + } + default: + internal := InternalError(nil, fmt.Errorf("unhandled error code: %s: please report this", e.Type)) + s = internal.ErrorBody() + } + if len(e.Detail) != 0 { + s += fmt.Sprintf(": %s", e.Detail) + } + + return s +} + +// WithOrigin adds origin information to the FieldError +func (e *Error) WithOrigin(o string) *Error { + e.Origin = o + return e +} + +// MarkCoveredByDeclarative marks the error as covered by declarative validation. +func (e *Error) MarkCoveredByDeclarative() *Error { + e.CoveredByDeclarative = true + return e +} + +// ErrorType is a machine readable value providing more detail about why +// a field is invalid. These values are expected to match 1-1 with +// CauseType in api/types.go. +type ErrorType string + +// TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it. +const ( + // ErrorTypeNotFound is used to report failure to find a requested value + // (e.g. looking up an ID). See NotFound(). + ErrorTypeNotFound ErrorType = "FieldValueNotFound" + // ErrorTypeRequired is used to report required values that are not + // provided (e.g. empty strings, null values, or empty arrays). See + // Required(). + ErrorTypeRequired ErrorType = "FieldValueRequired" + // ErrorTypeDuplicate is used to report collisions of values that must be + // unique (e.g. unique IDs). See Duplicate(). + ErrorTypeDuplicate ErrorType = "FieldValueDuplicate" + // ErrorTypeInvalid is used to report malformed values (e.g. failed regex + // match, too long, out of bounds). See Invalid(). + ErrorTypeInvalid ErrorType = "FieldValueInvalid" + // ErrorTypeNotSupported is used to report unknown values for enumerated + // fields (e.g. a list of valid values). See NotSupported(). + ErrorTypeNotSupported ErrorType = "FieldValueNotSupported" + // ErrorTypeForbidden is used to report valid (as per formatting rules) + // values which would be accepted under some conditions, but which are not + // permitted by the current conditions (such as security policy). See + // Forbidden(). + ErrorTypeForbidden ErrorType = "FieldValueForbidden" + // ErrorTypeTooLong is used to report that the given value is too long. + // This is similar to ErrorTypeInvalid, but the error will not include the + // too-long value. See TooLong(). + ErrorTypeTooLong ErrorType = "FieldValueTooLong" + // ErrorTypeTooMany is used to report "too many". This is used to + // report that a given list has too many items. This is similar to FieldValueTooLong, + // but the error indicates quantity instead of length. + ErrorTypeTooMany ErrorType = "FieldValueTooMany" + // ErrorTypeTooFew is used to report "too few". This is used to + // report that a given list has too few items. This is similar to FieldValueTooLong, + // but the error indicates quantity instead of length. + ErrorTypeTooFew ErrorType = "FieldValueTooFew" + // ErrorTypeInternal is used to report other errors that are not related + // to user input. See InternalError(). + ErrorTypeInternal ErrorType = "InternalError" + // ErrorTypeTypeInvalid is for the value did not match the schema type for that field + ErrorTypeTypeInvalid ErrorType = "FieldValueTypeInvalid" + // ErrorTypeTooShort is used to report that the given value is too short. + // This is similar to ErrorTypeInvalid. See TooShort(). + ErrorTypeTooShort ErrorType = "FieldValueTooShort" +) + +// String converts a ErrorType into its corresponding canonical error message. +func (t ErrorType) String() string { + switch t { + case ErrorTypeNotFound: + return "Not found" + case ErrorTypeRequired: + return "Required value" + case ErrorTypeDuplicate: + return "Duplicate value" + case ErrorTypeInvalid: + return "Invalid value" + case ErrorTypeNotSupported: + return "Unsupported value" + case ErrorTypeForbidden: + return "Forbidden" + case ErrorTypeTooLong: + return "Too long" + case ErrorTypeTooMany: + return "Too many" + case ErrorTypeTooFew: + return "Too few" + case ErrorTypeInternal: + return "Internal error" + case ErrorTypeTypeInvalid: + return "Invalid value" + case ErrorTypeTooShort: + return "Too short" + default: + return fmt.Sprintf("", string(t)) + } +} + +// TypeInvalid returns a *Error indicating "type is invalid" +func TypeInvalid(field *Path, value interface{}, detail string) *Error { + return &Error{ + Type: ErrorTypeTypeInvalid, + Field: field.String(), + BadValue: value, + Detail: detail, + } +} + +// NotFound returns a *Error indicating "value not found". This is +// used to report failure to find a requested value (e.g. looking up an ID). +func NotFound(field *Path, value interface{}) *Error { + return &Error{ + Type: ErrorTypeNotFound, + Field: field.String(), + BadValue: value, + } +} + +// Required returns a *Error indicating "value required". This is used +// to report required values that are not provided (e.g. empty strings, null +// values, or empty arrays). +func Required(field *Path, detail string) *Error { + return &Error{ + Type: ErrorTypeRequired, + Field: field.String(), + Detail: detail, + BadValue: "", + } +} + +// Duplicate returns a *Error indicating "duplicate value". This is +// used to report collisions of values that must be unique (e.g. names or IDs). +func Duplicate(field *Path, value interface{}) *Error { + return &Error{ + Type: ErrorTypeDuplicate, + Field: field.String(), + BadValue: value, + } +} + +// Invalid returns a *Error indicating "invalid value". This is used +// to report malformed values (e.g. failed regex match, too long, out of bounds). +func Invalid(field *Path, value interface{}, detail string) *Error { + return &Error{ + Type: ErrorTypeInvalid, + Field: field.String(), + BadValue: value, + Detail: detail, + } + +} + +// NotSupported returns a *Error indicating "unsupported value". +// This is used to report unknown values for enumerated fields (e.g. a list of +// valid values). +func NotSupported[T ~string](field *Path, value interface{}, validValues []T) *Error { + detail := "" + if len(validValues) > 0 { + quotedValues := make([]string, len(validValues)) + for i, v := range validValues { + quotedValues[i] = strconv.Quote(fmt.Sprint(v)) + } + detail = "supported values: " + strings.Join(quotedValues, ", ") + } + return &Error{ + Type: ErrorTypeNotSupported, + Field: field.String(), + BadValue: value, + Detail: detail, + } +} + +// Forbidden returns a *Error indicating "forbidden". This is used to +// report valid (as per formatting rules) values which would be accepted under +// some conditions, but which are not permitted by current conditions (e.g. +// security policy). +func Forbidden(field *Path, detail string) *Error { + return &Error{ + Type: ErrorTypeForbidden, + Field: field.String(), + Detail: detail, + BadValue: "", + } +} + +// TooLong returns a *Error indicating "too long". This is used to report that +// the given value is too long. This is similar to Invalid, but the returned +// error will not include the too-long value. If maxLength is negative, it will +// be included in the message. The value argument is not used. +func TooLong(field *Path, _ interface{}, maxLength int) *Error { + var msg string + if maxLength >= 0 { + bs := "bytes" + if maxLength == 1 { + bs = "byte" + } + msg = fmt.Sprintf("may not be more than %d %s", maxLength, bs) + } else { + msg = "value is too long" + } + return &Error{ + Type: ErrorTypeTooLong, + Field: field.String(), + BadValue: "", + Detail: msg, + } +} + +// TooLongCharacters returns a *Error indicating "too long". This is used to report that +// the given value is too long in characters (including multi-byte characters). +// This is similar to Invalid, but the returned error will not include the too-long value. +// If maxLength is negative, it will be included in the message. The value argument is not used. +func TooLongCharacters[T ~string](field *Path, _ T, maxLength int) *Error { + var msg string + if maxLength >= 0 { + bs := "characters" + if maxLength == 1 { + bs = "character" + } + msg = fmt.Sprintf("may not be more than %d %s", maxLength, bs) + } else { + msg = "value is too long" + } + return &Error{ + Type: ErrorTypeTooLong, + Field: field.String(), + BadValue: "", + Detail: msg, + } +} + +// TooLongMaxLength returns a *Error indicating "too long". +// Deprecated: Use TooLong instead. +func TooLongMaxLength(field *Path, value interface{}, maxLength int) *Error { + return TooLong(field, "", maxLength) +} + +// TooMany returns a *Error indicating "too many". This is used to +// report that a given list has too many items. This is similar to TooLong, +// but the returned error indicates quantity instead of length. +func TooMany(field *Path, actualQuantity, maxQuantity int) *Error { + var msg string + + if maxQuantity >= 0 { + is := "items" + if maxQuantity == 1 { + is = "item" + } + msg = fmt.Sprintf("must have at most %d %s", maxQuantity, is) + } else { + msg = "has too many items" + } + + var actual interface{} + if actualQuantity >= 0 { + actual = actualQuantity + } else { + actual = omitValue + } + + return &Error{ + Type: ErrorTypeTooMany, + Field: field.String(), + BadValue: actual, + Detail: msg, + } +} + +// InternalError returns a *Error indicating "internal error". This is used +// to signal that an error was found that was not directly related to user +// input. The err argument must be non-nil. +func InternalError(field *Path, err error) *Error { + return &Error{ + Type: ErrorTypeInternal, + Field: field.String(), + BadValue: err, + Detail: err.Error(), + } +} + +// TooShort returns a *Error indicating "too short". This is used to report that +// the given value is too short in characters. This is similar to Invalid. +// If minLength is non-negative, it will be included in the message. +func TooShort[T ~string](field *Path, value T, minLength int) *Error { + var msg string + if minLength >= 0 { + bs := "characters" + if minLength == 1 { + bs = "character" + } + msg = fmt.Sprintf("must be at least %d %s", minLength, bs) + } else { + msg = "value is too short" + } + return &Error{ + Type: ErrorTypeTooShort, + Field: field.String(), + BadValue: value, + Detail: msg, + } +} + +// ErrorList holds a set of Errors. It is plausible that we might one day have +// non-field errors in this same umbrella package, but for now we don't, so +// we can keep it simple and leave ErrorList here. +type ErrorList []*Error + +// NewErrorTypeMatcher returns an errors.Matcher that returns true +// if the provided error is a Error and has the provided ErrorType. +func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher { + return func(err error) bool { + if e, ok := err.(*Error); ok { + return e.Type == t + } + return false + } +} + +// WithOrigin sets the origin for all errors in the list and returns the updated list. +func (list ErrorList) WithOrigin(origin string) ErrorList { + for _, err := range list { + err.Origin = origin + } + return list +} + +// MarkCoveredByDeclarative marks all errors in the list as covered by declarative validation. +func (list ErrorList) MarkCoveredByDeclarative() ErrorList { + for _, err := range list { + err.CoveredByDeclarative = true + } + return list +} + +// PrefixDetail adds a prefix to the Detail for all errors in the list and returns the updated list. +func (list ErrorList) PrefixDetail(prefix string) ErrorList { + for _, err := range list { + err.Detail = prefix + err.Detail + } + return list +} + +// ToAggregate converts the ErrorList into an errors.Aggregate. +func (list ErrorList) ToAggregate() utilerrors.Aggregate { + if len(list) == 0 { + return nil + } + errs := make([]error, 0, len(list)) + errorMsgs := sets.NewString() + for _, err := range list { + msg := fmt.Sprintf("%v", err) + if errorMsgs.Has(msg) { + continue + } + errorMsgs.Insert(msg) + errs = append(errs, err) + } + return utilerrors.NewAggregate(errs) +} + +func fromAggregate(agg utilerrors.Aggregate) ErrorList { + errs := agg.Errors() + list := make(ErrorList, len(errs)) + for i := range errs { + list[i] = errs[i].(*Error) + } + return list +} + +// Filter removes items from the ErrorList that match the provided fns. +func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList { + err := utilerrors.FilterOut(list.ToAggregate(), fns...) + if err == nil { + return nil + } + // FilterOut takes an Aggregate and returns an Aggregate + return fromAggregate(err.(utilerrors.Aggregate)) +} + +// ExtractCoveredByDeclarative returns a new ErrorList containing only the errors that should be covered by declarative validation. +func (list ErrorList) ExtractCoveredByDeclarative() ErrorList { + newList := ErrorList{} + for _, err := range list { + if err.CoveredByDeclarative { + newList = append(newList, err) + } + } + return newList +} + +// MarkAlpha marks the error as an alpha validation error. +func (e *Error) MarkAlpha() *Error { + e.ValidationStabilityLevel = stabilityLevelAlpha + return e +} + +// MarkShortCircuitedInDV marks that this error is not returned by declarative validations, because DV returns before running the current validation. Handwritten validations still return it. +func (e *Error) MarkShortCircuitedInDV() *Error { + e.ShortCircuitedInDeclarative = true + return e +} + +// MarkAlpha marks the errors as alpha validation errors. +func (list ErrorList) MarkAlpha() ErrorList { + for _, err := range list { + err.ValidationStabilityLevel = stabilityLevelAlpha + } + return list +} + +// MarkBeta marks the error as a beta validation error. +func (e *Error) MarkBeta() *Error { + e.ValidationStabilityLevel = stabilityLevelBeta + return e +} + +// MarkBeta marks the errors as beta validation errors. +func (list ErrorList) MarkBeta() ErrorList { + for _, err := range list { + err.ValidationStabilityLevel = stabilityLevelBeta + } + return list +} + +func (e *Error) MarkFromImperative() *Error { + e.FromImperative = true + return e +} + +func (list ErrorList) MarkFromImperative() ErrorList { + for _, err := range list { + err.FromImperative = true + } + return list +} + +// MarkShortCircuit marks the errors as short-circuit errors. +func (list ErrorList) MarkShortCircuit() ErrorList { + for _, err := range list { + err.ShortCircuit = true + } + return list +} + +// RemoveCoveredByDeclarative returns a new ErrorList containing only the errors that should not be covered by declarative validation. +func (list ErrorList) RemoveCoveredByDeclarative() ErrorList { + newList := ErrorList{} + for _, err := range list { + if !err.CoveredByDeclarative { + newList = append(newList, err) + } + } + return newList +} + +// TooFew returns a *Error indicating "too few". This is used to +// report that a given list has too few items. This is similar to TooLong, +// but the returned error indicates quantity instead of length. +func TooFew(field *Path, actualQuantity, minQuantity int) *Error { + var msg string + + if minQuantity >= 0 { + is := "items" + if minQuantity == 1 { + is = "item" + } + msg = fmt.Sprintf("must have at least %d %s", minQuantity, is) + } else { + msg = "has too few items" + } + + return &Error{ + Type: ErrorTypeTooFew, + Field: field.String(), + BadValue: actualQuantity, + Detail: msg, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go new file mode 100644 index 0000000000..229595d900 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/errors_test.go @@ -0,0 +1,725 @@ +/* +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 field + +import ( + "fmt" + "reflect" + "testing" + "time" + + "k8s.io/utils/ptr" +) + +func TestMakeFuncs(t *testing.T) { + testCases := []struct { + fn func() *Error + expected ErrorType + }{ + { + func() *Error { return Invalid(NewPath("f"), "v", "d") }, + ErrorTypeInvalid, + }, + { + func() *Error { return NotSupported[string](NewPath("f"), "v", nil) }, + ErrorTypeNotSupported, + }, + { + func() *Error { return Duplicate(NewPath("f"), "v") }, + ErrorTypeDuplicate, + }, + { + func() *Error { return NotFound(NewPath("f"), "v") }, + ErrorTypeNotFound, + }, + { + func() *Error { return Required(NewPath("f"), "d") }, + ErrorTypeRequired, + }, + { + func() *Error { return InternalError(NewPath("f"), fmt.Errorf("e")) }, + ErrorTypeInternal, + }, + } + + for _, testCase := range testCases { + err := testCase.fn() + if err.Type != testCase.expected { + t.Errorf("expected Type %q, got %q", testCase.expected, err.Type) + } + } +} + +func TestToAggregate(t *testing.T) { + testCases := struct { + ErrList []ErrorList + NumExpectedErrs []int + }{ + []ErrorList{ + nil, + {}, + {Invalid(NewPath("f"), "v", "d")}, + {Invalid(NewPath("f"), "v", "d"), Invalid(NewPath("f"), "v", "d")}, + {Invalid(NewPath("f"), "v", "d"), InternalError(NewPath(""), fmt.Errorf("e"))}, + }, + []int{ + 0, + 0, + 1, + 1, + 2, + }, + } + + if len(testCases.ErrList) != len(testCases.NumExpectedErrs) { + t.Errorf("Mismatch: length of NumExpectedErrs does not match length of ErrList") + } + for i, tc := range testCases.ErrList { + agg := tc.ToAggregate() + numErrs := 0 + + if agg != nil { + numErrs = len(agg.Errors()) + } + if numErrs != testCases.NumExpectedErrs[i] { + t.Errorf("[%d] Expected %d, got %d", i, testCases.NumExpectedErrs[i], numErrs) + } + + if len(tc) == 0 { + if agg != nil { + t.Errorf("[%d] Expected nil, got %#v", i, agg) + } + } else if agg == nil { + t.Errorf("[%d] Expected non-nil", i) + } + } +} + +func TestErrListFilter(t *testing.T) { + list := ErrorList{ + Invalid(NewPath("test.field"), "", ""), + Invalid(NewPath("field.test"), "", ""), + Duplicate(NewPath("test"), "value"), + } + if len(list.Filter(NewErrorTypeMatcher(ErrorTypeDuplicate))) != 2 { + t.Errorf("should not filter") + } + if len(list.Filter(NewErrorTypeMatcher(ErrorTypeInvalid))) != 1 { + t.Errorf("should filter") + } +} + +func TestErrorOrigin(t *testing.T) { + err := Invalid(NewPath("field"), "value", "detail") + + // Test WithOrigin + newErr := err.WithOrigin("origin1") + if newErr.Origin != "origin1" { + t.Errorf("Expected Origin to be 'origin1', got '%s'", newErr.Origin) + } + if err.Origin != "origin1" { + t.Errorf("Expected Origin to be 'origin1', got '%s'", err.Origin) + } +} + +func TestErrorListOrigin(t *testing.T) { + // Create an ErrorList with multiple errors + list := ErrorList{ + Invalid(NewPath("field1"), "value1", "detail1"), + Invalid(NewPath("field2"), "value2", "detail2"), + Required(NewPath("field3"), "detail3"), + } + + // Test WithOrigin + newList := list.WithOrigin("origin1") + // Check that WithOrigin returns the modified list + for i, err := range newList { + if err.Origin != "origin1" { + t.Errorf("Error %d: Expected Origin to be 'origin2', got '%s'", i, err.Origin) + } + } + + // Check that the original list was also modified (WithOrigin modifies and returns the same list) + for i, err := range list { + if err.Origin != "origin1" { + t.Errorf("Error %d: Expected original list Origin to be 'origin2', got '%s'", i, err.Origin) + } + } +} + +func TestErrorMarkDeclarative(t *testing.T) { + // Test for single Error + err := Invalid(NewPath("field"), "value", "detail") + if err.CoveredByDeclarative { + t.Errorf("New error should not be declarative by default") + } + + // Mark as declarative + err.MarkCoveredByDeclarative() //nolint:errcheck // The "error" here is not an unexpected error from the function. + if !err.CoveredByDeclarative { + t.Errorf("Error should be declarative after marking") + } +} + +func TestErrorListMarkDeclarative(t *testing.T) { + // Test for ErrorList + list := ErrorList{ + Invalid(NewPath("field1"), "value1", "detail1"), + Invalid(NewPath("field2"), "value2", "detail2"), + } + + // Verify none are declarative by default + for i, err := range list { + if err.CoveredByDeclarative { + t.Errorf("Error %d should not be declarative by default", i) + } + } + + // Mark list as declarative + list.MarkCoveredByDeclarative() + + // Verify all errors in the list are now declarative + for i, err := range list { + if !err.CoveredByDeclarative { + t.Errorf("Error %d should be declarative after marking the list", i) + } + } +} + +func TestErrorMarkFromImperative(t *testing.T) { + // Test for single Error + err := Invalid(NewPath("field"), "value", "detail") + if err.FromImperative { + t.Errorf("New error should not be from imperative by default") + } + + // Mark as FromImperative + err.MarkFromImperative() //nolint:errcheck // The "error" here is not an unexpected error from the function. + if !err.FromImperative { + t.Errorf("Error should be from imperative after marking") + } +} + +func TestErrorListMarkFromImperative(t *testing.T) { + // Test for ErrorList + list := ErrorList{ + Invalid(NewPath("field1"), "value1", "detail1"), + Invalid(NewPath("field2"), "value2", "detail2"), + } + + // Verify none are from imperative by default + for i, err := range list { + if err.FromImperative { + t.Errorf("Error %d should not be from imperative by default", i) + } + } + + // Mark list as from imperative + list.MarkFromImperative() + + // Verify all errors in the list are now from imperative + for i, err := range list { + if !err.FromImperative { + t.Errorf("Error %d should be from imperative after marking the list", i) + } + } +} + +func TestErrorListExtractCoveredByDeclarative(t *testing.T) { + testCases := []struct { + list ErrorList + expectedList ErrorList + }{ + { + ErrorList{}, + ErrorList{}, + }, + { + ErrorList{Invalid(NewPath("field1"), nil, "")}, + ErrorList{}, + }, + { + ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative(), Required(NewPath("field2"), "detail2")}, + ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative()}, + }, + } + + for _, tc := range testCases { + got := tc.list.ExtractCoveredByDeclarative() + if !reflect.DeepEqual(got, tc.expectedList) { + t.Errorf("For list %v, expected %v, got %v", tc.list, tc.expectedList, got) + } + } +} + +func TestErrorListRemoveCoveredByDeclarative(t *testing.T) { + testCases := []struct { + list ErrorList + expectedList ErrorList + }{ + { + ErrorList{}, + ErrorList{}, + }, + { + ErrorList{Invalid(NewPath("field1"), nil, "").MarkCoveredByDeclarative(), Required(NewPath("field2"), "detail2")}, + ErrorList{Required(NewPath("field2"), "detail2")}, + }, + } + + for _, tc := range testCases { + got := tc.list.RemoveCoveredByDeclarative() + if !reflect.DeepEqual(got, tc.expectedList) { + t.Errorf("For list %v, expected %v, got %v", tc.list, tc.expectedList, got) + } + } +} + +func TestErrorFormatting(t *testing.T) { + cases := []struct { + name string + input *Error + expect string + }{{ + name: "required", + input: &Error{ + Type: ErrorTypeRequired, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Required value: the details`, + }, { + name: "required func", + input: Required(NewPath("path.to.field"), "the details"), + expect: `path.to.field: Required value: the details`, + }, { + name: "forbidden", + input: &Error{ + Type: ErrorTypeForbidden, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Forbidden: the details`, + }, { + name: "forbidden func", + input: Forbidden(NewPath("path.to.field"), "the details"), + expect: `path.to.field: Forbidden: the details`, + }, { + name: "too long", + input: &Error{ + Type: ErrorTypeTooLong, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Too long: the details`, + }, { + name: "too long func(1)", + input: TooLong(NewPath("path.to.field"), "the value", 1), + expect: `path.to.field: Too long: may not be more than 1 byte`, + }, { + name: "too long func(2)", + input: TooLong(NewPath("path.to.field"), "the value", 2), + expect: `path.to.field: Too long: may not be more than 2 bytes`, + }, { + name: "too long func(-1)", + input: TooLong(NewPath("path.to.field"), "the value", -1), + expect: `path.to.field: Too long: value is too long`, + }, { + name: "too many", + input: &Error{ + Type: ErrorTypeTooMany, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Too many: "the value": the details`, + }, { + name: "too many func(2, 1)", + input: TooMany(NewPath("path.to.field"), 2, 1), + expect: `path.to.field: Too many: 2: must have at most 1 item`, + }, { + name: "too many func(3, 2)", + input: TooMany(NewPath("path.to.field"), 3, 2), + expect: `path.to.field: Too many: 3: must have at most 2 items`, + }, { + name: "too many func(2, -1)", + input: TooMany(NewPath("path.to.field"), 2, -1), + expect: `path.to.field: Too many: 2: has too many items`, + }, { + name: "too many func(-1, 1)", + input: TooMany(NewPath("path.to.field"), -1, 1), + expect: `path.to.field: Too many: must have at most 1 item`, + }, { + name: "internal error", + input: &Error{ + Type: ErrorTypeInternal, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Internal error: the details`, + }, { + name: "internal error func", + input: InternalError(NewPath("path.to.field"), fmt.Errorf("the error")), + expect: `path.to.field: Internal error: the error`, + }, { + name: "invalid string", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: "the value": the details`, + }, { + name: "invalid string type", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: StringType("the value"), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: "the value": the details`, + }, { + name: "invalid int", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: -42, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: -42: the details`, + }, { + name: "invalid bool", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: true, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: true: the details`, + }, { + name: "invalid struct", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: mkTinyStruct(), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: {"stringField":"stringval","intField":9376,"boolField":true}: the details`, + }, { + name: "invalid list", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: []string{"one", "two", "three"}, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: ["one","two","three"]: the details`, + }, { + name: "invalid map", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: map[string]int{"one": 1, "two": 2, "three": 3}, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: {"one":1,"three":3,"two":2}: the details`, + }, { + name: "invalid time", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: time.Time{}, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: "0001-01-01T00:00:00Z": the details`, + }, { + name: "invalid omitValue", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: omitValue, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: the details`, + }, { + name: "invalid untyped nil", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: nil, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: null: the details`, + }, { + name: "invalid typed nil", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: (*string)(nil), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: null: the details`, + }, { + name: "invalid string ptr", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: ptr.To("the value"), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: "the value": the details`, + }, { + name: "invalid string type ptr", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: ptr.To(StringType("the value")), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: "the value": the details`, + }, { + name: "invalid int ptr", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: ptr.To(-42), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: -42: the details`, + }, { + name: "invalid bool ptr", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: ptr.To(true), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: true: the details`, + }, { + name: "invalid struct ptr", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: ptr.To(mkTinyStruct()), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: {"stringField":"stringval","intField":9376,"boolField":true}: the details`, + }, { + name: "invalid list ptr", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: ptr.To([]string{"one", "two", "three"}), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: ["one","two","three"]: the details`, + }, { + name: "invalid map ptr", + input: &Error{ + Type: ErrorTypeInvalid, + Field: "path.to.field", + BadValue: ptr.To(map[string]int{"one": 1, "two": 2, "three": 3}), + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: {"one":1,"three":3,"two":2}: the details`, + }, { + name: "invalid func", + input: Invalid(NewPath("path.to.field"), "the value", "the details"), + expect: `path.to.field: Invalid value: "the value": the details`, + }, { + name: "not found", + input: &Error{ + Type: ErrorTypeNotFound, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Not found: "the value": the details`, + }, { + name: "not supported", + input: &Error{ + Type: ErrorTypeNotSupported, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Unsupported value: "the value": the details`, + }, { + name: "not supported func", + input: NotSupported(NewPath("path.to.field"), "the value", []string{"val1", "val2"}), + expect: `path.to.field: Unsupported value: "the value": supported values: "val1", "val2"`, + }, { + name: "duplicate", + input: &Error{ + Type: ErrorTypeDuplicate, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Duplicate value: "the value": the details`, + }, { + name: "duplicate func", + input: Duplicate(NewPath("path.to.field"), "the value"), + expect: `path.to.field: Duplicate value: "the value"`, + }, { + name: "type invalid", + input: &Error{ + Type: ErrorTypeTypeInvalid, + Field: "path.to.field", + BadValue: "the value", + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: "the value": the details`, + }, { + name: "type invalid func", + input: TypeInvalid(NewPath("path.to.field"), "the value", "the details"), + expect: `path.to.field: Invalid value: "the value": the details`, + }, { + name: "failed marshal stringer", + input: &Error{ + Type: ErrorTypeTypeInvalid, + Field: "path.to.field", + BadValue: SelfMarshalerStringer{"invisible"}, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: magic: the details`, + }, { + name: "failed marshal non-stringer", + input: &Error{ + Type: ErrorTypeTypeInvalid, + Field: "path.to.field", + BadValue: SelfMarshalerNonStringer{"visible"}, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Invalid value: field.SelfMarshalerNonStringer{S:"visible"}: the details`, + }, { + name: "unknown error type", + input: &Error{ + Type: "not real", + Field: "path.to.field", + BadValue: SelfMarshalerNonStringer{"visible"}, + Detail: "the details", + Origin: "theOrigin", + CoveredByDeclarative: true, + }, + expect: `path.to.field: Internal error: unhandled error code: : please report this: the details`, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + result := tc.input.Error() + if want := tc.expect; result != want { + t.Errorf("wrong error string:\n expected: %q\n got: %q", want, result) + } + }) + } +} + +type StringType string + +type TinyStruct struct { + StringField string `json:"stringField"` + IntField int `json:"intField"` + BoolField bool `json:"boolField"` +} + +func mkTinyStruct() TinyStruct { + return TinyStruct{ + StringField: "stringval", + IntField: 9376, + BoolField: true, + } +} + +type SelfMarshalerStringer struct{ S string } + +func (SelfMarshalerStringer) MarshalJSON() ([]byte, error) { + return nil, fmt.Errorf("this always fails") +} + +func (SelfMarshalerStringer) String() string { + return "magic" +} + +type SelfMarshalerNonStringer struct{ S string } + +func (SelfMarshalerNonStringer) MarshalJSON() ([]byte, error) { + return nil, fmt.Errorf("this always fails") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/path.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/path.go new file mode 100644 index 0000000000..daccb05890 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/path.go @@ -0,0 +1,117 @@ +/* +Copyright 2015 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 field + +import ( + "bytes" + "fmt" + "strconv" +) + +type pathOptions struct { + path *Path +} + +// PathOption modifies a pathOptions +type PathOption func(o *pathOptions) + +// WithPath generates a PathOption +func WithPath(p *Path) PathOption { + return func(o *pathOptions) { + o.path = p + } +} + +// ToPath produces *Path from a set of PathOption +func ToPath(opts ...PathOption) *Path { + c := &pathOptions{} + for _, opt := range opts { + opt(c) + } + return c.path +} + +// Path represents the path from some root to a particular field. +type Path struct { + name string // the name of this field or "" if this is an index + index string // if name == "", this is a subscript (index or map key) of the previous element + parent *Path // nil if this is the root element +} + +// NewPath creates a root Path object. +func NewPath(name string, moreNames ...string) *Path { + r := &Path{name: name, parent: nil} + for _, anotherName := range moreNames { + r = &Path{name: anotherName, parent: r} + } + return r +} + +// Root returns the root element of this Path. +func (p *Path) Root() *Path { + for ; p.parent != nil; p = p.parent { + // Do nothing. + } + return p +} + +// Child creates a new Path that is a child of the method receiver. +func (p *Path) Child(name string, moreNames ...string) *Path { + r := NewPath(name, moreNames...) + r.Root().parent = p + return r +} + +// Index indicates that the previous Path is to be subscripted by an int. +// This sets the same underlying value as Key. +func (p *Path) Index(index int) *Path { + return &Path{index: strconv.Itoa(index), parent: p} +} + +// Key indicates that the previous Path is to be subscripted by a string. +// This sets the same underlying value as Index. +func (p *Path) Key(key string) *Path { + return &Path{index: key, parent: p} +} + +// String produces a string representation of the Path. +func (p *Path) String() string { + if p == nil { + return "" + } + // make a slice to iterate + elems := []*Path{} + for ; p != nil; p = p.parent { + elems = append(elems, p) + } + + // iterate, but it has to be backwards + buf := bytes.NewBuffer(nil) + for i := range elems { + p := elems[len(elems)-1-i] + if p.parent != nil && len(p.name) > 0 { + // This is either the root or it is a subscript. + buf.WriteString(".") + } + if len(p.name) > 0 { + buf.WriteString(p.name) + } else { + fmt.Fprintf(buf, "[%s]", p.index) + } + } + return buf.String() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/path_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/path_test.go new file mode 100644 index 0000000000..d2f568c36f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/field/path_test.go @@ -0,0 +1,123 @@ +/* +Copyright 2015 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 field + +import "testing" + +func TestPath(t *testing.T) { + testCases := []struct { + op func(*Path) *Path + expected string + }{ + { + func(p *Path) *Path { return p }, + "root", + }, + { + func(p *Path) *Path { return p.Child("first") }, + "root.first", + }, + { + func(p *Path) *Path { return p.Child("second") }, + "root.first.second", + }, + { + func(p *Path) *Path { return p.Index(0) }, + "root.first.second[0]", + }, + { + func(p *Path) *Path { return p.Child("third") }, + "root.first.second[0].third", + }, + { + func(p *Path) *Path { return p.Index(93) }, + "root.first.second[0].third[93]", + }, + { + func(p *Path) *Path { return p.parent }, + "root.first.second[0].third", + }, + { + func(p *Path) *Path { return p.parent }, + "root.first.second[0]", + }, + { + func(p *Path) *Path { return p.Key("key") }, + "root.first.second[0][key]", + }, + } + + root := NewPath("root") + p := root + for i, tc := range testCases { + p = tc.op(p) + if p.String() != tc.expected { + t.Errorf("[%d] Expected %q, got %q", i, tc.expected, p.String()) + } + if p.Root() != root { + t.Errorf("[%d] Wrong root: %#v", i, p.Root()) + } + } +} + +func TestPathMultiArg(t *testing.T) { + testCases := []struct { + op func(*Path) *Path + expected string + }{ + { + func(p *Path) *Path { return p }, + "root.first", + }, + { + func(p *Path) *Path { return p.Child("second", "third") }, + "root.first.second.third", + }, + { + func(p *Path) *Path { return p.Index(0) }, + "root.first.second.third[0]", + }, + { + func(p *Path) *Path { return p.parent }, + "root.first.second.third", + }, + { + func(p *Path) *Path { return p.parent }, + "root.first.second", + }, + { + func(p *Path) *Path { return p.parent }, + "root.first", + }, + { + func(p *Path) *Path { return p.parent }, + "root", + }, + } + + root := NewPath("root", "first") + p := root + for i, tc := range testCases { + p = tc.op(p) + if p.String() != tc.expected { + t.Errorf("[%d] Expected %q, got %q", i, tc.expected, p.String()) + } + if p.Root() != root.Root() { + t.Errorf("[%d] Wrong root: %#v", i, p.Root()) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/ip.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/ip.go new file mode 100644 index 0000000000..95f4e6218f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/ip.go @@ -0,0 +1,280 @@ +/* +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 validation + +import ( + "fmt" + "net" + "net/netip" + "slices" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/klog/v2" + netutils "k8s.io/utils/net" +) + +func parseIP(fldPath *field.Path, value string, strictValidation bool) (net.IP, field.ErrorList) { + var allErrors field.ErrorList + + ip := netutils.ParseIPSloppy(value) + if ip == nil { + allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IP address, (e.g. 10.9.8.7 or 2001:db8::ffff)")) + return nil, allErrors + } + + if strictValidation { + addr, err := netip.ParseAddr(value) + if err != nil { + // If netutils.ParseIPSloppy parsed it, but netip.ParseAddr + // doesn't, then it must have illegal leading 0s. + allErrors = append(allErrors, field.Invalid(fldPath, value, "must not have leading 0s")) + } + if addr.Is4In6() { + allErrors = append(allErrors, field.Invalid(fldPath, value, "must not be an IPv4-mapped IPv6 address")) + } + } + + return ip, allErrors +} + +// IsValidIPForLegacyField tests that the argument is a valid IP address for a "legacy" +// API field that predates strict IP validation. In particular, this allows IPs that are +// not in canonical form (e.g., "FE80:0:0:0:0:0:0:0abc" instead of "fe80::abc"). +// +// If strictValidation is false, this also allows IPs in certain invalid or ambiguous +// formats: +// +// 1. IPv4 IPs are allowed to have leading "0"s in octets (e.g. "010.002.003.004"). +// Historically, net.ParseIP (and later netutils.ParseIPSloppy) simply ignored leading +// "0"s in IPv4 addresses, but most libc-based software treats 0-prefixed IPv4 octets +// as octal, meaning different software might interpret the same string as two +// different IPs, potentially leading to security issues. (Current net.ParseIP and +// netip.ParseAddr simply reject inputs with leading "0"s.) +// +// 2. IPv4-mapped IPv6 IPs (e.g. "::ffff:1.2.3.4") are allowed. These can also lead to +// different software interpreting the value in different ways, because they may be +// treated as IPv4 by some software and IPv6 by other software. (net.ParseIP and +// netip.ParseAddr both allow these, but there are no use cases for representing IPv4 +// addresses as IPv4-mapped IPv6 addresses in Kubernetes.) +// +// Alternatively, when validating an update to an existing field, you can pass a list of +// IP values from the old object that should be accepted if they appear in the new object +// even if they are not valid. +// +// This function should only be used to validate the existing fields that were +// historically validated in this way, and strictValidation should be true unless the +// StrictIPCIDRValidation feature gate is disabled. Use IsValidIP for parsing new fields. +func IsValidIPForLegacyField(fldPath *field.Path, value string, strictValidation bool, validOldIPs []string) field.ErrorList { + if slices.Contains(validOldIPs, value) { + return nil + } + _, allErrors := parseIP(fldPath, value, strictValidation) + return allErrors.WithOrigin("format=ip-sloppy") +} + +// IsValidIP tests that the argument is a valid IP address, according to current +// Kubernetes standards for IP address validation. +func IsValidIP(fldPath *field.Path, value string) field.ErrorList { + ip, allErrors := parseIP(fldPath, value, true) + if len(allErrors) != 0 { + return allErrors.WithOrigin("format=ip-strict") + } + + if value != ip.String() { + allErrors = append(allErrors, field.Invalid(fldPath, value, fmt.Sprintf("must be in canonical form (%q)", ip.String()))) + } + return allErrors.WithOrigin("format=ip-strict") +} + +// GetWarningsForIP returns warnings for IP address values in non-standard forms. This +// should only be used with fields that are validated with IsValidIPForLegacyField(). +func GetWarningsForIP(fldPath *field.Path, value string) []string { + ip := netutils.ParseIPSloppy(value) + if ip == nil { + //nolint:logcheck // Should not be reached. + klog.ErrorS(nil, "GetWarningsForIP called on value that was not validated with IsValidIPForLegacyField", "field", fldPath, "value", value) + return nil + } + + addr, _ := netip.ParseAddr(value) + if !addr.IsValid() || addr.Is4In6() { + // This catches 2 cases: leading 0s (if ParseIPSloppy() accepted it but + // ParseAddr() doesn't) or IPv4-mapped IPv6 (.Is4In6()). Either way, + // re-stringifying the net.IP value will give the preferred form. + return []string{ + fmt.Sprintf("%s: non-standard IP address %q is invalid: use %q", fldPath, value, ip.String()), + } + } + + // If ParseIPSloppy() and ParseAddr() both accept it then it's fully valid, though + // it may be non-canonical. + if addr.Is6() && addr.String() != value { + return []string{ + fmt.Sprintf("%s: IPv6 address %q should be in RFC 5952 canonical format (%q)", fldPath, value, addr.String()), + } + } + + return nil +} + +func parseCIDR(fldPath *field.Path, value string, strictValidation bool) (*net.IPNet, field.ErrorList) { + var allErrors field.ErrorList + + _, ipnet, err := netutils.ParseCIDRSloppy(value) + if err != nil { + allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid CIDR value, (e.g. 10.9.8.0/24 or 2001:db8::/64)")) + return nil, allErrors + } + + if strictValidation { + prefix, err := netip.ParsePrefix(value) + if err != nil { + // If netutils.ParseCIDRSloppy parsed it, but netip.ParsePrefix + // doesn't, then it must have illegal leading 0s (either in the + // IP part or the prefix). + allErrors = append(allErrors, field.Invalid(fldPath, value, "must not have leading 0s in IP or prefix length")) + } else if prefix.Addr().Is4In6() { + allErrors = append(allErrors, field.Invalid(fldPath, value, "must not have an IPv4-mapped IPv6 address")) + } else if prefix.Addr() != prefix.Masked().Addr() { + allErrors = append(allErrors, field.Invalid(fldPath, value, "must not have bits set beyond the prefix length")) + } + } + + return ipnet, allErrors +} + +// IsValidCIDRForLegacyField tests that the argument is a valid CIDR value for a "legacy" +// API field that predates strict IP validation. In particular, this allows IPs that are +// not in canonical form (e.g., "FE80:0abc:0:0:0:0:0:0/64" instead of "fe80:abc::/64"). +// +// If strictValidation is false, this also allows CIDR values in certain invalid or +// ambiguous formats: +// +// 1. The IP part of the CIDR value is parsed as with IsValidIPForLegacyField with +// strictValidation=false. +// +// 2. The CIDR value is allowed to be either a "subnet"/"mask" (with the lower bits after +// the prefix length all being 0), or an "interface address" as with `ip addr` (with a +// complete IP address and associated subnet length). With strict validation, the +// value is required to be in "subnet"/"mask" form. +// +// 3. The prefix length is allowed to have leading 0s. +// +// Alternatively, when validating an update to an existing field, you can pass a list of +// CIDR values from the old object that should be accepted if they appear in the new +// object even if they are not valid. +// +// This function should only be used to validate the existing fields that were +// historically validated in this way, and strictValidation should be true unless the +// StrictIPCIDRValidation feature gate is disabled. Use IsValidCIDR or +// IsValidInterfaceAddress for parsing new fields. +func IsValidCIDRForLegacyField(fldPath *field.Path, value string, strictValidation bool, validOldCIDRs []string) field.ErrorList { + if slices.Contains(validOldCIDRs, value) { + return nil + } + + _, allErrors := parseCIDR(fldPath, value, strictValidation) + return allErrors +} + +// IsValidCIDR tests that the argument is a valid CIDR value, according to current +// Kubernetes standards for CIDR validation. This function is only for +// "subnet"/"mask"-style CIDR values (e.g., "192.168.1.0/24", with no bits set beyond the +// prefix length). Use IsValidInterfaceAddress for "ifaddr"-style CIDR values. +func IsValidCIDR(fldPath *field.Path, value string) field.ErrorList { + ipnet, allErrors := parseCIDR(fldPath, value, true) + if len(allErrors) != 0 { + return allErrors + } + + if value != ipnet.String() { + allErrors = append(allErrors, field.Invalid(fldPath, value, fmt.Sprintf("must be in canonical form (%q)", ipnet.String()))) + } + return allErrors +} + +// GetWarningsForCIDR returns warnings for CIDR values in non-standard forms. This should +// only be used with fields that are validated with IsValidCIDRForLegacyField(). +func GetWarningsForCIDR(fldPath *field.Path, value string) []string { + ip, ipnet, err := netutils.ParseCIDRSloppy(value) + if err != nil { + //nolint:logcheck // Should not be reached. + klog.ErrorS(err, "GetWarningsForCIDR called on value that was not validated with IsValidCIDRForLegacyField", "field", fldPath, "value", value) + return nil + } + + var warnings []string + + // Check for bits set after prefix length + if !ip.Equal(ipnet.IP) { + _, addrlen := ipnet.Mask.Size() + singleIPCIDR := fmt.Sprintf("%s/%d", ip.String(), addrlen) + warnings = append(warnings, + fmt.Sprintf("%s: CIDR value %q is ambiguous in this context (should be %q or %q?)", fldPath, value, ipnet.String(), singleIPCIDR), + ) + } + + prefix, _ := netip.ParsePrefix(value) + addr := prefix.Addr() + if !prefix.IsValid() || addr.Is4In6() { + // This catches 2 cases: leading 0s (if ParseCIDRSloppy() accepted it but + // ParsePrefix() doesn't) or IPv4-mapped IPv6 (.Is4In6()). Either way, + // re-stringifying the net.IPNet value will give the preferred form. + warnings = append(warnings, + fmt.Sprintf("%s: non-standard CIDR value %q is invalid: use %q", fldPath, value, ipnet.String()), + ) + } + + // If ParseCIDRSloppy() and ParsePrefix() both accept it then it's fully valid, + // though it may be non-canonical. But only check this if there are no other + // warnings, since either of the other warnings would also cause a round-trip + // failure. + if len(warnings) == 0 && addr.Is6() && prefix.String() != value { + warnings = append(warnings, + fmt.Sprintf("%s: IPv6 CIDR value %q should be in RFC 5952 canonical format (%q)", fldPath, value, prefix.String()), + ) + } + + return warnings +} + +// IsValidInterfaceAddress tests that the argument is a valid "ifaddr"-style CIDR value in +// canonical form (e.g., "192.168.1.5/24", with a complete IP address and associated +// subnet length). Use IsValidCIDR for "subnet"/"mask"-style CIDR values (e.g., +// "192.168.1.0/24"). +func IsValidInterfaceAddress(fldPath *field.Path, value string) field.ErrorList { + var allErrors field.ErrorList + ip, ipnet, err := netutils.ParseCIDRSloppy(value) + if err != nil { + allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid address in CIDR form, (e.g. 10.9.8.7/24 or 2001:db8::1/64)")) + return allErrors + } + + // The canonical form of `value` is not `ipnet.String()`, because `ipnet` doesn't + // include the bits after the prefix. We need to construct the canonical form + // ourselves from `ip` and `ipnet.Mask`. + maskSize, _ := ipnet.Mask.Size() + if netutils.IsIPv4(ip) && maskSize > net.IPv4len*8 { + // "::ffff:192.168.0.1/120" -> "192.168.0.1/24" + maskSize -= (net.IPv6len - net.IPv4len) * 8 + } + canonical := fmt.Sprintf("%s/%d", ip.String(), maskSize) + if value != canonical { + allErrors = append(allErrors, field.Invalid(fldPath, value, fmt.Sprintf("must be in canonical form (%q)", canonical))) + } + return allErrors +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/ip_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/ip_test.go new file mode 100644 index 0000000000..a96ced3cad --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/ip_test.go @@ -0,0 +1,709 @@ +/* +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 validation + +import ( + "reflect" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestIsValidIP(t *testing.T) { + testCases := []struct { + name string + in string + + err string + legacyErr string + legacyStrictErr string + }{ + // GOOD VALUES + { + name: "ipv4", + in: "1.2.3.4", + }, + { + name: "ipv4, all zeros", + in: "0.0.0.0", + }, + { + name: "ipv4, max", + in: "255.255.255.255", + }, + { + name: "ipv6", + in: "1234::abcd", + }, + { + name: "ipv6, all zeros, collapsed", + in: "::", + }, + { + name: "ipv6, max", + in: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + }, + + // NON-CANONICAL VALUES + { + name: "ipv6, all zeros, expanded (non-canonical)", + in: "0:0:0:0:0:0:0:0", + + err: `must be in canonical form ("::")`, + }, + { + name: "ipv6, leading 0s (non-canonical)", + in: "0001:002:03:4::", + + err: `must be in canonical form ("1:2:3:4::")`, + }, + { + name: "ipv6, capital letters (non-canonical)", + in: "1234::ABCD", + + err: `must be in canonical form ("1234::abcd")`, + }, + + // GOOD WITH LEGACY VALIDATION, BAD WITH STRICT VALIDATION + { + name: "ipv4 with leading 0s", + in: "1.1.1.01", + + err: "must not have leading 0s", + legacyErr: "", + legacyStrictErr: "must not have leading 0s", + }, + { + name: "ipv4-in-ipv6 value", + in: "::ffff:1.1.1.1", + + err: "must not be an IPv4-mapped IPv6 address", + legacyErr: "", + legacyStrictErr: "must not be an IPv4-mapped IPv6 address", + }, + + // BAD VALUES + { + name: "empty string", + in: "", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "junk", + in: "aaaaaaa", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "domain name", + in: "myhost.mydomain", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "cidr", + in: "1.2.3.0/24", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "ipv4 with out-of-range octets", + in: "1.2.3.400", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "ipv4 with negative octets", + in: "-1.0.0.0", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "ipv6 with out-of-range segment", + in: "2001:db8::10005", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "ipv4:port", + in: "1.2.3.4:80", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "ipv6 with brackets", + in: "[2001:db8::1]", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "[ipv6]:port", + in: "[2001:db8::1]:80", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "host:port", + in: "example.com:80", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "ipv6 with zone", + in: "1234::abcd%eth0", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + { + name: "ipv4 with zone", + in: "169.254.0.0%eth0", + + err: "must be a valid IP address", + legacyErr: "must be a valid IP address", + legacyStrictErr: "must be a valid IP address", + }, + } + + var badIPs []string + for _, tc := range testCases { + if tc.legacyStrictErr != "" { + badIPs = append(badIPs, tc.in) + } + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + errs := IsValidIP(field.NewPath(""), tc.in) + if tc.err == "" { + if len(errs) != 0 { + t.Errorf("expected %q to be valid but got: %v", tc.in, errs) + } + } else { + if len(errs) != 1 { + t.Errorf("expected %q to have 1 error but got: %v", tc.in, errs) + } else if !strings.Contains(errs[0].Detail, tc.err) { + t.Errorf("expected error for %q to contain %q but got: %q", tc.in, tc.err, errs[0].Detail) + } + } + + errs = IsValidIPForLegacyField(field.NewPath(""), tc.in, false, nil) + if tc.legacyErr == "" { + if len(errs) != 0 { + t.Errorf("expected %q to be valid according to IsValidIPForLegacyField but got: %v", tc.in, errs) + } + } else { + if len(errs) != 1 { + t.Errorf("expected %q to have 1 error from IsValidIPForLegacyField but got: %v", tc.in, errs) + } else if !strings.Contains(errs[0].Detail, tc.legacyErr) { + t.Errorf("expected error from IsValidIPForLegacyField for %q to contain %q but got: %q", tc.in, tc.legacyErr, errs[0].Detail) + } + } + + errs = IsValidIPForLegacyField(field.NewPath(""), tc.in, true, nil) + if tc.legacyStrictErr == "" { + if len(errs) != 0 { + t.Errorf("expected %q to be valid according to IsValidIPForLegacyField with strict validation, but got: %v", tc.in, errs) + } + } else { + if len(errs) != 1 { + t.Errorf("expected %q to have 1 error from IsValidIPForLegacyField with strict validation, but got: %v", tc.in, errs) + } else if !strings.Contains(errs[0].Detail, tc.legacyStrictErr) { + t.Errorf("expected error from IsValidIPForLegacyField with strict validation for %q to contain %q but got: %q", tc.in, tc.legacyStrictErr, errs[0].Detail) + } + } + + errs = IsValidIPForLegacyField(field.NewPath(""), tc.in, true, badIPs) + if len(errs) != 0 { + t.Errorf("expected %q to be accepted when using validOldIPs, but got: %v", tc.in, errs) + } + }) + } +} + +func TestGetWarningsForIP(t *testing.T) { + tests := []struct { + name string + fieldPath *field.Path + address string + want []string + }{ + { + name: "IPv4 No failures", + address: "192.12.2.2", + fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0), + want: nil, + }, + { + name: "IPv6 No failures", + address: "2001:db8::2", + fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0), + want: nil, + }, + { + name: "IPv4 with leading zeros", + address: "192.012.2.2", + fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0), + want: []string{ + `spec.clusterIPs[0]: non-standard IP address "192.012.2.2" is invalid: use "192.12.2.2"`, + }, + }, + { + name: "IPv4-mapped IPv6", + address: "::ffff:192.12.2.2", + fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0), + want: []string{ + `spec.clusterIPs[0]: non-standard IP address "::ffff:192.12.2.2" is invalid: use "192.12.2.2"`, + }, + }, + { + name: "IPv6 non-canonical format", + address: "2001:db8:0:0::2", + fieldPath: field.NewPath("spec").Child("loadBalancerIP"), + want: []string{ + `spec.loadBalancerIP: IPv6 address "2001:db8:0:0::2" should be in RFC 5952 canonical format ("2001:db8::2")`, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetWarningsForIP(tt.fieldPath, tt.address); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getWarningsForIP() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsValidCIDR(t *testing.T) { + testCases := []struct { + name string + in string + + err string + legacyErr string + legacyStrictErr string + }{ + // GOOD VALUES + { + name: "ipv4", + in: "1.0.0.0/8", + }, + { + name: "ipv4, all IPs", + in: "0.0.0.0/0", + }, + { + name: "ipv4, single IP", + in: "1.1.1.1/32", + }, + { + name: "ipv6", + in: "2001:4860:4860::/48", + }, + { + name: "ipv6, all IPs", + in: "::/0", + }, + { + name: "ipv6, single IP", + in: "::1/128", + }, + + // NON-CANONICAL VALUES + { + name: "ipv6, extra 0s (non-canonical)", + in: "2a00:79e0:2:0::/64", + + err: `must be in canonical form ("2a00:79e0:2::/64")`, + }, + { + name: "ipv6, capital letters (non-canonical)", + in: "2001:DB8::/64", + + err: `must be in canonical form ("2001:db8::/64")`, + }, + + // GOOD WITH LEGACY VALIDATION, BAD WITH STRICT VALIDATION + { + name: "ipv4 with leading 0s", + in: "1.1.01.0/24", + + err: "must not have leading 0s in IP", + legacyErr: "", + legacyStrictErr: "must not have leading 0s in IP", + }, + { + name: "ipv4-in-ipv6 with ipv4-sized prefix", + in: "::ffff:1.1.1.0/24", + + err: "must not have an IPv4-mapped IPv6 address", + legacyErr: "", + legacyStrictErr: "must not have an IPv4-mapped IPv6 address", + }, + { + name: "ipv4-in-ipv6 with ipv6-sized prefix", + in: "::ffff:1.1.1.0/120", + + err: "must not have an IPv4-mapped IPv6 address", + legacyErr: "", + legacyStrictErr: "must not have an IPv4-mapped IPv6 address", + }, + { + name: "ipv4 ifaddr", + in: "1.2.3.4/24", + + err: "must not have bits set beyond the prefix length", + legacyErr: "", + legacyStrictErr: "must not have bits set beyond the prefix length", + }, + { + name: "ipv6 ifaddr", + in: "2001:db8::1/64", + + err: "must not have bits set beyond the prefix length", + legacyErr: "", + legacyStrictErr: "must not have bits set beyond the prefix length", + }, + { + name: "prefix length with leading 0s", + in: "192.168.0.0/016", + + err: "must not have leading 0s", + legacyErr: "", + legacyStrictErr: "must not have leading 0s", + }, + + // BAD VALUES + { + name: "empty string", + in: "", + + err: "must be a valid CIDR value", + legacyErr: "must be a valid CIDR value", + legacyStrictErr: "must be a valid CIDR value", + }, + { + name: "junk", + in: "aaaaaaa", + + err: "must be a valid CIDR value", + legacyErr: "must be a valid CIDR value", + legacyStrictErr: "must be a valid CIDR value", + }, + { + name: "IP address", + in: "1.2.3.4", + + err: "must be a valid CIDR value", + legacyErr: "must be a valid CIDR value", + legacyStrictErr: "must be a valid CIDR value", + }, + { + name: "partial URL", + in: "192.168.0.1/healthz", + + err: "must be a valid CIDR value", + legacyErr: "must be a valid CIDR value", + legacyStrictErr: "must be a valid CIDR value", + }, + { + name: "partial URL 2", + in: "192.168.0.1/0/99", + + err: "must be a valid CIDR value", + legacyErr: "must be a valid CIDR value", + legacyStrictErr: "must be a valid CIDR value", + }, + { + name: "negative prefix length", + in: "192.168.0.0/-16", + + err: "must be a valid CIDR value", + legacyErr: "must be a valid CIDR value", + legacyStrictErr: "must be a valid CIDR value", + }, + { + name: "prefix length with sign", + in: "192.168.0.0/+16", + + err: "must be a valid CIDR value", + legacyErr: "must be a valid CIDR value", + legacyStrictErr: "must be a valid CIDR value", + }, + } + + var badCIDRs []string + for _, tc := range testCases { + if tc.legacyStrictErr != "" { + badCIDRs = append(badCIDRs, tc.in) + } + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + errs := IsValidCIDR(field.NewPath(""), tc.in) + if tc.err == "" { + if len(errs) != 0 { + t.Errorf("expected %q to be valid but got: %v", tc.in, errs) + } + } else { + if len(errs) != 1 { + t.Errorf("expected %q to have 1 error but got: %v", tc.in, errs) + } else if !strings.Contains(errs[0].Detail, tc.err) { + t.Errorf("expected error for %q to contain %q but got: %q", tc.in, tc.err, errs[0].Detail) + } + } + + errs = IsValidCIDRForLegacyField(field.NewPath(""), tc.in, false, nil) + if tc.legacyErr == "" { + if len(errs) != 0 { + t.Errorf("expected %q to be valid according to IsValidCIDRForLegacyField but got: %v", tc.in, errs) + } + } else { + if len(errs) != 1 { + t.Errorf("expected %q to have 1 error from IsValidCIDRForLegacyField but got: %v", tc.in, errs) + } else if !strings.Contains(errs[0].Detail, tc.legacyErr) { + t.Errorf("expected error for %q from IsValidCIDRForLegacyField to contain %q but got: %q", tc.in, tc.legacyErr, errs[0].Detail) + } + } + + errs = IsValidCIDRForLegacyField(field.NewPath(""), tc.in, true, nil) + if tc.legacyStrictErr == "" { + if len(errs) != 0 { + t.Errorf("expected %q to be valid according to IsValidCIDRForLegacyField with strict validation but got: %v", tc.in, errs) + } + } else { + if len(errs) != 1 { + t.Errorf("expected %q to have 1 error from IsValidCIDRForLegacyField with strict validation but got: %v", tc.in, errs) + } else if !strings.Contains(errs[0].Detail, tc.legacyStrictErr) { + t.Errorf("expected error for %q from IsValidCIDRForLegacyField with strict validation to contain %q but got: %q", tc.in, tc.legacyStrictErr, errs[0].Detail) + } + } + + errs = IsValidCIDRForLegacyField(field.NewPath(""), tc.in, true, badCIDRs) + if len(errs) != 0 { + t.Errorf("expected %q to be accepted when using validOldCIDRs, but got: %v", tc.in, errs) + } + }) + } +} + +func TestGetWarningsForCIDR(t *testing.T) { + tests := []struct { + name string + fieldPath *field.Path + cidr string + want []string + }{ + { + name: "IPv4 No failures", + cidr: "192.12.2.0/24", + fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), + want: nil, + }, + { + name: "IPv6 No failures", + cidr: "2001:db8::/64", + fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), + want: nil, + }, + { + name: "IPv4 with leading zeros", + cidr: "192.012.2.0/24", + fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), + want: []string{ + `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.012.2.0/24" is invalid: use "192.12.2.0/24"`, + }, + }, + { + name: "leading zeros in prefix length", + cidr: "192.12.2.0/024", + fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), + want: []string{ + `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.12.2.0/024" is invalid: use "192.12.2.0/24"`, + }, + }, + { + name: "IPv4-mapped IPv6", + cidr: "::ffff:192.12.2.0/120", + fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), + want: []string{ + `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "::ffff:192.12.2.0/120" is invalid: use "192.12.2.0/24"`, + }, + }, + { + name: "bits after prefix length", + cidr: "192.12.2.8/24", + fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), + want: []string{ + `spec.loadBalancerSourceRanges[0]: CIDR value "192.12.2.8/24" is ambiguous in this context (should be "192.12.2.0/24" or "192.12.2.8/32"?)`, + }, + }, + { + name: "multiple problems", + cidr: "192.012.2.8/24", + fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), + want: []string{ + `spec.loadBalancerSourceRanges[0]: CIDR value "192.012.2.8/24" is ambiguous in this context (should be "192.12.2.0/24" or "192.12.2.8/32"?)`, + `spec.loadBalancerSourceRanges[0]: non-standard CIDR value "192.012.2.8/24" is invalid: use "192.12.2.0/24"`, + }, + }, + { + name: "IPv6 non-canonical format", + cidr: "2001:db8:0:0::/64", + fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0), + want: []string{ + `spec.loadBalancerSourceRanges[0]: IPv6 CIDR value "2001:db8:0:0::/64" should be in RFC 5952 canonical format ("2001:db8::/64")`, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetWarningsForCIDR(tt.fieldPath, tt.cidr); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getWarningsForCIDR() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsValidInterfaceAddress(t *testing.T) { + for _, tc := range []struct { + name string + in string + err string + }{ + // GOOD VALUES + { + name: "ipv4", + in: "1.2.3.4/24", + }, + { + name: "ipv4, single IP", + in: "1.1.1.1/32", + }, + { + name: "ipv6", + in: "2001:4860:4860::1/48", + }, + { + name: "ipv6, single IP", + in: "::1/128", + }, + + // BAD VALUES + { + name: "empty string", + in: "", + err: "must be a valid address in CIDR form", + }, + { + name: "junk", + in: "aaaaaaa", + err: "must be a valid address in CIDR form", + }, + { + name: "IP address", + in: "1.2.3.4", + err: "must be a valid address in CIDR form", + }, + { + name: "partial URL", + in: "192.168.0.1/healthz", + err: "must be a valid address in CIDR form", + }, + { + name: "partial URL 2", + in: "192.168.0.1/0/99", + err: "must be a valid address in CIDR form", + }, + { + name: "negative prefix length", + in: "192.168.0.0/-16", + err: "must be a valid address in CIDR form", + }, + { + name: "prefix length with sign", + in: "192.168.0.0/+16", + err: "must be a valid address in CIDR form", + }, + { + name: "ipv6 non-canonical", + in: "2001:0:0:0::0BCD/64", + err: `must be in canonical form ("2001::bcd/64")`, + }, + { + name: "ipv4 with leading 0s", + in: "1.1.01.002/24", + err: `must be in canonical form ("1.1.1.2/24")`, + }, + { + name: "ipv4-in-ipv6 with ipv4-sized prefix", + in: "::ffff:1.1.1.1/24", + err: `must be in canonical form ("1.1.1.1/24")`, + }, + { + name: "ipv4-in-ipv6 with ipv6-sized prefix", + in: "::ffff:1.1.1.1/120", + err: `must be in canonical form ("1.1.1.1/24")`, + }, + { + name: "prefix length with leading 0s", + in: "192.168.0.5/016", + err: `must be in canonical form ("192.168.0.5/16")`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + errs := IsValidInterfaceAddress(field.NewPath(""), tc.in) + if tc.err == "" { + if len(errs) != 0 { + t.Errorf("expected %q to be valid but got: %v", tc.in, errs) + } + } else { + if len(errs) != 1 { + t.Errorf("expected %q to have 1 error but got: %v", tc.in, errs) + } else if !strings.Contains(errs[0].Detail, tc.err) { + t.Errorf("expected error for %q to contain %q but got: %q", tc.in, tc.err, errs[0].Detail) + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/validation.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/validation.go new file mode 100644 index 0000000000..352ff19ae5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/validation.go @@ -0,0 +1,468 @@ +/* +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 validation + +import ( + "fmt" + "math" + "regexp" + "strings" + "unicode" + + "k8s.io/apimachinery/pkg/api/validate/content" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// IsQualifiedName tests whether the value passed is what Kubernetes calls a +// "qualified name". This is a format used in various places throughout the +// system. If the value is not valid, a list of error strings is returned. +// Otherwise an empty list (or nil) is returned. +// Deprecated: Use k8s.io/apimachinery/pkg/api/validate/content.IsQualifiedName instead. +var IsQualifiedName = content.IsLabelKey + +// IsFullyQualifiedName checks if the name is fully qualified. This is similar +// to IsFullyQualifiedDomainName but requires a minimum of 3 segments instead of +// 2 and does not accept a trailing . as valid. +// TODO: This function is deprecated and preserved until all callers migrate to +// IsFullyQualifiedDomainName; please don't add new callers. +func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList { + var allErrors field.ErrorList + if len(name) == 0 { + return append(allErrors, field.Required(fldPath, "")) + } + if errs := IsDNS1123Subdomain(name); len(errs) > 0 { + return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ","))) + } + if len(strings.Split(name, ".")) < 3 { + return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least three segments separated by dots")) + } + return allErrors +} + +// IsFullyQualifiedDomainName checks if the domain name is fully qualified. This +// is similar to IsFullyQualifiedName but only requires a minimum of 2 segments +// instead of 3 and accepts a trailing . as valid. +func IsFullyQualifiedDomainName(fldPath *field.Path, name string) field.ErrorList { + var allErrors field.ErrorList + if len(name) == 0 { + return append(allErrors, field.Required(fldPath, "")) + } + if strings.HasSuffix(name, ".") { + name = name[:len(name)-1] + } + if errs := IsDNS1123Subdomain(name); len(errs) > 0 { + return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ","))) + } + if len(strings.Split(name, ".")) < 2 { + return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least two segments separated by dots")) + } + for _, label := range strings.Split(name, ".") { + if errs := IsDNS1123Label(label); len(errs) > 0 { + return append(allErrors, field.Invalid(fldPath, label, strings.Join(errs, ","))) + } + } + return allErrors +} + +// Allowed characters in an HTTP Path as defined by RFC 3986. A HTTP path may +// contain: +// * unreserved characters (alphanumeric, '-', '.', '_', '~') +// * percent-encoded octets +// * sub-delims ("!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=") +// * a colon character (":") +const httpPathFmt string = `[A-Za-z0-9/\-._~%!$&'()*+,;=:]+` + +var httpPathRegexp = regexp.MustCompile("^" + httpPathFmt + "$") + +// IsDomainPrefixedPath checks if the given string is a domain-prefixed path +// (e.g. acme.io/foo). All characters before the first "/" must be a valid +// subdomain as defined by RFC 1123. All characters trailing the first "/" must +// be valid HTTP Path characters as defined by RFC 3986. +func IsDomainPrefixedPath(fldPath *field.Path, dpPath string) field.ErrorList { + var allErrs field.ErrorList + if len(dpPath) == 0 { + return append(allErrs, field.Required(fldPath, "")) + } + + segments := strings.SplitN(dpPath, "/", 2) + if len(segments) != 2 || len(segments[0]) == 0 || len(segments[1]) == 0 { + return append(allErrs, field.Invalid(fldPath, dpPath, "must be a domain-prefixed path (such as \"acme.io/foo\")")) + } + + host := segments[0] + for _, err := range IsDNS1123Subdomain(host) { + allErrs = append(allErrs, field.Invalid(fldPath, host, err)) + } + + path := segments[1] + if !httpPathRegexp.MatchString(path) { + return append(allErrs, field.Invalid(fldPath, path, RegexError("Invalid path", httpPathFmt))) + } + + return allErrs +} + +// IsDomainPrefixedKey checks if the given key string is a domain-prefixed key +// (e.g. acme.io/foo). All characters before the first "/" must be a valid +// subdomain as defined by RFC 1123. All characters trailing the first "/" must +// be non-empty and match the regex ^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$. +func IsDomainPrefixedKey(fldPath *field.Path, key string) field.ErrorList { + var allErrs field.ErrorList + if len(key) == 0 { + return append(allErrs, field.Required(fldPath, "")) + } + for _, errMessages := range content.IsLabelKey(key) { + allErrs = append(allErrs, field.Invalid(fldPath, key, errMessages)) + } + + if len(allErrs) > 0 { + return allErrs + } + + segments := strings.Split(key, "/") + if len(segments) != 2 { + return append(allErrs, field.Invalid(fldPath, key, "must be a domain-prefixed key (such as \"acme.io/foo\")")) + } + + return allErrs +} + +// LabelValueMaxLength is a label's max length +// Deprecated: Use k8s.io/apimachinery/pkg/api/validate/content.LabelValueMaxLength instead. +const LabelValueMaxLength int = content.LabelValueMaxLength + +// IsValidLabelValue tests whether the value passed is a valid label value. If +// the value is not valid, a list of error strings is returned. Otherwise an +// empty list (or nil) is returned. +// Deprecated: Use k8s.io/apimachinery/pkg/api/validate/content.IsLabelValue instead. +var IsValidLabelValue = content.IsLabelValue + +const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" +const dns1123LabelFmtWithUnderscore string = "_?[a-z0-9]([-_a-z0-9]*[a-z0-9])?" + +const dns1123LabelErrMsg string = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character" + +// DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123) +const DNS1123LabelMaxLength int = 63 + +var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$") + +// IsDNS1123Label tests for a string that conforms to the definition of a label in +// DNS (RFC 1123). +func IsDNS1123Label(value string) []string { + var errs []string + if len(value) > DNS1123LabelMaxLength { + errs = append(errs, MaxLenError(DNS1123LabelMaxLength)) + } + if !dns1123LabelRegexp.MatchString(value) { + if dns1123SubdomainRegexp.MatchString(value) { + // It was a valid subdomain and not a valid label. Since we + // already checked length, it must be dots. + errs = append(errs, "must not contain dots") + } else { + errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc")) + } + } + return errs +} + +const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*" +const dns1123SubdomainErrorMsg string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" + +const dns1123SubdomainFmtWithUnderscore string = dns1123LabelFmtWithUnderscore + "(\\." + dns1123LabelFmtWithUnderscore + ")*" +const dns1123SubdomainErrorMsgFG string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '_', '-' or '.', and must start and end with an alphanumeric character" + +// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123) +const DNS1123SubdomainMaxLength int = 253 + +var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$") +var dns1123SubdomainRegexpWithUnderscore = regexp.MustCompile("^" + dns1123SubdomainFmtWithUnderscore + "$") + +// IsDNS1123Subdomain tests for a string that conforms to the definition of a +// subdomain in DNS (RFC 1123). +func IsDNS1123Subdomain(value string) []string { + var errs []string + if len(value) > DNS1123SubdomainMaxLength { + errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) + } + if !dns1123SubdomainRegexp.MatchString(value) { + errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com")) + } + return errs +} + +// IsDNS1123SubdomainWithUnderscore tests for a string that conforms to the definition of a +// subdomain in DNS (RFC 1123), but allows the use of an underscore in the string +func IsDNS1123SubdomainWithUnderscore(value string) []string { + var errs []string + if len(value) > DNS1123SubdomainMaxLength { + errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) + } + if !dns1123SubdomainRegexpWithUnderscore.MatchString(value) { + errs = append(errs, RegexError(dns1123SubdomainErrorMsgFG, dns1123SubdomainFmtWithUnderscore, "example.com")) + } + return errs +} + +const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?" +const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character" + +// DNS1035LabelMaxLength is a label's max length in DNS (RFC 1035) +const DNS1035LabelMaxLength int = 63 + +var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$") + +// IsDNS1035Label tests for a string that conforms to the definition of a label in +// DNS (RFC 1035). +func IsDNS1035Label(value string) []string { + var errs []string + if len(value) > DNS1035LabelMaxLength { + errs = append(errs, MaxLenError(DNS1035LabelMaxLength)) + } + if !dns1035LabelRegexp.MatchString(value) { + errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123")) + } + return errs +} + +// wildcard definition - RFC 1034 section 4.3.3. +// examples: +// - valid: *.bar.com, *.foo.bar.com +// - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, * +const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt +const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character" + +// IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a +// wildcard subdomain in DNS (RFC 1034 section 4.3.3). +func IsWildcardDNS1123Subdomain(value string) []string { + wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$") + + var errs []string + if len(value) > DNS1123SubdomainMaxLength { + errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) + } + if !wildcardDNS1123SubdomainRegexp.MatchString(value) { + errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com")) + } + return errs +} + +// IsCIdentifier tests for a string that conforms the definition of an identifier +// in C. This checks the format, but not the length. +// Deprecated: Use k8s.io/apimachinery/pkg/api/validate/content.IsCIdentifier instead. +var IsCIdentifier = content.IsCIdentifier + +// IsValidPortNum tests that the argument is a valid, non-zero port number. +func IsValidPortNum(port int) []string { + if 1 <= port && port <= 65535 { + return nil + } + return []string{InclusiveRangeError(1, 65535)} +} + +// IsInRange tests that the argument is in an inclusive range. +func IsInRange(value int, min int, max int) []string { + if value >= min && value <= max { + return nil + } + return []string{InclusiveRangeError(min, max)} +} + +// Now in libcontainer UID/GID limits is 0 ~ 1<<31 - 1 +// TODO: once we have a type for UID/GID we should make these that type. +const ( + minUserID = 0 + maxUserID = math.MaxInt32 + minGroupID = 0 + maxGroupID = math.MaxInt32 +) + +// IsValidGroupID tests that the argument is a valid Unix GID. +func IsValidGroupID(gid int64) []string { + if minGroupID <= gid && gid <= maxGroupID { + return nil + } + return []string{InclusiveRangeError(minGroupID, maxGroupID)} +} + +// IsValidUserID tests that the argument is a valid Unix UID. +func IsValidUserID(uid int64) []string { + if minUserID <= uid && uid <= maxUserID { + return nil + } + return []string{InclusiveRangeError(minUserID, maxUserID)} +} + +var portNameCharsetRegex = regexp.MustCompile("^[-a-z0-9]+$") +var portNameOneLetterRegexp = regexp.MustCompile("[a-z]") + +// IsValidPortName check that the argument is valid syntax. It must be +// non-empty and no more than 15 characters long. It may contain only [-a-z0-9] +// and must contain at least one letter [a-z]. It must not start or end with a +// hyphen, nor contain adjacent hyphens. +// +// Note: We only allow lower-case characters, even though RFC 6335 is case +// insensitive. +func IsValidPortName(port string) []string { + var errs []string + if len(port) > 15 { + errs = append(errs, MaxLenError(15)) + } + if !portNameCharsetRegex.MatchString(port) { + errs = append(errs, "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)") + } + if !portNameOneLetterRegexp.MatchString(port) { + errs = append(errs, "must contain at least one letter (a-z)") + } + if strings.Contains(port, "--") { + errs = append(errs, "must not contain consecutive hyphens") + } + if len(port) > 0 && (port[0] == '-' || port[len(port)-1] == '-') { + errs = append(errs, "must not begin or end with a hyphen") + } + return errs +} + +const percentFmt string = "[0-9]+%" +const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'" + +var percentRegexp = regexp.MustCompile("^" + percentFmt + "$") + +// IsValidPercent checks that string is in the form of a percentage +func IsValidPercent(percent string) []string { + if !percentRegexp.MatchString(percent) { + return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")} + } + return nil +} + +const httpHeaderNameFmt string = "[-A-Za-z0-9]+" +const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'" + +var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$") + +// IsHTTPHeaderName checks that a string conforms to the Go HTTP library's +// definition of a valid header field name (a stricter subset than RFC7230). +func IsHTTPHeaderName(value string) []string { + if !httpHeaderNameRegexp.MatchString(value) { + return []string{RegexError(httpHeaderNameErrMsg, httpHeaderNameFmt, "X-Header-Name")} + } + return nil +} + +const envVarNameFmt = "[-._a-zA-Z][-._a-zA-Z0-9]*" +const envVarNameFmtErrMsg string = "a valid environment variable name must consist of alphabetic characters, digits, '_', '-', or '.', and must not start with a digit" + +// TODO(hirazawaui): Rename this when the RelaxedEnvironmentVariableValidation gate is removed. +const relaxedEnvVarNameFmtErrMsg string = "a valid environment variable name must consist only of printable ASCII characters other than '='" + +var envVarNameRegexp = regexp.MustCompile("^" + envVarNameFmt + "$") + +// IsEnvVarName tests if a string is a valid environment variable name. +func IsEnvVarName(value string) []string { + var errs []string + if !envVarNameRegexp.MatchString(value) { + errs = append(errs, RegexError(envVarNameFmtErrMsg, envVarNameFmt, "my.env-name", "MY_ENV.NAME", "MyEnvName1")) + } + + errs = append(errs, hasChDirPrefix(value)...) + return errs +} + +// IsRelaxedEnvVarName tests if a string is a valid environment variable name. +func IsRelaxedEnvVarName(value string) []string { + var errs []string + + if len(value) == 0 { + errs = append(errs, "environment variable name "+EmptyError()) + } + + for _, r := range value { + if r > unicode.MaxASCII || !unicode.IsPrint(r) || r == '=' { + errs = append(errs, relaxedEnvVarNameFmtErrMsg) + break + } + } + + return errs +} + +const configMapKeyFmt = `[-._a-zA-Z0-9]+` +const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'" + +var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$") + +// IsConfigMapKey tests for a string that is a valid key for a ConfigMap or Secret +func IsConfigMapKey(value string) []string { + var errs []string + if len(value) > DNS1123SubdomainMaxLength { + errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) + } + if !configMapKeyRegexp.MatchString(value) { + errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name")) + } + errs = append(errs, hasChDirPrefix(value)...) + return errs +} + +// MaxLenError returns a string explanation of a "string too long" validation +// failure. +func MaxLenError(length int) string { + return fmt.Sprintf("must be no more than %d characters", length) +} + +// RegexError returns a string explanation of a regex validation failure. +func RegexError(msg string, fmt string, examples ...string) string { + if len(examples) == 0 { + return msg + " (regex used for validation is '" + fmt + "')" + } + msg += " (e.g. " + for i := range examples { + if i > 0 { + msg += " or " + } + msg += "'" + examples[i] + "', " + } + msg += "regex used for validation is '" + fmt + "')" + return msg +} + +// EmptyError returns a string explanation of a "must not be empty" validation +// failure. +func EmptyError() string { + return "must be non-empty" +} + +// InclusiveRangeError returns a string explanation of a numeric "must be +// between" validation failure. +func InclusiveRangeError(lo, hi int) string { + return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi) +} + +func hasChDirPrefix(value string) []string { + var errs []string + switch { + case value == ".": + errs = append(errs, `must not be '.'`) + case value == "..": + errs = append(errs, `must not be '..'`) + case strings.HasPrefix(value, ".."): + errs = append(errs, `must not start with '..'`) + } + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/validation_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/validation_test.go new file mode 100644 index 0000000000..7affccfb29 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/validation/validation_test.go @@ -0,0 +1,764 @@ +/* +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 validation + +import ( + "reflect" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestIsDNS1123Label(t *testing.T) { + goodValues := []string{ + "a", "ab", "abc", "a1", "a-1", "a--1--2--b", + "0", "01", "012", "1a", "1-a", "1--a--b--2", + strings.Repeat("a", 63), + } + for _, val := range goodValues { + if msgs := IsDNS1123Label(val); len(msgs) != 0 { + t.Errorf("expected true for '%s': %v", val, msgs) + } + } + + badValues := []string{ + "", "A", "ABC", "aBc", "A1", "A-1", "1-A", + "-", "a-", "-a", "1-", "-1", + "_", "a_", "_a", "a_b", "1_", "_1", "1_2", + ".", "a.", ".a", "a.b", "1.", ".1", "1.2", + " ", "a ", " a", "a b", "1 ", " 1", "1 2", + strings.Repeat("a", 64), + } + for _, val := range badValues { + if msgs := IsDNS1123Label(val); len(msgs) == 0 { + t.Errorf("expected false for '%s'", val) + } + } +} + +func TestIsDNS1123Subdomain(t *testing.T) { + goodValues := []string{ + "a", "ab", "abc", "a1", "a-1", "a--1--2--b", + "0", "01", "012", "1a", "1-a", "1--a--b--2", + "a.a", "ab.a", "abc.a", "a1.a", "a-1.a", "a--1--2--b.a", + "a.1", "ab.1", "abc.1", "a1.1", "a-1.1", "a--1--2--b.1", + "0.a", "01.a", "012.a", "1a.a", "1-a.a", "1--a--b--2", + "0.1", "01.1", "012.1", "1a.1", "1-a.1", "1--a--b--2.1", + "a.b.c.d.e", "aa.bb.cc.dd.ee", "1.2.3.4.5", "11.22.33.44.55", + strings.Repeat("a", 253), + } + for _, val := range goodValues { + if msgs := IsDNS1123Subdomain(val); len(msgs) != 0 { + t.Errorf("expected true for '%s': %v", val, msgs) + } + } + + badValues := []string{ + "", "A", "ABC", "aBc", "A1", "A-1", "1-A", + "-", "a-", "-a", "1-", "-1", + "_", "a_", "_a", "a_b", "1_", "_1", "1_2", + ".", "a.", ".a", "a..b", "1.", ".1", "1..2", + " ", "a ", " a", "a b", "1 ", " 1", "1 2", + "A.a", "aB.a", "ab.A", "A1.a", "a1.A", + "A.1", "aB.1", "A1.1", "1A.1", + "0.A", "01.A", "012.A", "1A.a", "1a.A", + "A.B.C.D.E", "AA.BB.CC.DD.EE", "a.B.c.d.e", "aa.bB.cc.dd.ee", + "a@b", "a,b", "a_b", "a;b", + "a:b", "a%b", "a?b", "a$b", + strings.Repeat("a", 254), + } + for _, val := range badValues { + if msgs := IsDNS1123Subdomain(val); len(msgs) == 0 { + t.Errorf("expected false for '%s'", val) + } + } +} + +func TestIsDNS1035Label(t *testing.T) { + goodValues := []string{ + "a", "ab", "abc", "a1", "a-1", "a--1--2--b", + strings.Repeat("a", 63), + } + for _, val := range goodValues { + if msgs := IsDNS1035Label(val); len(msgs) != 0 { + t.Errorf("expected true for '%s': %v", val, msgs) + } + } + + badValues := []string{ + "0", "01", "012", "1a", "1-a", "1--a--b--2", + "", "A", "ABC", "aBc", "A1", "A-1", "1-A", + "-", "a-", "-a", "1-", "-1", + "_", "a_", "_a", "a_b", "1_", "_1", "1_2", + ".", "a.", ".a", "a.b", "1.", ".1", "1.2", + " ", "a ", " a", "a b", "1 ", " 1", "1 2", + strings.Repeat("a", 64), + } + for _, val := range badValues { + if msgs := IsDNS1035Label(val); len(msgs) == 0 { + t.Errorf("expected false for '%s'", val) + } + } +} + +func TestIsValidPortNum(t *testing.T) { + goodValues := []int{1, 2, 1000, 16384, 32768, 65535} + for _, val := range goodValues { + if msgs := IsValidPortNum(val); len(msgs) != 0 { + t.Errorf("expected true for %d, got %v", val, msgs) + } + } + + badValues := []int{0, -1, 65536, 100000} + for _, val := range badValues { + if msgs := IsValidPortNum(val); len(msgs) == 0 { + t.Errorf("expected false for %d", val) + } + } +} + +func TestIsInRange(t *testing.T) { + goodValues := []struct { + value int + min int + max int + }{{1, 0, 10}, {5, 5, 20}, {25, 10, 25}} + for _, val := range goodValues { + if msgs := IsInRange(val.value, val.min, val.max); len(msgs) > 0 { + t.Errorf("expected no errors for %#v, but got %v", val, msgs) + } + } + + badValues := []struct { + value int + min int + max int + }{{1, 2, 10}, {5, -4, 2}, {25, 100, 120}} + for _, val := range badValues { + if msgs := IsInRange(val.value, val.min, val.max); len(msgs) == 0 { + t.Errorf("expected errors for %#v", val) + } + } +} + +func createGroupIDs(ids ...int64) []int64 { + var output []int64 + for _, id := range ids { + output = append(output, int64(id)) + } + return output +} + +func createUserIDs(ids ...int64) []int64 { + var output []int64 + for _, id := range ids { + output = append(output, int64(id)) + } + return output +} + +func TestIsValidGroupID(t *testing.T) { + goodValues := createGroupIDs(0, 1, 1000, 65535, 2147483647) + for _, val := range goodValues { + if msgs := IsValidGroupID(val); len(msgs) != 0 { + t.Errorf("expected true for '%d': %v", val, msgs) + } + } + + badValues := createGroupIDs(-1, -1003, 2147483648, 4147483647) + for _, val := range badValues { + if msgs := IsValidGroupID(val); len(msgs) == 0 { + t.Errorf("expected false for '%d'", val) + } + } +} + +func TestIsValidUserID(t *testing.T) { + goodValues := createUserIDs(0, 1, 1000, 65535, 2147483647) + for _, val := range goodValues { + if msgs := IsValidUserID(val); len(msgs) != 0 { + t.Errorf("expected true for '%d': %v", val, msgs) + } + } + + badValues := createUserIDs(-1, -1003, 2147483648, 4147483647) + for _, val := range badValues { + if msgs := IsValidUserID(val); len(msgs) == 0 { + t.Errorf("expected false for '%d'", val) + } + } +} + +func TestIsValidPortName(t *testing.T) { + goodValues := []string{"telnet", "re-mail-ck", "pop3", "a", "a-1", "1-a", "a-1-b-2-c", "1-a-2-b-3"} + for _, val := range goodValues { + if msgs := IsValidPortName(val); len(msgs) != 0 { + t.Errorf("expected true for %q: %v", val, msgs) + } + } + + badValues := []string{"longerthan15characters", "", strings.Repeat("a", 16), "12345", "1-2-3-4", "-begin", "end-", "two--hyphens", "whois++"} + for _, val := range badValues { + if msgs := IsValidPortName(val); len(msgs) == 0 { + t.Errorf("expected false for %q", val) + } + } +} + +func TestIsQualifiedName(t *testing.T) { + successCases := []string{ + "simple", + "now-with-dashes", + "1-starts-with-num", + "1234", + "simple/simple", + "now-with-dashes/simple", + "now-with-dashes/now-with-dashes", + "now.with.dots/simple", + "now-with.dashes-and.dots/simple", + "1-num.2-num/3-num", + "1234/5678", + "1.2.3.4/5678", + "Uppercase_Is_OK_123", + "example.com/Uppercase_Is_OK_123", + "requests.storage-foo", + strings.Repeat("a", 63), + strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63), + } + for i := range successCases { + if errs := IsQualifiedName(successCases[i]); len(errs) != 0 { + t.Errorf("case[%d]: %q: expected success: %v", i, successCases[i], errs) + } + } + + errorCases := []string{ + "nospecialchars%^=@", + "cantendwithadash-", + "-cantstartwithadash-", + "only/one/slash", + "Example.com/abc", + "example_com/abc", + "example.com/", + "/simple", + strings.Repeat("a", 64), + strings.Repeat("a", 254) + "/abc", + } + for i := range errorCases { + if errs := IsQualifiedName(errorCases[i]); len(errs) == 0 { + t.Errorf("case[%d]: %q: expected failure", i, errorCases[i]) + } + } +} + +func TestIsValidLabelValue(t *testing.T) { + successCases := []string{ + "simple", + "now-with-dashes", + "1-starts-with-num", + "end-with-num-1", + "1234", // only num + strings.Repeat("a", 63), // to the limit + "", // empty value + } + for i := range successCases { + if errs := IsValidLabelValue(successCases[i]); len(errs) != 0 { + t.Errorf("case %s expected success: %v", successCases[i], errs) + } + } + + errorCases := []string{ + "nospecialchars%^=@", + "Tama-nui-te-rā.is.Māori.sun", + "\\backslashes\\are\\bad", + "-starts-with-dash", + "ends-with-dash-", + ".starts.with.dot", + "ends.with.dot.", + strings.Repeat("a", 64), // over the limit + } + for i := range errorCases { + if errs := IsValidLabelValue(errorCases[i]); len(errs) == 0 { + t.Errorf("case[%d] expected failure", i) + } + } +} + +func TestIsHTTPHeaderName(t *testing.T) { + goodValues := []string{ + // Common ones + "Accept-Encoding", "Host", "If-Modified-Since", "X-Forwarded-For", + // Weirdo, but still conforming names + "a", "ab", "abc", "a1", "-a", "a-", "a-b", "a-1", "a--1--2--b", "--abc-123", + "A", "AB", "AbC", "A1", "-A", "A-", "A-B", "A-1", "A--1--2--B", "--123-ABC", + } + for _, val := range goodValues { + if msgs := IsHTTPHeaderName(val); len(msgs) != 0 { + t.Errorf("expected true for '%s': %v", val, msgs) + } + } + + badValues := []string{ + "Host:", "X-Forwarded-For:", "X-@Home", + "", "_", "a_", "_a", "1_", "1_2", ".", "a.", ".a", "a.b", "1.", ".1", "1.2", + " ", "a ", " a", "a b", "1 ", " 1", "1 2", "#a#", "^", ",", ";", "=", "<", + "?", "@", "{", + } + for _, val := range badValues { + if msgs := IsHTTPHeaderName(val); len(msgs) == 0 { + t.Errorf("expected false for '%s'", val) + } + } +} + +func TestIsValidPercent(t *testing.T) { + goodValues := []string{ + "0%", + "00000%", + "1%", + "01%", + "99%", + "100%", + "101%", + } + for _, val := range goodValues { + if msgs := IsValidPercent(val); len(msgs) != 0 { + t.Errorf("expected true for %q: %v", val, msgs) + } + } + + badValues := []string{ + "", + "0", + "100", + "0.0%", + "99.9%", + "hundred", + " 1%", + "1% ", + "-0%", + "-1%", + "+1%", + } + for _, val := range badValues { + if msgs := IsValidPercent(val); len(msgs) == 0 { + t.Errorf("expected false for %q", val) + } + } +} + +func TestIsConfigMapKey(t *testing.T) { + successCases := []string{ + "a", + "good", + "good-good", + "still.good", + "this.is.also.good", + ".so.is.this", + "THIS_IS_GOOD", + "so_is_this_17", + } + + for i := range successCases { + if errs := IsConfigMapKey(successCases[i]); len(errs) != 0 { + t.Errorf("[%d] expected success: %v", i, errs) + } + } + + failureCases := []string{ + ".", + "..", + "..bad", + "b*d", + "bad!&bad", + } + + for i := range failureCases { + if errs := IsConfigMapKey(failureCases[i]); len(errs) == 0 { + t.Errorf("[%d] expected failure", i) + } + } +} + +func TestIsWildcardDNS1123Subdomain(t *testing.T) { + goodValues := []string{ + "*.example.com", + "*.bar.com", + "*.foo.bar.com", + } + for _, val := range goodValues { + if errs := IsWildcardDNS1123Subdomain(val); len(errs) != 0 { + t.Errorf("expected no errors for %q: %v", val, errs) + } + } + + badValues := []string{ + "*.*.bar.com", + "*.foo.*.com", + "*bar.com", + "f*.bar.com", + "*", + } + for _, val := range badValues { + if errs := IsWildcardDNS1123Subdomain(val); len(errs) == 0 { + t.Errorf("expected errors for %q", val) + } + } +} + +func TestIsFullyQualifiedDomainName(t *testing.T) { + goodValues := []string{ + "a.com", + "k8s.io", + "dev.k8s.io", + "dev.k8s.io.", + "foo.example.com", + "this.is.a.really.long.fqdn", + "bbc.co.uk", + "10.0.0.1", // DNS labels can start with numbers and there is no requirement for letters. + "hyphens-are-good.k8s.io", + strings.Repeat("a", 63) + ".k8s.io", + strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 54) + ".k8s.io", + } + for _, val := range goodValues { + if err := IsFullyQualifiedDomainName(field.NewPath(""), val).ToAggregate(); err != nil { + t.Errorf("expected no errors for %q: %v", val, err) + } + } + + badValues := []string{ + ".", + "...", + ".io", + "com", + ".com", + "Dev.k8s.io", + ".foo.example.com", + "*.example.com", + "*.bar.com", + "*.foo.bar.com", + "underscores_are_bad.k8s.io", + "foo@bar.example.com", + "http://foo.example.com", + strings.Repeat("a", 64) + ".k8s.io", + strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 55) + ".k8s.io", + } + for _, val := range badValues { + if err := IsFullyQualifiedDomainName(field.NewPath(""), val).ToAggregate(); err == nil { + t.Errorf("expected errors for %q", val) + } + } +} + +func TestIsFullyQualifiedName(t *testing.T) { + goodValues := []string{ + "dev.k8s.io", + "foo.example.com", + "this.is.a.really.long.fqdn", + "bbc.co.uk", + "10.0.0.1", // DNS labels can start with numbers and there is no requirement for letters. + "hyphens-are-good.k8s.io", + strings.Repeat("a", 246) + ".k8s.io", + } + for _, val := range goodValues { + if err := IsFullyQualifiedName(field.NewPath(""), val).ToAggregate(); err != nil { + t.Errorf("expected no errors for %q: %v", val, err) + } + } + + badValues := []string{ + "...", + "dev.k8s.io.", + ".io", + "Dev.k8s.io", + "k8s.io", + "*.example.com", + "*.bar.com", + "*.foo.bar.com", + "underscores_are_bad.k8s.io", + "foo@bar.example.com", + "http://foo.example.com", + strings.Repeat("a", 247) + ".k8s.io", + } + for _, val := range badValues { + if err := IsFullyQualifiedName(field.NewPath(""), val).ToAggregate(); err == nil { + t.Errorf("expected errors for %q", val) + } + } + + messageTests := []struct { + name string + targetName string + err string + }{{ + name: "name needs to be fully qualified, i.e., contains at least 2 dots", + targetName: "k8s.io", + err: "should be a domain with at least three segments separated by dots", + }, { + name: "name should not include scheme", + targetName: "http://foo.k8s.io", + err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", + }, { + name: "email should be invalid", + targetName: "example@foo.k8s.io", + err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", + }, { + name: "name cannot be empty", + targetName: "", + err: "Required value", + }, { + name: "name must conform to RFC 1123", + targetName: "A.B.C", + err: "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters", + }} + for _, tc := range messageTests { + err := IsFullyQualifiedName(field.NewPath(""), tc.targetName).ToAggregate() + switch { + case tc.err == "" && err != nil: + t.Errorf("%q: unexpected error: %v", tc.name, err) + case tc.err != "" && err == nil: + t.Errorf("%q: unexpected no error, expected %s", tc.name, tc.err) + case tc.err != "" && err != nil && !strings.Contains(err.Error(), tc.err): + t.Errorf("%q: expected %s, got %v", tc.name, tc.err, err) + } + } +} + +func TestIsDomainPrefixedPath(t *testing.T) { + goodValues := []string{ + "a/b", + "a/b/c/d", + "a.com/foo", + "a.b.c.d/foo", + "k8s.io/foo/bar", + "k8s.io/FOO/BAR", + "dev.k8s.io/more/path", + "this.is.a.really.long.fqdn/even/longer/path/just/because", + "bbc.co.uk/path/goes/here", + "10.0.0.1/foo", + "hyphens-are-good.k8s.io/and-in-paths-too", + strings.Repeat("a", 240) + ".k8s.io/a", + "k8s.io/" + strings.Repeat("a", 240), + } + for _, val := range goodValues { + if err := IsDomainPrefixedPath(field.NewPath(""), val).ToAggregate(); err != nil { + t.Errorf("expected no errors for %q: %v", val, err) + } + } + + badValues := []string{ + ".", + "...", + "/b", + "com", + ".com", + "a.b.c.d/foo?a=b", + "a.b.c.d/foo#a", + "Dev.k8s.io", + ".foo.example.com", + "*.example.com", + "example.com/foo{}[]@^`", + "underscores_are_bad.k8s.io", + "underscores_are_bad.k8s.io/foo", + "foo@bar.example.com", + "foo@bar.example.com/foo", + strings.Repeat("a", 247) + ".k8s.io", + } + for _, val := range badValues { + if err := IsDomainPrefixedPath(field.NewPath(""), val).ToAggregate(); err == nil { + t.Errorf("expected errors for %q", val) + } + } +} + +func TestIsRelaxedEnvVarName(t *testing.T) { + goodValues := []string{ + "-", ":", "_", "+a", ">a", "= 0; i-- { + currentHighestVer, err := ParseGeneric(versions[i]) + if err != nil { + theErr = err + continue + } + + if currentHighestVer.Major() > 1 { + continue + } + + if highestSupportedVersion == nil || highestSupportedVersion.LessThan(currentHighestVer) { + highestSupportedVersion = currentHighestVer + } + } + + if highestSupportedVersion == nil { + return nil, fmt.Errorf( + "could not find a highest supported version from versions (%v) reported: %+v", + versions, theErr) + } + + if highestSupportedVersion.Major() != 1 { + return nil, fmt.Errorf("highest supported version reported is %v, must be v1.x", highestSupportedVersion) + } + + return highestSupportedVersion, nil +} + +// ParseGeneric parses a "generic" version string. The version string must consist of two +// or more dot-separated numeric fields (the first of which can't have leading zeroes), +// followed by arbitrary uninterpreted data (which need not be separated from the final +// numeric field by punctuation). For convenience, leading and trailing whitespace is +// ignored, and the version can be preceded by the letter "v". See also ParseSemantic. +func ParseGeneric(str string) (*Version, error) { + return parse(str, false) +} + +// MustParseGeneric is like ParseGeneric except that it panics on error +func MustParseGeneric(str string) *Version { + v, err := ParseGeneric(str) + if err != nil { + panic(err) + } + return v +} + +// Parse tries to do ParseSemantic first to keep more information. +// If ParseSemantic fails, it would just do ParseGeneric. +func Parse(str string) (*Version, error) { + v, err := parse(str, true) + if err != nil { + return parse(str, false) + } + return v, err +} + +// MustParse is like Parse except that it panics on error +func MustParse(str string) *Version { + v, err := Parse(str) + if err != nil { + panic(err) + } + return v +} + +// ParseMajorMinor parses a "generic" version string and returns a version with the major and minor version. +func ParseMajorMinor(str string) (*Version, error) { + v, err := ParseGeneric(str) + if err != nil { + return nil, err + } + return MajorMinor(v.Major(), v.Minor()), nil +} + +// MustParseMajorMinor is like ParseMajorMinor except that it panics on error +func MustParseMajorMinor(str string) *Version { + v, err := ParseMajorMinor(str) + if err != nil { + panic(err) + } + return v +} + +// ParseSemantic parses a version string that exactly obeys the syntax and semantics of +// the "Semantic Versioning" specification (http://semver.org/) (although it ignores +// leading and trailing whitespace, and allows the version to be preceded by "v"). For +// version strings that are not guaranteed to obey the Semantic Versioning syntax, use +// ParseGeneric. +func ParseSemantic(str string) (*Version, error) { + return parse(str, true) +} + +// MustParseSemantic is like ParseSemantic except that it panics on error +func MustParseSemantic(str string) *Version { + v, err := ParseSemantic(str) + if err != nil { + panic(err) + } + return v +} + +// MajorMinor returns a version with the provided major and minor version. +func MajorMinor(major, minor uint) *Version { + return &Version{components: []uint{major, minor}} +} + +// Major returns the major release number +func (v *Version) Major() uint { + return v.components[0] +} + +// Minor returns the minor release number +func (v *Version) Minor() uint { + return v.components[1] +} + +// Patch returns the patch release number if v is a Semantic Version, or 0 +func (v *Version) Patch() uint { + if len(v.components) < 3 { + return 0 + } + return v.components[2] +} + +// BuildMetadata returns the build metadata, if v is a Semantic Version, or "" +func (v *Version) BuildMetadata() string { + return v.buildMetadata +} + +// PreRelease returns the prerelease metadata, if v is a Semantic Version, or "" +func (v *Version) PreRelease() string { + return v.preRelease +} + +// Components returns the version number components +func (v *Version) Components() []uint { + return v.components +} + +// WithMajor returns copy of the version object with requested major number +func (v *Version) WithMajor(major uint) *Version { + result := *v + result.components = []uint{major, v.Minor(), v.Patch()} + return &result +} + +// WithMinor returns copy of the version object with requested minor number +func (v *Version) WithMinor(minor uint) *Version { + result := *v + result.components = []uint{v.Major(), minor, v.Patch()} + return &result +} + +// SubtractMinor returns the version with offset from the original minor, with the same major and no patch. +// If -offset >= current minor, the minor would be 0. +func (v *Version) OffsetMinor(offset int) *Version { + var minor uint + if offset >= 0 { + minor = v.Minor() + uint(offset) + } else { + diff := uint(-offset) + if diff < v.Minor() { + minor = v.Minor() - diff + } + } + return MajorMinor(v.Major(), minor) +} + +// SubtractMinor returns the version diff minor versions back, with the same major and no patch. +// If diff >= current minor, the minor would be 0. +func (v *Version) SubtractMinor(diff uint) *Version { + return v.OffsetMinor(-int(diff)) +} + +// AddMinor returns the version diff minor versions forward, with the same major and no patch. +func (v *Version) AddMinor(diff uint) *Version { + return v.OffsetMinor(int(diff)) +} + +// WithPatch returns copy of the version object with requested patch number +func (v *Version) WithPatch(patch uint) *Version { + result := *v + result.components = []uint{v.Major(), v.Minor(), patch} + return &result +} + +// WithPreRelease returns copy of the version object with requested prerelease +func (v *Version) WithPreRelease(preRelease string) *Version { + if len(preRelease) == 0 { + return v + } + result := *v + result.components = []uint{v.Major(), v.Minor(), v.Patch()} + result.preRelease = preRelease + return &result +} + +// WithBuildMetadata returns copy of the version object with requested buildMetadata +func (v *Version) WithBuildMetadata(buildMetadata string) *Version { + result := *v + result.components = []uint{v.Major(), v.Minor(), v.Patch()} + result.buildMetadata = buildMetadata + return &result +} + +// String converts a Version back to a string; note that for versions parsed with +// ParseGeneric, this will not include the trailing uninterpreted portion of the version +// number. +func (v *Version) String() string { + if v == nil { + return "" + } + var buffer bytes.Buffer + + for i, comp := range v.components { + if i > 0 { + buffer.WriteString(".") + } + buffer.WriteString(fmt.Sprintf("%d", comp)) + } + if v.preRelease != "" { + buffer.WriteString("-") + buffer.WriteString(v.preRelease) + } + if v.buildMetadata != "" { + buffer.WriteString("+") + buffer.WriteString(v.buildMetadata) + } + + return buffer.String() +} + +// compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0 +// if they are equal +func (v *Version) compareInternal(other *Version) int { + + vLen := len(v.components) + oLen := len(other.components) + for i := 0; i < vLen && i < oLen; i++ { + switch { + case other.components[i] < v.components[i]: + return 1 + case other.components[i] > v.components[i]: + return -1 + } + } + + // If components are common but one has more items and they are not zeros, it is bigger + switch { + case oLen < vLen && !onlyZeros(v.components[oLen:]): + return 1 + case oLen > vLen && !onlyZeros(other.components[vLen:]): + return -1 + } + + if !v.semver || !other.semver { + return 0 + } + + switch { + case v.preRelease == "" && other.preRelease != "": + return 1 + case v.preRelease != "" && other.preRelease == "": + return -1 + case v.preRelease == other.preRelease: // includes case where both are "" + return 0 + } + + vPR := strings.Split(v.preRelease, ".") + oPR := strings.Split(other.preRelease, ".") + for i := 0; i < len(vPR) && i < len(oPR); i++ { + vNum, err := strconv.ParseUint(vPR[i], 10, 0) + if err == nil { + oNum, err := strconv.ParseUint(oPR[i], 10, 0) + if err == nil { + switch { + case oNum < vNum: + return 1 + case oNum > vNum: + return -1 + default: + continue + } + } + } + if oPR[i] < vPR[i] { + return 1 + } else if oPR[i] > vPR[i] { + return -1 + } + } + + switch { + case len(oPR) < len(vPR): + return 1 + case len(oPR) > len(vPR): + return -1 + } + + return 0 +} + +// returns false if array contain any non-zero element +func onlyZeros(array []uint) bool { + for _, num := range array { + if num != 0 { + return false + } + } + return true +} + +// EqualTo tests if a version is equal to a given version. +func (v *Version) EqualTo(other *Version) bool { + if v == nil { + return other == nil + } + if other == nil { + return false + } + return v.compareInternal(other) == 0 +} + +// AtLeast tests if a version is at least equal to a given minimum version. If both +// Versions are Semantic Versions, this will use the Semantic Version comparison +// algorithm. Otherwise, it will compare only the numeric components, with non-present +// components being considered "0" (ie, "1.4" is equal to "1.4.0"). +func (v *Version) AtLeast(min *Version) bool { + return v.compareInternal(min) != -1 +} + +// LessThan tests if a version is less than a given version. (It is exactly the opposite +// of AtLeast, for situations where asking "is v too old?" makes more sense than asking +// "is v new enough?".) +func (v *Version) LessThan(other *Version) bool { + return v.compareInternal(other) == -1 +} + +// GreaterThan tests if a version is greater than a given version. +func (v *Version) GreaterThan(other *Version) bool { + return v.compareInternal(other) == 1 +} + +// Compare compares v against a version string (which will be parsed as either Semantic +// or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if +// it is greater than other, or 0 if they are equal. +func (v *Version) Compare(other string) (int, error) { + ov, err := parse(other, v.semver) + if err != nil { + return 0, err + } + return v.compareInternal(ov), nil +} + +// WithInfo returns copy of the version object. +// Deprecated: The Info field has been removed from the Version struct. This method no longer modifies the Version object. +func (v *Version) WithInfo(info apimachineryversion.Info) *Version { + result := *v + return &result +} + +// Info returns the version information of a component. +// Deprecated: Use Info() from effective version instead. +func (v *Version) Info() *apimachineryversion.Info { + if v == nil { + return nil + } + // in case info is empty, or the major and minor in info is different from the actual major and minor + return &apimachineryversion.Info{ + Major: Itoa(v.Major()), + Minor: Itoa(v.Minor()), + GitVersion: v.String(), + } +} + +func Itoa(i uint) string { + if i == 0 { + return "" + } + return strconv.Itoa(int(i)) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/version/version_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/version/version_test.go new file mode 100644 index 0000000000..a3345ddfdd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/version/version_test.go @@ -0,0 +1,550 @@ +/* +Copyright 2016 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 version + +import ( + "fmt" + "reflect" + "testing" +) + +type testItem struct { + version string + unparsed string + equalsPrev bool +} + +func testOne(v *Version, item, prev testItem) error { + str := v.String() + if item.unparsed == "" { + if str != item.version { + return fmt.Errorf("bad round-trip: %q -> %q", item.version, str) + } + } else { + if str != item.unparsed { + return fmt.Errorf("bad unparse: %q -> %q, expected %q", item.version, str, item.unparsed) + } + } + + if prev.version != "" { + cmp, err := v.Compare(prev.version) + if err != nil { + return fmt.Errorf("unexpected parse error: %v", err) + } + rv, err := parse(prev.version, v.semver) + if err != nil { + return fmt.Errorf("unexpected parse error: %v", err) + } + rcmp, err := rv.Compare(item.version) + if err != nil { + return fmt.Errorf("unexpected parse error: %v", err) + } + + switch { + case cmp == -1: + return fmt.Errorf("unexpected ordering %q < %q", item.version, prev.version) + case cmp == 0 && !item.equalsPrev: + return fmt.Errorf("unexpected comparison %q == %q", item.version, prev.version) + case cmp == 1 && item.equalsPrev: + return fmt.Errorf("unexpected comparison %q != %q", item.version, prev.version) + case cmp != -rcmp: + return fmt.Errorf("unexpected reverse comparison %q <=> %q %v %v %v %v", item.version, prev.version, cmp, rcmp, v.Components(), rv.Components()) + } + } + + return nil +} + +func TestSemanticVersions(t *testing.T) { + tests := []testItem{ + // This is every version string that appears in the 2.0 semver spec, + // sorted in strictly increasing order except as noted. + {version: "0.1.0"}, + {version: "1.0.0-0.3.7"}, + {version: "1.0.0-alpha"}, + {version: "1.0.0-alpha+001", equalsPrev: true}, + {version: "1.0.0-alpha.1"}, + {version: "1.0.0-alpha.beta"}, + {version: "1.0.0-beta"}, + {version: "1.0.0-beta+exp.sha.5114f85", equalsPrev: true}, + {version: "1.0.0-beta.2"}, + {version: "1.0.0-beta.11"}, + {version: "1.0.0-rc.1"}, + {version: "1.0.0-x.7.z.92"}, + {version: "1.0.0"}, + {version: "1.0.0+20130313144700", equalsPrev: true}, + {version: "1.8.0-alpha.3"}, + {version: "1.8.0-alpha.3.673+73326ef01d2d7c"}, + {version: "1.9.0"}, + {version: "1.10.0"}, + {version: "1.11.0"}, + {version: "2.0.0"}, + {version: "2.1.0"}, + {version: "2.1.1"}, + {version: "42.0.0"}, + + // We also allow whitespace and "v" prefix + {version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true}, + {version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true}, + {version: "43.0.0-1", unparsed: "43.0.0-1"}, + {version: "43.0.0-1 ", unparsed: "43.0.0-1", equalsPrev: true}, + {version: "v43.0.0-1", unparsed: "43.0.0-1", equalsPrev: true}, + {version: " v43.0.0", unparsed: "43.0.0"}, + {version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true}, + } + + var prev testItem + for _, item := range tests { + v, err := ParseSemantic(item.version) + if err != nil { + t.Errorf("unexpected parse error: %v", err) + continue + } + err = testOne(v, item, prev) + if err != nil { + t.Errorf("%v", err) + } + prev = item + } +} + +func TestBadSemanticVersions(t *testing.T) { + tests := []string{ + // "MUST take the form X.Y.Z" + "1", + "1.2", + "1.2.3.4", + ".2.3", + "1..3", + "1.2.", + "", + "..", + // "where X, Y, and Z are non-negative integers" + "-1.2.3", + "1.-2.3", + "1.2.-3", + "1a.2.3", + "1.2a.3", + "1.2.3a", + "a1.2.3", + "a.b.c", + "1 .2.3", + "1. 2.3", + // "and MUST NOT contain leading zeroes." + "01.2.3", + "1.02.3", + "1.2.03", + // "[pre-release] identifiers MUST comprise only ASCII alphanumerics and hyphen" + "1.2.3-/", + // "[pre-release] identifiers MUST NOT be empty" + "1.2.3-", + "1.2.3-.", + "1.2.3-foo.", + "1.2.3-.foo", + // "Numeric [pre-release] identifiers MUST NOT include leading zeroes" + "1.2.3-01", + // "[build metadata] identifiers MUST comprise only ASCII alphanumerics and hyphen" + "1.2.3+/", + // "[build metadata] identifiers MUST NOT be empty" + "1.2.3+", + "1.2.3+.", + "1.2.3+foo.", + "1.2.3+.foo", + + // whitespace/"v"-prefix checks + "v 1.2.3", + "vv1.2.3", + } + + for i := range tests { + _, err := ParseSemantic(tests[i]) + if err == nil { + t.Errorf("unexpected success parsing invalid semver %q", tests[i]) + } + } +} + +func TestGenericVersions(t *testing.T) { + tests := []testItem{ + // This is all of the strings from TestSemanticVersions, plus some strings + // from TestBadSemanticVersions that should parse as generic versions, + // plus some additional strings. + {version: "0.1.0", unparsed: "0.1.0"}, + {version: "1.0.0-0.3.7", unparsed: "1.0.0"}, + {version: "1.0.0-alpha", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0-alpha+001", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0-alpha.1", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0-alpha.beta", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0.beta", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0-beta+exp.sha.5114f85", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0.beta.2", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0.beta.11", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0.rc.1", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0-x.7.z.92", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.0.0+20130313144700", unparsed: "1.0.0", equalsPrev: true}, + {version: "1.2", unparsed: "1.2"}, + {version: "1.2a.3", unparsed: "1.2", equalsPrev: true}, + {version: "1.2.3", unparsed: "1.2.3"}, + {version: "1.2.3.0", unparsed: "1.2.3.0", equalsPrev: true}, + {version: "1.2.3a", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.3-foo.", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.3-.foo", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.3-01", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.3+", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.3+foo.", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.3+.foo", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.02.3", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.03", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.003", unparsed: "1.2.3", equalsPrev: true}, + {version: "1.2.3.4", unparsed: "1.2.3.4"}, + {version: "1.2.3.4b3", unparsed: "1.2.3.4", equalsPrev: true}, + {version: "1.2.3.4.5", unparsed: "1.2.3.4.5"}, + {version: "1.9.0", unparsed: "1.9.0"}, + {version: "1.9.0.0.0.0.0.0", unparsed: "1.9.0.0.0.0.0.0", equalsPrev: true}, + {version: "1.10.0", unparsed: "1.10.0"}, + {version: "1.11.0", unparsed: "1.11.0"}, + {version: "1.11.0.0.5", unparsed: "1.11.0.0.5"}, + {version: "2.0.0", unparsed: "2.0.0"}, + {version: "2.1.0", unparsed: "2.1.0"}, + {version: "2.1.1", unparsed: "2.1.1"}, + {version: "42.0.0", unparsed: "42.0.0"}, + {version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true}, + {version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true}, + {version: "42.0.0-1", unparsed: "42.0.0", equalsPrev: true}, + {version: "42.0.0-1 ", unparsed: "42.0.0", equalsPrev: true}, + {version: "v42.0.0-1", unparsed: "42.0.0", equalsPrev: true}, + {version: " v43.0.0", unparsed: "43.0.0"}, + {version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true}, + } + + var prev testItem + for _, item := range tests { + v, err := ParseGeneric(item.version) + if err != nil { + t.Errorf("unexpected parse error: %v", err) + continue + } + err = testOne(v, item, prev) + if err != nil { + t.Errorf("%v", err) + } + prev = item + } +} + +func TestBadGenericVersions(t *testing.T) { + tests := []string{ + "1", + "01.2.3", + "-1.2.3", + "1.-2.3", + ".2.3", + "1..3", + "1a.2.3", + "a1.2.3", + "1 .2.3", + "1. 2.3", + "1.bob", + "bob", + "v 1.2.3", + "vv1.2.3", + "", + ".", + } + + for i := range tests { + _, err := ParseGeneric(tests[i]) + if err == nil { + t.Errorf("unexpected success parsing invalid version %q", tests[i]) + } + } +} + +func TestComponents(t *testing.T) { + + var tests = []struct { + version string + semver bool + expectedComponents []uint + expectedMajor uint + expectedMinor uint + expectedPatch uint + expectedPreRelease string + expectedBuildMetadata string + }{ + { + version: "1.0.2", + semver: true, + expectedComponents: []uint{1, 0, 2}, + expectedMajor: 1, + expectedMinor: 0, + expectedPatch: 2, + }, + { + version: "1.0.2-alpha+001", + semver: true, + expectedComponents: []uint{1, 0, 2}, + expectedMajor: 1, + expectedMinor: 0, + expectedPatch: 2, + expectedPreRelease: "alpha", + expectedBuildMetadata: "001", + }, + { + version: "1.2", + semver: false, + expectedComponents: []uint{1, 2}, + expectedMajor: 1, + expectedMinor: 2, + }, + { + version: "1.0.2-beta+exp.sha.5114f85", + semver: true, + expectedComponents: []uint{1, 0, 2}, + expectedMajor: 1, + expectedMinor: 0, + expectedPatch: 2, + expectedPreRelease: "beta", + expectedBuildMetadata: "exp.sha.5114f85", + }, + } + + for _, test := range tests { + version, _ := parse(test.version, test.semver) + if !reflect.DeepEqual(test.expectedComponents, version.Components()) { + t.Error("parse returned un'expected components") + } + if test.expectedMajor != version.Major() { + t.Errorf("parse returned version.Major %d, expected %d", test.expectedMajor, version.Major()) + } + if test.expectedMinor != version.Minor() { + t.Errorf("parse returned version.Minor %d, expected %d", test.expectedMinor, version.Minor()) + } + if test.expectedPatch != version.Patch() { + t.Errorf("parse returned version.Patch %d, expected %d", test.expectedPatch, version.Patch()) + } + if test.expectedPreRelease != version.PreRelease() { + t.Errorf("parse returned version.PreRelease %s, expected %s", test.expectedPreRelease, version.PreRelease()) + } + if test.expectedBuildMetadata != version.BuildMetadata() { + t.Errorf("parse returned version.BuildMetadata %s, expected %s", test.expectedBuildMetadata, version.BuildMetadata()) + } + } +} + +func TestHighestSupportedVersion(t *testing.T) { + testCases := []struct { + versions []string + expectedHighestSupportedVersion string + shouldFail bool + }{ + { + versions: []string{"v1.0.0"}, + expectedHighestSupportedVersion: "1.0.0", + shouldFail: false, + }, + { + versions: []string{"0.3.0"}, + shouldFail: true, + }, + { + versions: []string{"0.2.0"}, + shouldFail: true, + }, + { + versions: []string{"1.0.0"}, + expectedHighestSupportedVersion: "1.0.0", + shouldFail: false, + }, + { + versions: []string{"v0.3.0"}, + shouldFail: true, + }, + { + versions: []string{"v0.2.0"}, + shouldFail: true, + }, + { + versions: []string{"0.2.0", "v0.3.0"}, + shouldFail: true, + }, + { + versions: []string{"0.2.0", "v1.0.0"}, + expectedHighestSupportedVersion: "1.0.0", + shouldFail: false, + }, + { + versions: []string{"0.2.0", "v1.2.3"}, + expectedHighestSupportedVersion: "1.2.3", + shouldFail: false, + }, + { + versions: []string{"v1.2.3", "v0.3.0"}, + expectedHighestSupportedVersion: "1.2.3", + shouldFail: false, + }, + { + versions: []string{"v1.2.3", "v0.3.0", "2.0.1"}, + expectedHighestSupportedVersion: "1.2.3", + shouldFail: false, + }, + { + versions: []string{"v1.2.3", "4.9.12", "v0.3.0", "2.0.1"}, + expectedHighestSupportedVersion: "1.2.3", + shouldFail: false, + }, + { + versions: []string{"4.9.12", "2.0.1"}, + expectedHighestSupportedVersion: "", + shouldFail: true, + }, + { + versions: []string{"v1.2.3", "boo", "v0.3.0", "2.0.1"}, + expectedHighestSupportedVersion: "1.2.3", + shouldFail: false, + }, + { + versions: []string{}, + expectedHighestSupportedVersion: "", + shouldFail: true, + }, + { + versions: []string{"var", "boo", "foo"}, + expectedHighestSupportedVersion: "", + shouldFail: true, + }, + } + + for _, tc := range testCases { + // Arrange & Act + actual, err := HighestSupportedVersion(tc.versions) + + // Assert + if tc.shouldFail && err == nil { + t.Fatalf("expecting highestSupportedVersion to fail, but got nil error for testcase: %#v", tc) + } + if !tc.shouldFail && err != nil { + t.Fatalf("unexpected error during ValidatePlugin for testcase: %#v\r\n err:%v", tc, err) + } + if tc.expectedHighestSupportedVersion != "" { + result, err := actual.Compare(tc.expectedHighestSupportedVersion) + if err != nil { + t.Fatalf("comparison failed with %v for testcase %#v", err, tc) + } + if result != 0 { + t.Fatalf("expectedHighestSupportedVersion %v, but got %v for tc: %#v", tc.expectedHighestSupportedVersion, actual, tc) + } + } + } +} + +func TestOffsetMinor(t *testing.T) { + var tests = []struct { + version string + diff int + expectedComponents []uint + }{ + { + version: "1.0.2", + diff: -3, + expectedComponents: []uint{1, 0}, + }, + { + version: "1.3.2-alpha+001", + diff: -2, + expectedComponents: []uint{1, 1}, + }, + { + version: "1.3.2-alpha+001", + diff: -3, + expectedComponents: []uint{1, 0}, + }, + { + version: "1.20", + diff: -5, + expectedComponents: []uint{1, 15}, + }, + { + version: "1.20", + diff: 5, + expectedComponents: []uint{1, 25}, + }, + } + + for _, test := range tests { + version, _ := ParseGeneric(test.version) + if !reflect.DeepEqual(test.expectedComponents, version.OffsetMinor(test.diff).Components()) { + t.Error("parse returned un'expected components") + } + } +} + +func TestParse(t *testing.T) { + + var tests = []struct { + version string + expectErr bool + expectedComponents []uint + expectedPreRelease string + expectedBuildMetadata string + }{ + { + version: "1.0.2", + expectedComponents: []uint{1, 0, 2}, + }, + { + version: "1.0.2-alpha+001", + expectedComponents: []uint{1, 0, 2}, + expectedPreRelease: "alpha", + expectedBuildMetadata: "001", + }, + { + version: "1.2", + expectedComponents: []uint{1, 2}, + }, + { + version: "1.0.2-beta+exp.sha.5114f85", + expectedComponents: []uint{1, 0, 2}, + expectedPreRelease: "beta", + expectedBuildMetadata: "exp.sha.5114f85", + }, + { + version: "a.b.c", + expectErr: true, + }, + } + + for _, test := range tests { + version, err := Parse(test.version) + if test.expectErr { + if err == nil { + t.Fatalf("got no err, expected err") + } + continue + } + if !reflect.DeepEqual(test.expectedComponents, version.Components()) { + t.Error("parse returned un'expected components") + } + if test.expectedPreRelease != version.PreRelease() { + t.Errorf("parse returned version.PreRelease %s, expected %s", test.expectedPreRelease, version.PreRelease()) + } + if test.expectedBuildMetadata != version.BuildMetadata() { + t.Errorf("parse returned version.BuildMetadata %s, expected %s", test.expectedBuildMetadata, version.BuildMetadata()) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/backoff.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/backoff.go new file mode 100644 index 0000000000..177be09a95 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/backoff.go @@ -0,0 +1,518 @@ +/* +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 wait + +import ( + "context" + "math" + "sync" + "time" + + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/utils/clock" +) + +// Backoff holds parameters applied to a Backoff function. +type Backoff struct { + // The initial duration. + Duration time.Duration + // Duration is multiplied by factor each iteration, if factor is not zero + // and the limits imposed by Steps and Cap have not been reached. + // Should not be negative. + // The jitter does not contribute to the updates to the duration parameter. + Factor float64 + // The sleep at each iteration is the duration plus an additional + // amount chosen uniformly at random from the interval between + // zero and `jitter*duration`. + Jitter float64 + // The remaining number of iterations in which the duration + // parameter may change (but progress can be stopped earlier by + // hitting the cap). If not positive, the duration is not + // changed. Used for exponential backoff in combination with + // Factor and Cap. + Steps int + // A limit on revised values of the duration parameter. If a + // multiplication by the factor parameter would make the duration + // exceed the cap then the duration is set to the cap and the + // steps parameter is set to zero. + Cap time.Duration +} + +// Step returns an amount of time to sleep determined by the original +// Duration and Jitter. The backoff is mutated to update its Steps and +// Duration. A nil Backoff always has a zero-duration step. +func (b *Backoff) Step() time.Duration { + if b == nil { + return 0 + } + var nextDuration time.Duration + nextDuration, b.Duration, b.Steps = delay(b.Steps, b.Duration, b.Cap, b.Factor, b.Jitter) + return nextDuration +} + +// DelayFunc returns a function that will compute the next interval to +// wait given the arguments in b. It does not mutate the original backoff +// but the function is safe to use only from a single goroutine. +func (b Backoff) DelayFunc() DelayFunc { + steps := b.Steps + duration := b.Duration + cap := b.Cap + factor := b.Factor + jitter := b.Jitter + + return func() time.Duration { + var nextDuration time.Duration + // jitter is applied per step and is not cumulative over multiple steps + nextDuration, duration, steps = delay(steps, duration, cap, factor, jitter) + return nextDuration + } +} + +// Timer returns a timer implementation appropriate to this backoff's parameters +// for use with wait functions. +func (b Backoff) Timer() Timer { + if b.Steps > 1 || b.Jitter != 0 { + return &variableTimer{new: internalClock.NewTimer, fn: b.DelayFunc()} + } + if b.Duration > 0 { + return &fixedTimer{new: internalClock.NewTicker, interval: b.Duration} + } + return newNoopTimer() +} + +// delay implements the core delay algorithm used in this package. +func delay(steps int, duration, cap time.Duration, factor, jitter float64) (_ time.Duration, next time.Duration, nextSteps int) { + // when steps is non-positive, do not alter the base duration + if steps < 1 { + if jitter > 0 { + return Jitter(duration, jitter), duration, 0 + } + return duration, duration, 0 + } + steps-- + + // calculate the next step's interval + if factor != 0 { + next = time.Duration(float64(duration) * factor) + if cap > 0 && next > cap { + next = cap + steps = 0 + } + } else { + next = duration + } + + // add jitter for this step + if jitter > 0 { + duration = Jitter(duration, jitter) + } + + return duration, next, steps + +} + +// DelayWithReset returns a DelayFunc that will return the appropriate next interval to +// wait. Every resetInterval the backoff parameters are reset to their initial state. +// This method is safe to invoke from multiple goroutines, but all calls will advance +// the backoff state when Factor is set. If Factor is zero, this method is the same as +// invoking b.DelayFunc() since Steps has no impact without Factor. If resetInterval is +// zero no backoff will be performed as the same calling DelayFunc with a zero factor +// and steps. +func (b Backoff) DelayWithReset(c clock.Clock, resetInterval time.Duration) DelayFunc { + if b.Factor <= 0 { + return b.DelayFunc() + } + if resetInterval <= 0 { + b.Steps = 0 + b.Factor = 0 + return b.DelayFunc() + } + return (&backoffManager{ + backoff: b, + initialBackoff: b, + resetInterval: resetInterval, + + clock: c, + lastStart: c.Now(), + timer: nil, + }).Step +} + +// Until loops until stop channel is closed, running f every period. +// +// Until is syntactic sugar on top of JitterUntil with zero jitter factor and +// with sliding = true (which means the timer for period starts after the f +// completes). +// +// Contextual logging: UntilWithContext should be used instead of Until in code which supports contextual logging. +func Until(f func(), period time.Duration, stopCh <-chan struct{}) { + JitterUntil(f, period, 0.0, true, stopCh) +} + +// UntilWithContext loops until context is done, running f every period. +// +// UntilWithContext is syntactic sugar on top of JitterUntilWithContext +// with zero jitter factor and with sliding = true (which means the timer +// for period starts after the f completes). +func UntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) { + JitterUntilWithContext(ctx, f, period, 0.0, true) +} + +// NonSlidingUntil loops until stop channel is closed, running f every +// period. +// +// NonSlidingUntil is syntactic sugar on top of JitterUntil with zero jitter +// factor, with sliding = false (meaning the timer for period starts at the same +// time as the function starts). +// +// Contextual logging: NonSlidingUntilWithContext should be used instead of NonSlidingUntil in code which supports contextual logging. +func NonSlidingUntil(f func(), period time.Duration, stopCh <-chan struct{}) { + JitterUntil(f, period, 0.0, false, stopCh) +} + +// NonSlidingUntilWithContext loops until context is done, running f every +// period. +// +// NonSlidingUntilWithContext is syntactic sugar on top of JitterUntilWithContext +// with zero jitter factor, with sliding = false (meaning the timer for period +// starts at the same time as the function starts). +func NonSlidingUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) { + JitterUntilWithContext(ctx, f, period, 0.0, false) +} + +// JitterUntil loops until stop channel is closed, running f every period. +// +// If jitterFactor is positive, the period is jittered before every run of f. +// If jitterFactor is not positive, the period is unchanged and not jittered. +// +// If sliding is true, the period is computed after f runs. If it is false then +// period includes the runtime for f. +// +// Close stopCh to stop. f may not be invoked if stop channel is already +// closed. Pass NeverStop to if you don't want it stop. +// +// Contextual logging: JitterUntilWithContext should be used instead of JitterUntil in code which supports contextual logging. +func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) { + BackoffUntil(f, NewJitteredBackoffManager(period, jitterFactor, &clock.RealClock{}), sliding, stopCh) +} + +// JitterUntilWithContext loops until context is done, running f every period. +// +// If jitterFactor is positive, the period is jittered before every run of f. +// If jitterFactor is not positive, the period is unchanged and not jittered. +// +// If sliding is true, the period is computed after f runs. If it is false then +// period includes the runtime for f. +// +// Cancel context to stop. f may not be invoked if context is already done. +func JitterUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration, jitterFactor float64, sliding bool) { + BackoffUntilWithContext(ctx, f, NewJitteredBackoffManager(period, jitterFactor, &clock.RealClock{}), sliding) +} + +// BackoffUntil loops until stop channel is closed, run f every duration given by BackoffManager. +// +// If sliding is true, the period is computed after f runs. If it is false then +// period includes the runtime for f. +// +// Contextual logging: BackoffUntilWithContext should be used instead of BackoffUntil in code which supports contextual logging. +func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan struct{}) { + BackoffUntilWithContext(ContextForChannel(stopCh), func(context.Context) { f() }, backoff, sliding) +} + +// BackoffUntilWithContext loops until context is done, run f every duration given by BackoffManager. +// +// If sliding is true, the period is computed after f runs. If it is false then +// period includes the runtime for f. +func BackoffUntilWithContext(ctx context.Context, f func(ctx context.Context), backoff BackoffManager, sliding bool) { + var t clock.Timer + for { + select { + case <-ctx.Done(): + return + default: + } + + if !sliding { + t = backoff.Backoff() + } + + func() { + defer runtime.HandleCrashWithContext(ctx) + f(ctx) + }() + + if sliding { + t = backoff.Backoff() + } + + // NOTE: b/c there is no priority selection in golang + // it is possible for this to race, meaning we could + // trigger t.C and stopCh, and t.C select falls through. + // In order to mitigate we re-check stopCh at the beginning + // of every loop to prevent extra executions of f(). + select { + case <-ctx.Done(): + if !t.Stop() { + <-t.C() + } + return + case <-t.C(): + } + } +} + +// backoffManager provides simple backoff behavior in a threadsafe manner to a caller. +type backoffManager struct { + backoff Backoff + initialBackoff Backoff + resetInterval time.Duration + + clock clock.Clock + + lock sync.Mutex + lastStart time.Time + timer clock.Timer +} + +// Step returns the expected next duration to wait. +func (b *backoffManager) Step() time.Duration { + b.lock.Lock() + defer b.lock.Unlock() + + switch { + case b.resetInterval == 0: + b.backoff = b.initialBackoff + case b.clock.Now().Sub(b.lastStart) > b.resetInterval: + b.backoff = b.initialBackoff + b.lastStart = b.clock.Now() + } + return b.backoff.Step() +} + +// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer +// for exponential backoff. The returned timer must be drained before calling Backoff() the second +// time. +func (b *backoffManager) Backoff() clock.Timer { + b.lock.Lock() + defer b.lock.Unlock() + if b.timer == nil { + b.timer = b.clock.NewTimer(b.Step()) + } else { + b.timer.Reset(b.Step()) + } + return b.timer +} + +// Timer returns a new Timer instance that shares the clock and the reset behavior with all other +// timers. +func (b *backoffManager) Timer() Timer { + return DelayFunc(b.Step).Timer(b.clock) +} + +// BackoffManager manages backoff with a particular scheme based on its underlying implementation. +type BackoffManager interface { + // Backoff returns a shared clock.Timer that is Reset on every invocation. This method is not + // safe for use from multiple threads. It returns a timer for backoff, and caller shall backoff + // until Timer.C() drains. If the second Backoff() is called before the timer from the first + // Backoff() call finishes, the first timer will NOT be drained and result in undetermined + // behavior. + Backoff() clock.Timer +} + +// Deprecated: Will be removed when the legacy polling functions are removed. +type exponentialBackoffManagerImpl struct { + backoff *Backoff + backoffTimer clock.Timer + lastBackoffStart time.Time + initialBackoff time.Duration + backoffResetDuration time.Duration + clock clock.Clock +} + +// NewExponentialBackoffManager returns a manager for managing exponential backoff. Each backoff is jittered and +// backoff will not exceed the given max. If the backoff is not called within resetDuration, the backoff is reset. +// This backoff manager is used to reduce load during upstream unhealthiness. +// +// Deprecated: Will be removed when the legacy Poll methods are removed. Callers should construct a +// Backoff struct, use DelayWithReset() to get a DelayFunc that periodically resets itself, and then +// invoke Timer() when calling wait.BackoffUntil. +// +// Instead of: +// +// bm := wait.NewExponentialBackoffManager(init, max, reset, factor, jitter, clock) +// ... +// wait.BackoffUntil(..., bm.Backoff, ...) +// +// Use: +// +// delayFn := wait.Backoff{ +// Duration: init, +// Cap: max, +// Steps: int(math.Ceil(float64(max) / float64(init))), // now a required argument +// Factor: factor, +// Jitter: jitter, +// }.DelayWithReset(reset, clock) +// wait.BackoffUntil(..., delayFn.Timer(), ...) +func NewExponentialBackoffManager(initBackoff, maxBackoff, resetDuration time.Duration, backoffFactor, jitter float64, c clock.Clock) BackoffManager { + return &exponentialBackoffManagerImpl{ + backoff: &Backoff{ + Duration: initBackoff, + Factor: backoffFactor, + Jitter: jitter, + + // the current impl of wait.Backoff returns Backoff.Duration once steps are used up, which is not + // what we ideally need here, we set it to max int and assume we will never use up the steps + Steps: math.MaxInt32, + Cap: maxBackoff, + }, + backoffTimer: nil, + initialBackoff: initBackoff, + lastBackoffStart: c.Now(), + backoffResetDuration: resetDuration, + clock: c, + } +} + +func (b *exponentialBackoffManagerImpl) getNextBackoff() time.Duration { + if b.clock.Now().Sub(b.lastBackoffStart) > b.backoffResetDuration { + b.backoff.Steps = math.MaxInt32 + b.backoff.Duration = b.initialBackoff + } + b.lastBackoffStart = b.clock.Now() + return b.backoff.Step() +} + +// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for exponential backoff. +// The returned timer must be drained before calling Backoff() the second time +func (b *exponentialBackoffManagerImpl) Backoff() clock.Timer { + if b.backoffTimer == nil { + b.backoffTimer = b.clock.NewTimer(b.getNextBackoff()) + } else { + b.backoffTimer.Reset(b.getNextBackoff()) + } + return b.backoffTimer +} + +// Deprecated: Will be removed when the legacy polling functions are removed. +type jitteredBackoffManagerImpl struct { + clock clock.Clock + duration time.Duration + jitter float64 + backoffTimer clock.Timer +} + +// NewJitteredBackoffManager returns a BackoffManager that backoffs with given duration plus given jitter. If the jitter +// is negative, backoff will not be jittered. +// +// Deprecated: Will be removed when the legacy Poll methods are removed. Callers should construct a +// Backoff struct and invoke Timer() when calling wait.BackoffUntil. +// +// Instead of: +// +// bm := wait.NewJitteredBackoffManager(duration, jitter, clock) +// ... +// wait.BackoffUntil(..., bm.Backoff, ...) +// +// Use: +// +// wait.BackoffUntil(..., wait.Backoff{Duration: duration, Jitter: jitter}.Timer(), ...) +func NewJitteredBackoffManager(duration time.Duration, jitter float64, c clock.Clock) BackoffManager { + return &jitteredBackoffManagerImpl{ + clock: c, + duration: duration, + jitter: jitter, + backoffTimer: nil, + } +} + +func (j *jitteredBackoffManagerImpl) getNextBackoff() time.Duration { + jitteredPeriod := j.duration + if j.jitter > 0.0 { + jitteredPeriod = Jitter(j.duration, j.jitter) + } + return jitteredPeriod +} + +// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for jittered backoff. +// The returned timer must be drained before calling Backoff() the second time +func (j *jitteredBackoffManagerImpl) Backoff() clock.Timer { + backoff := j.getNextBackoff() + if j.backoffTimer == nil { + j.backoffTimer = j.clock.NewTimer(backoff) + } else { + j.backoffTimer.Reset(backoff) + } + return j.backoffTimer +} + +// ExponentialBackoff repeats a condition check with exponential backoff. +// +// It repeatedly checks the condition and then sleeps, using `backoff.Step()` +// to determine the length of the sleep and adjust Duration and Steps. +// Stops and returns as soon as: +// 1. the condition check returns true or an error, +// 2. `backoff.Steps` checks of the condition have been done, or +// 3. a sleep truncated by the cap on duration has been completed. +// In case (1) the returned error is what the condition function returned. +// In all other cases, ErrWaitTimeout is returned. +// +// Since backoffs are often subject to cancellation, we recommend using +// ExponentialBackoffWithContext and passing a context to the method. +func ExponentialBackoff(backoff Backoff, condition ConditionFunc) error { + for backoff.Steps > 0 { + if ok, err := runConditionWithCrashProtection(condition); err != nil || ok { + return err + } + if backoff.Steps == 1 { + break + } + time.Sleep(backoff.Step()) + } + return ErrWaitTimeout +} + +// ExponentialBackoffWithContext repeats a condition check with exponential backoff. +// It immediately returns an error if the condition returns an error, the context is cancelled +// or hits the deadline, or if the maximum attempts defined in backoff is exceeded (ErrWaitTimeout). +// If an error is returned by the condition the backoff stops immediately. The condition will +// never be invoked more than backoff.Steps times. +func ExponentialBackoffWithContext(ctx context.Context, backoff Backoff, condition ConditionWithContextFunc) error { + for backoff.Steps > 0 { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + if ok, err := runConditionWithCrashProtectionWithContext(ctx, condition); err != nil || ok { + return err + } + + if backoff.Steps == 1 { + break + } + + waitBeforeRetry := backoff.Step() + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(waitBeforeRetry): + } + } + + return ErrWaitTimeout +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/delay.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/delay.go new file mode 100644 index 0000000000..1d3dcaa74e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/delay.go @@ -0,0 +1,51 @@ +/* +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 wait + +import ( + "context" + "sync" + "time" + + "k8s.io/utils/clock" +) + +// DelayFunc returns the next time interval to wait. +type DelayFunc func() time.Duration + +// Timer takes an arbitrary delay function and returns a timer that can handle arbitrary interval changes. +// Use Backoff{...}.Timer() for simple delays and more efficient timers. +func (fn DelayFunc) Timer(c clock.Clock) Timer { + return &variableTimer{fn: fn, new: c.NewTimer} +} + +// Until takes an arbitrary delay function and runs until cancelled or the condition indicates exit. This +// offers all of the functionality of the methods in this package. +func (fn DelayFunc) Until(ctx context.Context, immediate, sliding bool, condition ConditionWithContextFunc) error { + return loopConditionUntilContext(ctx, &variableTimer{fn: fn, new: internalClock.NewTimer}, immediate, sliding, condition) +} + +// Concurrent returns a version of this DelayFunc that is safe for use by multiple goroutines that +// wish to share a single delay timer. +func (fn DelayFunc) Concurrent() DelayFunc { + var lock sync.Mutex + return func() time.Duration { + lock.Lock() + defer lock.Unlock() + return fn() + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/doc.go new file mode 100644 index 0000000000..ff89dc170e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/doc.go @@ -0,0 +1,19 @@ +/* +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 wait provides tools for polling or listening for changes +// to a condition. +package wait diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/error.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/error.go new file mode 100644 index 0000000000..dd75801d82 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/error.go @@ -0,0 +1,96 @@ +/* +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 wait + +import ( + "context" + "errors" +) + +// ErrWaitTimeout is returned when the condition was not satisfied in time. +// +// Deprecated: This type will be made private in favor of Interrupted() +// for checking errors or ErrorInterrupted(err) for returning a wrapped error. +var ErrWaitTimeout = ErrorInterrupted(errors.New("timed out waiting for the condition")) + +// Interrupted returns true if the error indicates a Poll, ExponentialBackoff, or +// Until loop exited for any reason besides the condition returning true or an +// error. A loop is considered interrupted if the calling context is cancelled, +// the context reaches its deadline, or a backoff reaches its maximum allowed +// steps. +// +// Callers should use this method instead of comparing the error value directly to +// ErrWaitTimeout, as methods that cancel a context may not return that error. +// +// Instead of: +// +// err := wait.Poll(...) +// if err == wait.ErrWaitTimeout { +// log.Infof("Wait for operation exceeded") +// } else ... +// +// Use: +// +// err := wait.Poll(...) +// if wait.Interrupted(err) { +// log.Infof("Wait for operation exceeded") +// } else ... +func Interrupted(err error) bool { + switch { + case errors.Is(err, errWaitTimeout), + errors.Is(err, context.Canceled), + errors.Is(err, context.DeadlineExceeded): + return true + default: + return false + } +} + +// errInterrupted +type errInterrupted struct { + cause error +} + +// ErrorInterrupted returns an error that indicates the wait was ended +// early for a given reason. If no cause is provided a generic error +// will be used but callers are encouraged to provide a real cause for +// clarity in debugging. +func ErrorInterrupted(cause error) error { + switch cause.(type) { + case errInterrupted: + // no need to wrap twice since errInterrupted is only needed + // once in a chain + return cause + default: + return errInterrupted{cause} + } +} + +// errWaitTimeout is the private version of the previous ErrWaitTimeout +// and is private to prevent direct comparison. Use ErrorInterrupted(err) +// to get an error that will return true for Interrupted(err). +var errWaitTimeout = errInterrupted{} + +func (e errInterrupted) Unwrap() error { return e.cause } +func (e errInterrupted) Is(target error) bool { return target == errWaitTimeout } +func (e errInterrupted) Error() string { + if e.cause == nil { + // returns the same error message as historical behavior + return "timed out waiting for the condition" + } + return e.cause.Error() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/error_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/error_test.go new file mode 100644 index 0000000000..0c96f06198 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/error_test.go @@ -0,0 +1,144 @@ +/* +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 wait + +import ( + "context" + "errors" + "fmt" + "testing" +) + +type errWrapper struct { + wrapped error +} + +func (w errWrapper) Unwrap() error { + return w.wrapped +} +func (w errWrapper) Error() string { + return fmt.Sprintf("wrapped: %v", w.wrapped) +} + +type errNotWrapper struct { + wrapped error +} + +func (w errNotWrapper) Error() string { + return fmt.Sprintf("wrapped: %v", w.wrapped) +} + +func TestInterrupted(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + { + err: ErrWaitTimeout, + want: true, + }, + { + err: context.Canceled, + want: true, + }, { + err: context.DeadlineExceeded, + want: true, + }, + { + err: errWrapper{ErrWaitTimeout}, + want: true, + }, + { + err: errWrapper{context.Canceled}, + want: true, + }, + { + err: errWrapper{context.DeadlineExceeded}, + want: true, + }, + { + err: ErrorInterrupted(nil), + want: true, + }, + { + err: ErrorInterrupted(errors.New("unknown")), + want: true, + }, + { + err: ErrorInterrupted(context.Canceled), + want: true, + }, + { + err: ErrorInterrupted(ErrWaitTimeout), + want: true, + }, + + { + err: nil, + }, + { + err: errors.New("not a cancellation"), + }, + { + err: errNotWrapper{ErrWaitTimeout}, + }, + { + err: errNotWrapper{context.Canceled}, + }, + { + err: errNotWrapper{context.DeadlineExceeded}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Interrupted(tt.err); got != tt.want { + t.Errorf("Interrupted() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestErrorInterrupted(t *testing.T) { + internalErr := errInterrupted{} + if ErrorInterrupted(internalErr) != internalErr { + t.Fatalf("error should not be wrapped twice") + } + + internalErr = errInterrupted{errInterrupted{}} + if ErrorInterrupted(internalErr) != internalErr { + t.Fatalf("object should be identical") + } + + in := errors.New("test") + actual, expected := ErrorInterrupted(in), (errInterrupted{in}) + if actual != expected { + t.Fatalf("did not wrap error") + } + if !errors.Is(actual, errWaitTimeout) { + t.Fatalf("does not obey errors.Is contract") + } + if actual.Error() != in.Error() { + t.Fatalf("unexpected error output") + } + if !Interrupted(actual) { + t.Fatalf("is not Interrupted") + } + if Interrupted(in) { + t.Fatalf("should not be Interrupted") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/loop.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/loop.go new file mode 100644 index 0000000000..9f9b929ffa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/loop.go @@ -0,0 +1,95 @@ +/* +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 wait + +import ( + "context" + "time" + + "k8s.io/apimachinery/pkg/util/runtime" +) + +// loopConditionUntilContext executes the provided condition at intervals defined by +// the provided timer until the provided context is cancelled, the condition returns +// true, or the condition returns an error. If sliding is true, the period is computed +// after condition runs. If it is false then period includes the runtime for condition. +// If immediate is false the first delay happens before any call to condition, if +// immediate is true the condition will be invoked before waiting and guarantees that +// the condition is invoked at least once, regardless of whether the context has been +// cancelled. The returned error is the error returned by the last condition or the +// context error if the context was terminated. +// +// This is the common loop construct for all polling in the wait package. +func loopConditionUntilContext(ctx context.Context, t Timer, immediate, sliding bool, condition ConditionWithContextFunc) error { + defer t.Stop() + + var timeCh <-chan time.Time + doneCh := ctx.Done() + + if !sliding { + timeCh = t.C() + } + + // if immediate is true the condition is + // guaranteed to be executed at least once, + // if we haven't requested immediate execution, delay once + if immediate { + if ok, err := func() (bool, error) { + defer runtime.HandleCrashWithContext(ctx) + return condition(ctx) + }(); err != nil || ok { + return err + } + } + + if sliding { + timeCh = t.C() + } + + for { + + // Wait for either the context to be cancelled or the next invocation be called + select { + case <-doneCh: + return ctx.Err() + case <-timeCh: + } + + // IMPORTANT: Because there is no channel priority selection in golang + // it is possible for very short timers to "win" the race in the previous select + // repeatedly even when the context has been canceled. We therefore must + // explicitly check for context cancellation on every loop and exit if true to + // guarantee that we don't invoke condition more than once after context has + // been cancelled. + if err := ctx.Err(); err != nil { + return err + } + + if !sliding { + t.Next() + } + if ok, err := func() (bool, error) { + defer runtime.HandleCrashWithContext(ctx) + return condition(ctx) + }(); err != nil || ok { + return err + } + if sliding { + t.Next() + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/loop_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/loop_test.go new file mode 100644 index 0000000000..63bfa8540f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/loop_test.go @@ -0,0 +1,535 @@ +/* +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 wait + +import ( + "context" + "errors" + "fmt" + "reflect" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "k8s.io/utils/clock" + testingclock "k8s.io/utils/clock/testing" +) + +func timerWithClock(t Timer, c clock.WithTicker) Timer { + switch t := t.(type) { + case *fixedTimer: + t.new = c.NewTicker + case *variableTimer: + t.new = c.NewTimer + default: + panic("unrecognized timer type, cannot inject clock") + } + return t +} + +func Test_loopConditionWithContextImmediateDelay(t *testing.T) { + fakeClock := testingclock.NewFakeClock(time.Time{}) + backoff := Backoff{Duration: time.Second} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + expectedError := errors.New("Expected error") + var attempt int + f := ConditionFunc(func() (bool, error) { + attempt++ + return false, expectedError + }) + + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + if err := loopConditionUntilContext(ctx, timerWithClock(backoff.Timer(), fakeClock), false, true, f.WithContext()); err == nil || err != expectedError { + t.Errorf("unexpected error: %v", err) + } + }() + + for !fakeClock.HasWaiters() { + time.Sleep(time.Microsecond) + } + + fakeClock.Step(time.Second - time.Millisecond) + if attempt != 0 { + t.Fatalf("should still be waiting for condition") + } + fakeClock.Step(2 * time.Millisecond) + + select { + case <-doneCh: + case <-time.After(time.Second): + t.Fatalf("should have exited after a single loop") + } + if attempt != 1 { + t.Fatalf("expected attempt") + } +} + +func Test_loopConditionUntilContext_semantic(t *testing.T) { + defaultCallback := func(_ int) (bool, error) { + return false, nil + } + + conditionErr := errors.New("condition failed") + + tests := []struct { + name string + immediate bool + sliding bool + context func() (context.Context, context.CancelFunc) + callback func(calls int) (bool, error) + cancelContextAfter int + attemptsExpected int + errExpected error + timer Timer + }{ + { + name: "condition successful is only one attempt", + callback: func(attempts int) (bool, error) { + return true, nil + }, + attemptsExpected: 1, + }, + { + name: "delayed condition successful causes return and attempts", + callback: func(attempts int) (bool, error) { + return attempts > 1, nil + }, + attemptsExpected: 2, + }, + { + name: "delayed condition successful causes return and attempts many times", + callback: func(attempts int) (bool, error) { + return attempts >= 100, nil + }, + attemptsExpected: 100, + }, + { + name: "condition returns error even if ok is true", + callback: func(_ int) (bool, error) { + return true, conditionErr + }, + attemptsExpected: 1, + errExpected: conditionErr, + }, + { + name: "condition exits after an error", + callback: func(_ int) (bool, error) { + return false, conditionErr + }, + attemptsExpected: 1, + errExpected: conditionErr, + }, + { + name: "context already canceled no attempts expected", + context: cancelledContext, + callback: defaultCallback, + attemptsExpected: 0, + errExpected: context.Canceled, + }, + { + name: "context already canceled condition success and immediate 1 attempt expected", + context: cancelledContext, + callback: func(_ int) (bool, error) { + return true, nil + }, + immediate: true, + attemptsExpected: 1, + }, + { + name: "context already canceled condition fail and immediate 1 attempt expected", + context: cancelledContext, + callback: func(_ int) (bool, error) { + return false, conditionErr + }, + immediate: true, + attemptsExpected: 1, + errExpected: conditionErr, + }, + { + name: "context already canceled and immediate 1 attempt expected", + context: cancelledContext, + callback: defaultCallback, + immediate: true, + attemptsExpected: 1, + errExpected: context.Canceled, + }, + { + name: "context cancelled after 5 attempts", + context: defaultContext, + callback: defaultCallback, + cancelContextAfter: 5, + attemptsExpected: 5, + errExpected: context.Canceled, + }, + { + name: "context cancelled and immediate after 5 attempts", + context: defaultContext, + callback: defaultCallback, + immediate: true, + cancelContextAfter: 5, + attemptsExpected: 5, + errExpected: context.Canceled, + }, + { + name: "context at deadline and immediate 1 attempt expected", + context: deadlinedContext, + callback: defaultCallback, + immediate: true, + attemptsExpected: 1, + errExpected: context.DeadlineExceeded, + }, + { + name: "context at deadline no attempts expected", + context: deadlinedContext, + callback: defaultCallback, + attemptsExpected: 0, + errExpected: context.DeadlineExceeded, + }, + { + name: "context canceled before the second execution and immediate", + immediate: true, + context: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), time.Second) + }, + callback: func(attempts int) (bool, error) { + return false, nil + }, + attemptsExpected: 1, + errExpected: context.DeadlineExceeded, + timer: Backoff{Duration: 2 * time.Second}.Timer(), + }, + { + name: "immediate and long duration of condition and sliding false", + immediate: true, + sliding: false, + context: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), time.Second) + }, + callback: func(attempts int) (bool, error) { + if attempts >= 4 { + return true, nil + } + time.Sleep(time.Second / 5) + return false, nil + }, + attemptsExpected: 4, + timer: Backoff{Duration: time.Second / 5, Jitter: 0.001}.Timer(), + }, + { + name: "immediate and long duration of condition and sliding true", + immediate: true, + sliding: true, + context: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), time.Second) + }, + callback: func(attempts int) (bool, error) { + if attempts >= 4 { + return true, nil + } + time.Sleep(time.Second / 5) + return false, nil + }, + errExpected: context.DeadlineExceeded, + attemptsExpected: 3, + timer: Backoff{Duration: time.Second / 5, Jitter: 0.001}.Timer(), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + contextFn := test.context + if contextFn == nil { + contextFn = defaultContext + } + ctx, cancel := contextFn() + defer cancel() + + timer := test.timer + if timer == nil { + timer = Backoff{Duration: time.Microsecond}.Timer() + } + attempts := 0 + err := loopConditionUntilContext(ctx, timer, test.immediate, test.sliding, func(_ context.Context) (bool, error) { + attempts++ + defer func() { + if test.cancelContextAfter > 0 && test.cancelContextAfter == attempts { + cancel() + } + }() + return test.callback(attempts) + }) + + if test.errExpected != err { + t.Errorf("expected error: %v but got: %v", test.errExpected, err) + } + + if test.attemptsExpected != attempts { + t.Errorf("expected attempts count: %d but got: %d", test.attemptsExpected, attempts) + } + }) + } +} + +type timerWrapper struct { + timer clock.Timer + resets []time.Duration + onReset func(d time.Duration) +} + +func (w *timerWrapper) C() <-chan time.Time { return w.timer.C() } +func (w *timerWrapper) Stop() bool { return w.timer.Stop() } +func (w *timerWrapper) Reset(d time.Duration) bool { + w.resets = append(w.resets, d) + b := w.timer.Reset(d) + if w.onReset != nil { + w.onReset(d) + } + return b +} + +func Test_loopConditionUntilContext_timings(t *testing.T) { + // Verify that timings returned by the delay func are passed to the timer, and that + // the timer advancing is enough to drive the state machine. Not a deep verification + // of the behavior of the loop, but tests that we drive the scenario to completion. + tests := []struct { + name string + delayFn DelayFunc + immediate bool + sliding bool + context func() (context.Context, context.CancelFunc) + callback func(calls int, lastInterval time.Duration) (bool, error) + cancelContextAfter int + attemptsExpected int + errExpected error + expectedIntervals func(t *testing.T, delays []time.Duration, delaysRequested []time.Duration) + }{ + { + name: "condition success", + delayFn: Backoff{Duration: time.Second, Steps: 2, Factor: 2.0, Jitter: 0}.DelayFunc(), + callback: func(attempts int, _ time.Duration) (bool, error) { + return true, nil + }, + attemptsExpected: 1, + expectedIntervals: func(t *testing.T, delays []time.Duration, delaysRequested []time.Duration) { + if reflect.DeepEqual(delays, []time.Duration{time.Second, 2 * time.Second}) { + return + } + if reflect.DeepEqual(delaysRequested, []time.Duration{time.Second}) { + return + } + }, + }, + { + name: "condition success and immediate", + immediate: true, + delayFn: Backoff{Duration: time.Second, Steps: 2, Factor: 2.0, Jitter: 0}.DelayFunc(), + callback: func(attempts int, _ time.Duration) (bool, error) { + return true, nil + }, + attemptsExpected: 1, + expectedIntervals: func(t *testing.T, delays []time.Duration, delaysRequested []time.Duration) { + if reflect.DeepEqual(delays, []time.Duration{time.Second}) { + return + } + if reflect.DeepEqual(delaysRequested, []time.Duration{}) { + return + } + }, + }, + { + name: "condition success and sliding", + sliding: true, + delayFn: Backoff{Duration: time.Second, Steps: 2, Factor: 2.0, Jitter: 0}.DelayFunc(), + callback: func(attempts int, _ time.Duration) (bool, error) { + return true, nil + }, + attemptsExpected: 1, + expectedIntervals: func(t *testing.T, delays []time.Duration, delaysRequested []time.Duration) { + if reflect.DeepEqual(delays, []time.Duration{time.Second}) { + return + } + if !reflect.DeepEqual(delays, delaysRequested) { + t.Fatalf("sliding non-immediate should have equal delays: %v", cmp.Diff(delays, delaysRequested)) + } + }, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%s/sliding=%t/immediate=%t", test.name, test.sliding, test.immediate), func(t *testing.T) { + contextFn := test.context + if contextFn == nil { + contextFn = defaultContext + } + ctx, cancel := contextFn() + defer cancel() + + fakeClock := &testingclock.FakeClock{} + var fakeTimers []*timerWrapper + timerFn := func(d time.Duration) clock.Timer { + t := fakeClock.NewTimer(d) + fakeClock.Step(d + 1) + w := &timerWrapper{timer: t, resets: []time.Duration{d}, onReset: func(d time.Duration) { + fakeClock.Step(d + 1) + }} + fakeTimers = append(fakeTimers, w) + return w + } + + delayFn := test.delayFn + if delayFn == nil { + delayFn = Backoff{Duration: time.Microsecond}.DelayFunc() + } + var delays []time.Duration + wrappedDelayFn := func() time.Duration { + d := delayFn() + delays = append(delays, d) + return d + } + timer := &variableTimer{fn: wrappedDelayFn, new: timerFn} + + attempts := 0 + err := loopConditionUntilContext(ctx, timer, test.immediate, test.sliding, func(_ context.Context) (bool, error) { + attempts++ + defer func() { + if test.cancelContextAfter > 0 && test.cancelContextAfter == attempts { + cancel() + } + }() + lastInterval := time.Duration(-1) + if len(delays) > 0 { + lastInterval = delays[len(delays)-1] + } + return test.callback(attempts, lastInterval) + }) + + if test.errExpected != err { + t.Errorf("expected error: %v but got: %v", test.errExpected, err) + } + + if test.attemptsExpected != attempts { + t.Errorf("expected attempts count: %d but got: %d", test.attemptsExpected, attempts) + } + switch len(fakeTimers) { + case 0: + test.expectedIntervals(t, delays, nil) + case 1: + test.expectedIntervals(t, delays, fakeTimers[0].resets) + default: + t.Fatalf("expected zero or one timers: %#v", fakeTimers) + } + }) + } +} + +// Test_loopConditionUntilContext_timings runs actual timing loops and calculates the delta. This +// test depends on high precision wakeups which depends on low CPU contention so it is not a +// candidate to run during normal unit test execution (nor is it a benchmark or example). Instead, +// it can be run manually if there is a scenario where we suspect the timings are off and other +// tests haven't caught it. A final sanity test that would have to be run serially in isolation. +func Test_loopConditionUntilContext_Elapsed(t *testing.T) { + const maxAttempts = 10 + // TODO: this may be too aggressive, but the overhead should be minor + const estimatedLoopOverhead = time.Millisecond + // estimate how long this delay can be + intervalMax := func(backoff Backoff) time.Duration { + d := backoff.Duration + if backoff.Jitter > 0 { + d += time.Duration(backoff.Jitter * float64(d)) + } + return d + } + // estimate how short this delay can be + intervalMin := func(backoff Backoff) time.Duration { + d := backoff.Duration + return d + } + + // Because timing is dependent other factors in test environments, such as + // whether the OS or go runtime scheduler wake the timers, excess duration + // is logged by default and can be converted to a fatal error for testing. + // fail := t.Fatalf + fail := t.Logf + + for _, test := range []struct { + name string + backoff Backoff + t reflect.Type + }{ + {name: "variable timer with jitter", backoff: Backoff{Duration: time.Millisecond, Jitter: 1.0}, t: reflect.TypeOf(&variableTimer{})}, + {name: "fixed timer", backoff: Backoff{Duration: time.Millisecond}, t: reflect.TypeOf(&fixedTimer{})}, + {name: "no-op timer", backoff: Backoff{}, t: reflect.TypeOf(noopTimer{})}, + } { + t.Run(test.name, func(t *testing.T) { + var attempts int + start := time.Now() + timer := test.backoff.Timer() + if test.t != reflect.ValueOf(timer).Type() { + t.Fatalf("unexpected timer type %T: expected %v", timer, test.t) + } + if err := loopConditionUntilContext(context.Background(), timer, false, false, func(_ context.Context) (bool, error) { + attempts++ + if attempts > maxAttempts { + t.Fatalf("should not reach %d attempts", maxAttempts+1) + } + return attempts >= maxAttempts, nil + }); err != nil { + t.Fatal(err) + } + duration := time.Since(start) + if min := maxAttempts * intervalMin(test.backoff); duration < min { + fail("elapsed duration %v < expected min duration %v", duration, min) + } + if max := maxAttempts * (intervalMax(test.backoff) + estimatedLoopOverhead); duration > max { + fail("elapsed duration %v > expected max duration %v", duration, max) + } + }) + } +} + +func Benchmark_loopConditionUntilContext_ZeroDuration(b *testing.B) { + ctx := context.Background() + b.ResetTimer() + for i := 0; i < b.N; i++ { + attempts := 0 + if err := loopConditionUntilContext(ctx, Backoff{Duration: 0}.Timer(), true, false, func(_ context.Context) (bool, error) { + attempts++ + return attempts >= 100, nil + }); err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} + +func Benchmark_loopConditionUntilContext_ShortDuration(b *testing.B) { + ctx := context.Background() + b.ResetTimer() + for i := 0; i < b.N; i++ { + attempts := 0 + if err := loopConditionUntilContext(ctx, Backoff{Duration: time.Microsecond}.Timer(), true, false, func(_ context.Context) (bool, error) { + attempts++ + return attempts >= 100, nil + }); err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/poll.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/poll.go new file mode 100644 index 0000000000..231d4c3842 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/poll.go @@ -0,0 +1,315 @@ +/* +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 wait + +import ( + "context" + "time" +) + +// PollUntilContextCancel tries a condition func until it returns true, an error, or the context +// is cancelled or hits a deadline. condition will be invoked after the first interval if the +// context is not cancelled first. The returned error will be from ctx.Err(), the condition's +// err return value, or nil. If invoking condition takes longer than interval the next condition +// will be invoked immediately. When using very short intervals, condition may be invoked multiple +// times before a context cancellation is detected. If immediate is true, condition will be +// invoked before waiting and guarantees that condition is invoked at least once, regardless of +// whether the context has been cancelled. +func PollUntilContextCancel(ctx context.Context, interval time.Duration, immediate bool, condition ConditionWithContextFunc) error { + return loopConditionUntilContext(ctx, Backoff{Duration: interval}.Timer(), immediate, false, condition) +} + +// PollUntilContextTimeout will terminate polling after timeout duration by setting a context +// timeout. This is provided as a convenience function for callers not currently executing under +// a deadline and is equivalent to: +// +// deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout) +// err := PollUntilContextCancel(deadlineCtx, interval, immediate, condition) +// +// The deadline context will be cancelled if the Poll succeeds before the timeout, simplifying +// inline usage. All other behavior is identical to PollUntilContextCancel. +func PollUntilContextTimeout(ctx context.Context, interval, timeout time.Duration, immediate bool, condition ConditionWithContextFunc) error { + deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout) + defer deadlineCancel() + return loopConditionUntilContext(deadlineCtx, Backoff{Duration: interval}.Timer(), immediate, false, condition) +} + +// Poll tries a condition func until it returns true, an error, or the timeout +// is reached. +// +// Poll always waits the interval before the run of 'condition'. +// 'condition' will always be invoked at least once. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// If you want to Poll something forever, see PollInfinite. +// +// Deprecated: This method does not return errors from context, use PollUntilContextTimeout. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func Poll(interval, timeout time.Duration, condition ConditionFunc) error { + return PollWithContext(context.Background(), interval, timeout, condition.WithContext()) +} + +// PollWithContext tries a condition func until it returns true, an error, +// or when the context expires or the timeout is reached, whichever +// happens first. +// +// PollWithContext always waits the interval before the run of 'condition'. +// 'condition' will always be invoked at least once. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// If you want to Poll something forever, see PollInfinite. +// +// Deprecated: This method does not return errors from context, use PollUntilContextTimeout. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error { + return poll(ctx, false, poller(interval, timeout), condition) +} + +// PollUntil tries a condition func until it returns true, an error or stopCh is +// closed. +// +// PollUntil always waits interval before the first run of 'condition'. +// 'condition' will always be invoked at least once. +// +// Deprecated: This method does not return errors from context, use PollUntilContextCancel. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { + return PollUntilWithContext(ContextForChannel(stopCh), interval, condition.WithContext()) +} + +// PollUntilWithContext tries a condition func until it returns true, +// an error or the specified context is cancelled or expired. +// +// PollUntilWithContext always waits interval before the first run of 'condition'. +// 'condition' will always be invoked at least once. +// +// Deprecated: This method does not return errors from context, use PollUntilContextCancel. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { + return poll(ctx, false, poller(interval, 0), condition) +} + +// PollInfinite tries a condition func until it returns true or an error +// +// PollInfinite always waits the interval before the run of 'condition'. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// Deprecated: This method does not return errors from context, use PollUntilContextCancel. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollInfinite(interval time.Duration, condition ConditionFunc) error { + return PollInfiniteWithContext(context.Background(), interval, condition.WithContext()) +} + +// PollInfiniteWithContext tries a condition func until it returns true or an error +// +// PollInfiniteWithContext always waits the interval before the run of 'condition'. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// Deprecated: This method does not return errors from context, use PollUntilContextCancel. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { + return poll(ctx, false, poller(interval, 0), condition) +} + +// PollImmediate tries a condition func until it returns true, an error, or the timeout +// is reached. +// +// PollImmediate always checks 'condition' before waiting for the interval. 'condition' +// will always be invoked at least once. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// If you want to immediately Poll something forever, see PollImmediateInfinite. +// +// Deprecated: This method does not return errors from context, use PollUntilContextTimeout. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error { + return PollImmediateWithContext(context.Background(), interval, timeout, condition.WithContext()) +} + +// PollImmediateWithContext tries a condition func until it returns true, an error, +// or the timeout is reached or the specified context expires, whichever happens first. +// +// PollImmediateWithContext always checks 'condition' before waiting for the interval. +// 'condition' will always be invoked at least once. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// If you want to immediately Poll something forever, see PollImmediateInfinite. +// +// Deprecated: This method does not return errors from context, use PollUntilContextTimeout. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollImmediateWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error { + return poll(ctx, true, poller(interval, timeout), condition) +} + +// PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed. +// +// PollImmediateUntil runs the 'condition' before waiting for the interval. +// 'condition' will always be invoked at least once. +// +// Deprecated: This method does not return errors from context, use PollUntilContextCancel. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollImmediateUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error { + return PollImmediateUntilWithContext(ContextForChannel(stopCh), interval, condition.WithContext()) +} + +// PollImmediateUntilWithContext tries a condition func until it returns true, +// an error or the specified context is cancelled or expired. +// +// PollImmediateUntilWithContext runs the 'condition' before waiting for the interval. +// 'condition' will always be invoked at least once. +// +// Deprecated: This method does not return errors from context, use PollUntilContextCancel. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollImmediateUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { + return poll(ctx, true, poller(interval, 0), condition) +} + +// PollImmediateInfinite tries a condition func until it returns true or an error +// +// PollImmediateInfinite runs the 'condition' before waiting for the interval. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// Deprecated: This method does not return errors from context, use PollUntilContextCancel. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollImmediateInfinite(interval time.Duration, condition ConditionFunc) error { + return PollImmediateInfiniteWithContext(context.Background(), interval, condition.WithContext()) +} + +// PollImmediateInfiniteWithContext tries a condition func until it returns true +// or an error or the specified context gets cancelled or expired. +// +// PollImmediateInfiniteWithContext runs the 'condition' before waiting for the interval. +// +// Some intervals may be missed if the condition takes too long or the time +// window is too short. +// +// Deprecated: This method does not return errors from context, use PollUntilContextCancel. +// Note that the new method will no longer return ErrWaitTimeout and instead return errors +// defined by the context package. Will be removed in a future release. +func PollImmediateInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error { + return poll(ctx, true, poller(interval, 0), condition) +} + +// Internally used, each of the public 'Poll*' function defined in this +// package should invoke this internal function with appropriate parameters. +// ctx: the context specified by the caller, for infinite polling pass +// a context that never gets cancelled or expired. +// immediate: if true, the 'condition' will be invoked before waiting for the interval, +// in this case 'condition' will always be invoked at least once. +// wait: user specified WaitFunc function that controls at what interval the condition +// function should be invoked periodically and whether it is bound by a timeout. +// condition: user specified ConditionWithContextFunc function. +// +// Deprecated: will be removed in favor of loopConditionUntilContext. +func poll(ctx context.Context, immediate bool, wait waitWithContextFunc, condition ConditionWithContextFunc) error { + if immediate { + done, err := runConditionWithCrashProtectionWithContext(ctx, condition) + if err != nil { + return err + } + if done { + return nil + } + } + + select { + case <-ctx.Done(): + // returning ctx.Err() will break backward compatibility, use new PollUntilContext* + // methods instead + return ErrWaitTimeout + default: + return waitForWithContext(ctx, wait, condition) + } +} + +// poller returns a WaitFunc that will send to the channel every interval until +// timeout has elapsed and then closes the channel. +// +// Over very short intervals you may receive no ticks before the channel is +// closed. A timeout of 0 is interpreted as an infinity, and in such a case +// it would be the caller's responsibility to close the done channel. +// Failure to do so would result in a leaked goroutine. +// +// Output ticks are not buffered. If the channel is not ready to receive an +// item, the tick is skipped. +// +// Deprecated: Will be removed in a future release. +func poller(interval, timeout time.Duration) waitWithContextFunc { + return waitWithContextFunc(func(ctx context.Context) <-chan struct{} { + ch := make(chan struct{}) + + go func() { + defer close(ch) + + tick := time.NewTicker(interval) + defer tick.Stop() + + var after <-chan time.Time + if timeout != 0 { + // time.After is more convenient, but it + // potentially leaves timers around much longer + // than necessary if we exit early. + timer := time.NewTimer(timeout) + after = timer.C + defer timer.Stop() + } + + for { + select { + case <-tick.C: + // If the consumer isn't ready for this signal drop it and + // check the other channels. + select { + case ch <- struct{}{}: + default: + } + case <-after: + return + case <-ctx.Done(): + return + } + } + }() + + return ch + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/timer.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/timer.go new file mode 100644 index 0000000000..3efba32132 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/timer.go @@ -0,0 +1,121 @@ +/* +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 wait + +import ( + "time" + + "k8s.io/utils/clock" +) + +// Timer abstracts how wait functions interact with time runtime efficiently. Test +// code may implement this interface directly but package consumers are encouraged +// to use the Backoff type as the primary mechanism for acquiring a Timer. The +// interface is a simplification of clock.Timer to prevent misuse. Timers are not +// expected to be safe for calls from multiple goroutines. +type Timer interface { + // C returns a channel that will receive a struct{} each time the timer fires. + // The channel should not be waited on after Stop() is invoked. It is allowed + // to cache the returned value of C() for the lifetime of the Timer. + C() <-chan time.Time + // Next is invoked by wait functions to signal timers that the next interval + // should begin. You may only use Next() if you have drained the channel C(). + // You should not call Next() after Stop() is invoked. + Next() + // Stop releases the timer. It is safe to invoke if no other methods have been + // called. + Stop() +} + +type noopTimer struct { + closedCh <-chan time.Time +} + +// newNoopTimer creates a timer with a unique channel to avoid contention +// for the channel's lock across multiple unrelated timers. +func newNoopTimer() noopTimer { + ch := make(chan time.Time) + close(ch) + return noopTimer{closedCh: ch} +} + +func (t noopTimer) C() <-chan time.Time { + return t.closedCh +} +func (noopTimer) Next() {} +func (noopTimer) Stop() {} + +type variableTimer struct { + fn DelayFunc + t clock.Timer + new func(time.Duration) clock.Timer +} + +func (t *variableTimer) C() <-chan time.Time { + if t.t == nil { + d := t.fn() + t.t = t.new(d) + } + return t.t.C() +} +func (t *variableTimer) Next() { + if t.t == nil { + return + } + d := t.fn() + t.t.Reset(d) +} +func (t *variableTimer) Stop() { + if t.t == nil { + return + } + t.t.Stop() + t.t = nil +} + +type fixedTimer struct { + interval time.Duration + t clock.Ticker + new func(time.Duration) clock.Ticker +} + +func (t *fixedTimer) C() <-chan time.Time { + if t.t == nil { + t.t = t.new(t.interval) + } + return t.t.C() +} +func (t *fixedTimer) Next() { + // no-op for fixed timers +} +func (t *fixedTimer) Stop() { + if t.t == nil { + return + } + t.t.Stop() + t.t = nil +} + +var ( + // RealTimer can be passed to methods that need a clock.Timer. + RealTimer = clock.RealClock{}.NewTimer +) + +var ( + // internalClock is used for test injection of clocks + internalClock = clock.RealClock{} +) diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/wait.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/wait.go new file mode 100644 index 0000000000..7379a8d5ac --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/wait.go @@ -0,0 +1,228 @@ +/* +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 wait + +import ( + "context" + "math/rand" + "sync" + "time" + + "k8s.io/apimachinery/pkg/util/runtime" +) + +// For any test of the style: +// +// ... +// <- time.After(timeout): +// t.Errorf("Timed out") +// +// The value for timeout should effectively be "forever." Obviously we don't want our tests to truly lock up forever, but 30s +// is long enough that it is effectively forever for the things that can slow down a run on a heavily contended machine +// (GC, seeks, etc), but not so long as to make a developer ctrl-c a test run if they do happen to break that test. +var ForeverTestTimeout = time.Second * 30 + +// NeverStop may be passed to Until to make it never stop. +var NeverStop <-chan struct{} = make(chan struct{}) + +// Group allows to start a group of goroutines and wait for their completion. +type Group struct { + wg sync.WaitGroup +} + +func (g *Group) Wait() { + g.wg.Wait() +} + +// StartWithChannel starts f in a new goroutine in the group. +// stopCh is passed to f as an argument. f should stop when stopCh is available. +func (g *Group) StartWithChannel(stopCh <-chan struct{}, f func(stopCh <-chan struct{})) { + g.Start(func() { + f(stopCh) + }) +} + +// StartWithContext starts f in a new goroutine in the group. +// ctx is passed to f as an argument. f should stop when ctx.Done() is available. +func (g *Group) StartWithContext(ctx context.Context, f func(context.Context)) { + g.Start(func() { + f(ctx) + }) +} + +// Start starts f in a new goroutine in the group. +func (g *Group) Start(f func()) { + g.wg.Add(1) + go func() { + defer g.wg.Done() + f() + }() +} + +// Forever calls f every period for ever. +// +// Forever is syntactic sugar on top of Until. +func Forever(f func(), period time.Duration) { + Until(f, period, NeverStop) +} + +// jitterRand is a dedicated random source for jitter calculations. +// It defaults to rand.Float64, but is a package variable so it can be overridden to make unit tests deterministic. +var jitterRand = rand.Float64 + +// Jitter returns a time.Duration between duration and duration + maxFactor * +// duration. +// +// This allows clients to avoid converging on periodic behavior. If maxFactor +// is 0.0, a suggested default value will be chosen. +func Jitter(duration time.Duration, maxFactor float64) time.Duration { + if maxFactor <= 0.0 { + maxFactor = 1.0 + } + wait := duration + time.Duration(jitterRand()*maxFactor*float64(duration)) + return wait +} + +// ConditionFunc returns true if the condition is satisfied, or an error +// if the loop should be aborted. +type ConditionFunc func() (done bool, err error) + +// ConditionWithContextFunc returns true if the condition is satisfied, or an error +// if the loop should be aborted. +// +// The caller passes along a context that can be used by the condition function. +type ConditionWithContextFunc func(context.Context) (done bool, err error) + +// WithContext converts a ConditionFunc into a ConditionWithContextFunc +func (cf ConditionFunc) WithContext() ConditionWithContextFunc { + return func(context.Context) (done bool, err error) { + return cf() + } +} + +// ContextForChannel provides a context that will be treated as cancelled +// when the provided parentCh is closed. The implementation returns +// context.Canceled for Err() if and only if the parentCh is closed. +func ContextForChannel(parentCh <-chan struct{}) context.Context { + return channelContext{stopCh: parentCh} +} + +var _ context.Context = channelContext{} + +// channelContext will behave as if the context were cancelled when stopCh is +// closed. +type channelContext struct { + stopCh <-chan struct{} +} + +func (c channelContext) Done() <-chan struct{} { return c.stopCh } +func (c channelContext) Err() error { + select { + case <-c.stopCh: + return context.Canceled + default: + return nil + } +} +func (c channelContext) Deadline() (time.Time, bool) { return time.Time{}, false } +func (c channelContext) Value(key any) any { return nil } + +// runConditionWithCrashProtection runs a ConditionFunc with crash protection. +// +// Deprecated: Will be removed when the legacy polling methods are removed. +func runConditionWithCrashProtection(condition ConditionFunc) (bool, error) { + //nolint:logcheck // Already deprecated. + defer runtime.HandleCrash() + return condition() +} + +// runConditionWithCrashProtectionWithContext runs a ConditionWithContextFunc +// with crash protection. +// +// Deprecated: Will be removed when the legacy polling methods are removed. +func runConditionWithCrashProtectionWithContext(ctx context.Context, condition ConditionWithContextFunc) (bool, error) { + defer runtime.HandleCrashWithContext(ctx) + return condition(ctx) +} + +// waitFunc creates a channel that receives an item every time a test +// should be executed and is closed when the last test should be invoked. +// +// Deprecated: Will be removed in a future release in favor of +// loopConditionUntilContext. +type waitFunc func(done <-chan struct{}) <-chan struct{} + +// WithContext converts the WaitFunc to an equivalent WaitWithContextFunc +func (w waitFunc) WithContext() waitWithContextFunc { + return func(ctx context.Context) <-chan struct{} { + return w(ctx.Done()) + } +} + +// waitWithContextFunc creates a channel that receives an item every time a test +// should be executed and is closed when the last test should be invoked. +// +// When the specified context gets cancelled or expires the function +// stops sending item and returns immediately. +// +// Deprecated: Will be removed in a future release in favor of +// loopConditionUntilContext. +type waitWithContextFunc func(ctx context.Context) <-chan struct{} + +// waitForWithContext continually checks 'fn' as driven by 'wait'. +// +// waitForWithContext gets a channel from 'wait()”, and then invokes 'fn' +// once for every value placed on the channel and once more when the +// channel is closed. If the channel is closed and 'fn' +// returns false without error, waitForWithContext returns ErrWaitTimeout. +// +// If 'fn' returns an error the loop ends and that error is returned. If +// 'fn' returns true the loop ends and nil is returned. +// +// context.Canceled will be returned if the ctx.Done() channel is closed +// without fn ever returning true. +// +// When the ctx.Done() channel is closed, because the golang `select` statement is +// "uniform pseudo-random", the `fn` might still run one or multiple times, +// though eventually `waitForWithContext` will return. +// +// Deprecated: Will be removed in a future release in favor of +// loopConditionUntilContext. +func waitForWithContext(ctx context.Context, wait waitWithContextFunc, fn ConditionWithContextFunc) error { + waitCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + c := wait(waitCtx) + for { + select { + case _, open := <-c: + ok, err := runConditionWithCrashProtectionWithContext(ctx, fn) + if err != nil { + return err + } + if ok { + return nil + } + if !open { + return ErrWaitTimeout + } + case <-ctx.Done(): + // returning ctx.Err() will break backward compatibility, use new PollUntilContext* + // methods instead + return ErrWaitTimeout + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/wait_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/wait_test.go new file mode 100644 index 0000000000..39c5d29910 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/wait/wait_test.go @@ -0,0 +1,1575 @@ +/* +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 wait + +import ( + "context" + "errors" + "fmt" + "math/rand" + "sync" + "sync/atomic" + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/utils/clock" + testingclock "k8s.io/utils/clock/testing" +) + +func TestUntil(t *testing.T) { + ch := make(chan struct{}) + close(ch) + Until(func() { + t.Fatal("should not have been invoked") + }, 0, ch) + + ch = make(chan struct{}) + called := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + Until(func() { + called <- struct{}{} + }, 0, ch) + close(called) + }() + <-called + close(ch) + <-called + wg.Wait() +} + +func TestUntilWithContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.TODO()) + cancel() + UntilWithContext(ctx, func(context.Context) { + t.Fatal("should not have been invoked") + }, 0) + + ctx, cancel = context.WithCancel(context.TODO()) + called := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + UntilWithContext(ctx, func(context.Context) { + called <- struct{}{} + }, 0) + close(called) + }() + <-called + cancel() + <-called + wg.Wait() +} + +func TestNonSlidingUntil(t *testing.T) { + ch := make(chan struct{}) + close(ch) + NonSlidingUntil(func() { + t.Fatal("should not have been invoked") + }, 0, ch) + + ch = make(chan struct{}) + called := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + NonSlidingUntil(func() { + called <- struct{}{} + }, 0, ch) + close(called) + }() + <-called + close(ch) + <-called + wg.Wait() +} + +func TestNonSlidingUntilWithContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.TODO()) + cancel() + NonSlidingUntilWithContext(ctx, func(context.Context) { + t.Fatal("should not have been invoked") + }, 0) + + ctx, cancel = context.WithCancel(context.TODO()) + called := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + NonSlidingUntilWithContext(ctx, func(context.Context) { + called <- struct{}{} + }, 0) + close(called) + }() + <-called + cancel() + <-called + wg.Wait() +} + +func TestUntilReturnsImmediately(t *testing.T) { + now := time.Now() + ch := make(chan struct{}) + var attempts int + Until(func() { + attempts++ + if attempts > 1 { + t.Fatalf("invoked after close of channel") + } + close(ch) + }, 30*time.Second, ch) + if now.Add(25 * time.Second).Before(time.Now()) { + t.Errorf("Until did not return immediately when the stop chan was closed inside the func") + } +} + +func TestJitterUntil(t *testing.T) { + ch := make(chan struct{}) + // if a channel is closed JitterUntil never calls function f + // and returns immediately + close(ch) + JitterUntil(func() { + t.Fatal("should not have been invoked") + }, 0, 1.0, true, ch) + + ch = make(chan struct{}) + called := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + JitterUntil(func() { + called <- struct{}{} + }, 0, 1.0, true, ch) + close(called) + }() + <-called + close(ch) + <-called + wg.Wait() +} + +func TestJitterUntilWithContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.TODO()) + cancel() + JitterUntilWithContext(ctx, func(context.Context) { + t.Fatal("should not have been invoked") + }, 0, 1.0, true) + + ctx, cancel = context.WithCancel(context.TODO()) + called := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + JitterUntilWithContext(ctx, func(context.Context) { + called <- struct{}{} + }, 0, 1.0, true) + close(called) + }() + <-called + cancel() + <-called + wg.Wait() +} + +func TestJitterUntilReturnsImmediately(t *testing.T) { + now := time.Now() + ch := make(chan struct{}) + JitterUntil(func() { + close(ch) + }, 30*time.Second, 1.0, true, ch) + if now.Add(25 * time.Second).Before(time.Now()) { + t.Errorf("JitterUntil did not return immediately when the stop chan was closed inside the func") + } +} + +func TestJitterUntilRecoversPanic(t *testing.T) { + // Save and restore crash handlers + originalReallyCrash := runtime.ReallyCrash + originalHandlers := runtime.PanicHandlers + defer func() { + runtime.ReallyCrash = originalReallyCrash + runtime.PanicHandlers = originalHandlers + }() + + called := 0 + handled := 0 + + // Hook up a custom crash handler to ensure it is called when a jitter function panics + runtime.ReallyCrash = false + runtime.PanicHandlers = []func(context.Context, interface{}){ + func(_ context.Context, p interface{}) { + handled++ + }, + } + + ch := make(chan struct{}) + JitterUntil(func() { + called++ + if called > 2 { + close(ch) + return + } + panic("TestJitterUntilRecoversPanic") + }, time.Millisecond, 1.0, true, ch) + + if called != 3 { + t.Errorf("Expected panic recovers") + } +} + +func TestJitterUntilNegativeFactor(t *testing.T) { + now := time.Now() + ch := make(chan struct{}) + called := make(chan struct{}) + received := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + JitterUntil(func() { + called <- struct{}{} + <-received + }, time.Second, -30.0, true, ch) + }() + // first loop + <-called + received <- struct{}{} + // second loop + <-called + close(ch) + received <- struct{}{} + + // it should take at most 2 seconds + some overhead, not 3 + if now.Add(3 * time.Second).Before(time.Now()) { + t.Errorf("JitterUntil did not returned after predefined period with negative jitter factor when the stop chan was closed inside the func") + } + wg.Wait() +} + +func TestExponentialBackoff(t *testing.T) { + // exits immediately + i := 0 + err := ExponentialBackoff(Backoff{Factor: 1.0}, func() (bool, error) { + i++ + return false, nil + }) + if err != ErrWaitTimeout || i != 0 { + t.Errorf("unexpected error: %v", err) + } + + opts := Backoff{Factor: 1.0, Steps: 3} + + // waits up to steps + i = 0 + err = ExponentialBackoff(opts, func() (bool, error) { + i++ + return false, nil + }) + if err != ErrWaitTimeout || i != opts.Steps { + t.Errorf("unexpected error: %v", err) + } + + // returns immediately + i = 0 + err = ExponentialBackoff(opts, func() (bool, error) { + i++ + return true, nil + }) + if err != nil || i != 1 { + t.Errorf("unexpected error: %v", err) + } + + // returns immediately on error + testErr := fmt.Errorf("some other error") + err = ExponentialBackoff(opts, func() (bool, error) { + return false, testErr + }) + if err != testErr { + t.Errorf("unexpected error: %v", err) + } + + // invoked multiple times + i = 1 + err = ExponentialBackoff(opts, func() (bool, error) { + if i < opts.Steps { + i++ + return false, nil + } + return true, nil + }) + if err != nil || i != opts.Steps { + t.Errorf("unexpected error: %v", err) + } +} + +func TestPoller(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + w := poller(time.Millisecond, 2*time.Millisecond) + ch := w(ctx) + count := 0 +DRAIN: + for { + select { + case _, open := <-ch: + if !open { + break DRAIN + } + count++ + case <-time.After(ForeverTestTimeout): + t.Errorf("unexpected timeout after poll") + } + } + if count > 3 { + t.Errorf("expected up to three values, got %d", count) + } +} + +type fakePoller struct { + max int + used int32 // accessed with atomics + wg sync.WaitGroup +} + +func fakeTicker(max int, used *int32, doneFunc func()) waitFunc { + return func(done <-chan struct{}) <-chan struct{} { + ch := make(chan struct{}) + go func() { + defer doneFunc() + defer close(ch) + for i := 0; i < max; i++ { + select { + case ch <- struct{}{}: + case <-done: + return + } + if used != nil { + atomic.AddInt32(used, 1) + } + } + }() + return ch + } +} + +func (fp *fakePoller) GetwaitFunc() waitFunc { + fp.wg.Add(1) + return fakeTicker(fp.max, &fp.used, fp.wg.Done) +} + +func TestPoll(t *testing.T) { + invocations := 0 + f := ConditionWithContextFunc(func(ctx context.Context) (bool, error) { + invocations++ + return true, nil + }) + fp := fakePoller{max: 1} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := poll(ctx, false, fp.GetwaitFunc().WithContext(), f); err != nil { + t.Fatalf("unexpected error %v", err) + } + fp.wg.Wait() + if invocations != 1 { + t.Errorf("Expected exactly one invocation, got %d", invocations) + } + used := atomic.LoadInt32(&fp.used) + if used != 1 { + t.Errorf("Expected exactly one tick, got %d", used) + } +} + +func TestPollError(t *testing.T) { + expectedError := errors.New("Expected error") + f := ConditionFunc(func() (bool, error) { + return false, expectedError + }) + fp := fakePoller{max: 1} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := poll(ctx, false, fp.GetwaitFunc().WithContext(), f.WithContext()); err == nil || err != expectedError { + t.Fatalf("Expected error %v, got none %v", expectedError, err) + } + fp.wg.Wait() + used := atomic.LoadInt32(&fp.used) + if used != 1 { + t.Errorf("Expected exactly one tick, got %d", used) + } +} + +func TestPollImmediate(t *testing.T) { + invocations := 0 + f := ConditionFunc(func() (bool, error) { + invocations++ + return true, nil + }) + fp := fakePoller{max: 0} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := poll(ctx, true, fp.GetwaitFunc().WithContext(), f.WithContext()); err != nil { + t.Fatalf("unexpected error %v", err) + } + // We don't need to wait for fp.wg, as pollImmediate shouldn't call waitFunc at all. + if invocations != 1 { + t.Errorf("Expected exactly one invocation, got %d", invocations) + } + used := atomic.LoadInt32(&fp.used) + if used != 0 { + t.Errorf("Expected exactly zero ticks, got %d", used) + } +} + +func TestPollImmediateError(t *testing.T) { + expectedError := errors.New("Expected error") + f := ConditionFunc(func() (bool, error) { + return false, expectedError + }) + fp := fakePoller{max: 0} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := poll(ctx, true, fp.GetwaitFunc().WithContext(), f.WithContext()); err == nil || err != expectedError { + t.Fatalf("Expected error %v, got none %v", expectedError, err) + } + // We don't need to wait for fp.wg, as pollImmediate shouldn't call waitFunc at all. + used := atomic.LoadInt32(&fp.used) + if used != 0 { + t.Errorf("Expected exactly zero ticks, got %d", used) + } +} + +func TestPollForever(t *testing.T) { + ch := make(chan struct{}) + errc := make(chan error, 1) + done := make(chan struct{}, 1) + complete := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + f := ConditionFunc(func() (bool, error) { + ch <- struct{}{} + select { + case <-done: + return true, nil + default: + } + return false, nil + }) + + if err := PollInfinite(time.Microsecond, f); err != nil { + errc <- fmt.Errorf("unexpected error %v", err) + } + + close(ch) + complete <- struct{}{} + }() + + // ensure the condition is opened + <-ch + + // ensure channel sends events + for i := 0; i < 10; i++ { + select { + case _, open := <-ch: + if !open { + if len(errc) != 0 { + t.Fatalf("did not expect channel to be closed, %v", <-errc) + } + t.Fatal("did not expect channel to be closed") + } + case <-time.After(ForeverTestTimeout): + t.Fatalf("channel did not return at least once within the poll interval") + } + } + + // at most one poll notification should be sent once we return from the condition + done <- struct{}{} + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 2; i++ { + _, open := <-ch + if !open { + return + } + } + t.Error("expected closed channel after two iterations") + }() + <-complete + + if len(errc) != 0 { + t.Fatal(<-errc) + } + wg.Wait() +} + +func Test_waitFor(t *testing.T) { + var invocations int + testCases := map[string]struct { + F ConditionFunc + Ticks int + Invoked int + Err bool + }{ + "invoked once": { + ConditionFunc(func() (bool, error) { + invocations++ + return true, nil + }), + 2, + 1, + false, + }, + "invoked and returns a timeout": { + ConditionFunc(func() (bool, error) { + invocations++ + return false, nil + }), + 2, + 3, // the contract of waitFor() says the func is called once more at the end of the wait + true, + }, + "returns immediately on error": { + ConditionFunc(func() (bool, error) { + invocations++ + return false, errors.New("test") + }), + 2, + 1, + true, + }, + } + for k, c := range testCases { + invocations = 0 + ticker := fakeTicker(c.Ticks, nil, func() {}) + err := func() error { + done := make(chan struct{}) + defer close(done) + ctx := ContextForChannel(done) + return waitForWithContext(ctx, ticker.WithContext(), c.F.WithContext()) + }() + switch { + case c.Err && err == nil: + t.Errorf("%s: Expected error, got nil", k) + continue + case !c.Err && err != nil: + t.Errorf("%s: Expected no error, got: %#v", k, err) + continue + } + if invocations != c.Invoked { + t.Errorf("%s: Expected %d invocations, got %d", k, c.Invoked, invocations) + } + } +} + +// Test_waitForWithEarlyClosing_waitFunc tests WaitFor when the waitFunc closes its channel. The WaitFor should +// always return ErrWaitTimeout. +func Test_waitForWithEarlyClosing_waitFunc(t *testing.T) { + stopCh := make(chan struct{}) + defer close(stopCh) + + ctx := ContextForChannel(stopCh) + start := time.Now() + err := waitForWithContext(ctx, func(ctx context.Context) <-chan struct{} { + c := make(chan struct{}) + close(c) + return c + }, func(_ context.Context) (bool, error) { + return false, nil + }) + duration := time.Since(start) + + // The waitFor should return immediately, so the duration is close to 0s. + if duration >= ForeverTestTimeout/2 { + t.Errorf("expected short timeout duration") + } + if err != ErrWaitTimeout { + t.Errorf("expected ErrWaitTimeout from WaitFunc") + } +} + +// Test_waitForWithClosedChannel tests waitFor when it receives a closed channel. The waitFor should +// always return ErrWaitTimeout. +func Test_waitForWithClosedChannel(t *testing.T) { + stopCh := make(chan struct{}) + close(stopCh) + c := make(chan struct{}) + defer close(c) + ctx := ContextForChannel(stopCh) + + start := time.Now() + err := waitForWithContext(ctx, func(_ context.Context) <-chan struct{} { + return c + }, func(_ context.Context) (bool, error) { + return false, nil + }) + duration := time.Since(start) + // The waitFor should return immediately, so the duration is close to 0s. + if duration >= ForeverTestTimeout/2 { + t.Errorf("expected short timeout duration") + } + // The interval of the poller is ForeverTestTimeout, so the waitFor should always return ErrWaitTimeout. + if err != ErrWaitTimeout { + t.Errorf("expected ErrWaitTimeout from WaitFunc") + } +} + +// Test_waitForWithContextCancelsContext verifies that after the condition func returns true, +// waitForWithContext cancels the context it supplies to the WaitWithContextFunc. +func Test_waitForWithContextCancelsContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + waitFn := poller(time.Millisecond, ForeverTestTimeout) + + var ctxPassedToWait context.Context + waitForWithContext(ctx, func(ctx context.Context) <-chan struct{} { + ctxPassedToWait = ctx + return waitFn(ctx) + }, func(ctx context.Context) (bool, error) { + time.Sleep(10 * time.Millisecond) + return true, nil + }) + // The polling goroutine should be closed after waitForWithContext returning. + if ctxPassedToWait.Err() != context.Canceled { + t.Errorf("expected the context passed to waitForWithContext to be closed with: %v, but got: %v", context.Canceled, ctxPassedToWait.Err()) + } +} + +func TestPollUntil(t *testing.T) { + stopCh := make(chan struct{}) + called := make(chan bool) + pollDone := make(chan struct{}) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + PollUntil(time.Microsecond, ConditionFunc(func() (bool, error) { + called <- true + return false, nil + }), stopCh) + + close(pollDone) + }() + + // make sure we're called once + <-called + // this should trigger a "done" + close(stopCh) + + go func() { + // release the condition func if needed + for range called { + } + }() + + // make sure we finished the poll + <-pollDone + close(called) + wg.Wait() +} + +func TestBackoff_Step(t *testing.T) { + tests := []struct { + initial *Backoff + want []time.Duration + }{ + {initial: nil, want: []time.Duration{0, 0, 0, 0}}, + {initial: &Backoff{Duration: time.Second, Steps: -1}, want: []time.Duration{time.Second, time.Second, time.Second}}, + {initial: &Backoff{Duration: time.Second, Steps: 0}, want: []time.Duration{time.Second, time.Second, time.Second}}, + {initial: &Backoff{Duration: time.Second, Steps: 1}, want: []time.Duration{time.Second, time.Second, time.Second}}, + {initial: &Backoff{Duration: time.Second, Factor: 1.0, Steps: 1}, want: []time.Duration{time.Second, time.Second, time.Second}}, + {initial: &Backoff{Duration: time.Second, Factor: 2, Steps: 3}, want: []time.Duration{1 * time.Second, 2 * time.Second, 4 * time.Second}}, + {initial: &Backoff{Duration: time.Second, Factor: 2, Steps: 3, Cap: 3 * time.Second}, want: []time.Duration{1 * time.Second, 2 * time.Second, 3 * time.Second}}, + {initial: &Backoff{Duration: time.Second, Factor: 2, Steps: 2, Cap: 3 * time.Second, Jitter: 0.5}, want: []time.Duration{2 * time.Second, 3 * time.Second, 3 * time.Second}}, + {initial: &Backoff{Duration: time.Second, Factor: 2, Steps: 6, Jitter: 4}, want: []time.Duration{1 * time.Second, 2 * time.Second, 4 * time.Second, 8 * time.Second, 16 * time.Second, 32 * time.Second}}, + } + for seed := int64(0); seed < 5; seed++ { + for _, tt := range tests { + var initial *Backoff + if tt.initial != nil { + copied := *tt.initial + initial = &copied + } else { + initial = nil + } + t.Run(fmt.Sprintf("%#v seed=%d", initial, seed), func(t *testing.T) { + jitterRand = rand.New(rand.NewSource(seed)).Float64 + for i := 0; i < len(tt.want); i++ { + got := initial.Step() + t.Logf("[%d]=%s", i, got) + if initial != nil && initial.Jitter > 0 { + if got == tt.want[i] { + // this is statistically unlikely to happen by chance + t.Errorf("Backoff.Step(%d) = %v, no jitter", i, got) + continue + } + diff := float64(tt.want[i]-got) / float64(tt.want[i]) + if diff > initial.Jitter { + t.Errorf("Backoff.Step(%d) = %v, want %v, outside range", i, got, tt.want) + continue + } + } else { + if got != tt.want[i] { + t.Errorf("Backoff.Step(%d) = %v, want %v", i, got, tt.want) + continue + } + } + } + }) + } + } +} + +func TestContextForChannel(t *testing.T) { + var wg sync.WaitGroup + parentCh := make(chan struct{}) + done := make(chan struct{}) + + for i := 0; i < 3; i++ { + wg.Add(1) + go func() { + defer wg.Done() + ctx := ContextForChannel(parentCh) + <-ctx.Done() + }() + } + + go func() { + wg.Wait() + close(done) + }() + + // Closing parent channel should cancel all children contexts + close(parentCh) + + select { + case <-done: + case <-time.After(ForeverTestTimeout): + t.Errorf("unexpected timeout waiting for parent to cancel child contexts") + } +} + +func TestExponentialBackoffManagerGetNextBackoff(t *testing.T) { + fc := testingclock.NewFakeClock(time.Now()) + backoff := NewExponentialBackoffManager(1, 10, 10, 2.0, 0.0, fc) + durations := []time.Duration{1, 2, 4, 8, 10, 10, 10} + for i := 0; i < len(durations); i++ { + generatedBackoff := backoff.(*exponentialBackoffManagerImpl).getNextBackoff() + if generatedBackoff != durations[i] { + t.Errorf("unexpected %d-th backoff: %d, expecting %d", i, generatedBackoff, durations[i]) + } + } + + fc.Step(11) + resetDuration := backoff.(*exponentialBackoffManagerImpl).getNextBackoff() + if resetDuration != 1 { + t.Errorf("after reset, backoff should be 1, but got %d", resetDuration) + } +} + +func TestJitteredBackoffManagerGetNextBackoff(t *testing.T) { + // positive jitter + backoffMgr := NewJitteredBackoffManager(1, 1, testingclock.NewFakeClock(time.Now())) + for i := 0; i < 5; i++ { + backoff := backoffMgr.(*jitteredBackoffManagerImpl).getNextBackoff() + if backoff < 1 || backoff > 2 { + t.Errorf("backoff out of range: %d", backoff) + } + } + + // negative jitter, shall be a fixed backoff + backoffMgr = NewJitteredBackoffManager(1, -1, testingclock.NewFakeClock(time.Now())) + backoff := backoffMgr.(*jitteredBackoffManagerImpl).getNextBackoff() + if backoff != 1 { + t.Errorf("backoff should be 1, but got %d", backoff) + } +} + +func TestJitterBackoffManagerWithRealClock(t *testing.T) { + backoffMgr := NewJitteredBackoffManager(1*time.Millisecond, 0, &clock.RealClock{}) + for i := 0; i < 5; i++ { + start := time.Now() + <-backoffMgr.Backoff().C() + passed := time.Since(start) + if passed < 1*time.Millisecond { + t.Errorf("backoff should be at least 1ms, but got %s", passed.String()) + } + } +} + +func TestExponentialBackoffManagerWithRealClock(t *testing.T) { + // backoff at least 1ms, 2ms, 4ms, 8ms, 10ms, 10ms, 10ms + durationFactors := []time.Duration{1, 2, 4, 8, 10, 10, 10} + backoffMgr := NewExponentialBackoffManager(1*time.Millisecond, 10*time.Millisecond, 1*time.Hour, 2.0, 0.0, &clock.RealClock{}) + + for i := range durationFactors { + start := time.Now() + <-backoffMgr.Backoff().C() + passed := time.Since(start) + if passed < durationFactors[i]*time.Millisecond { + t.Errorf("backoff should be at least %d ms, but got %s", durationFactors[i], passed.String()) + } + } +} + +func TestBackoffDelayWithResetExponential(t *testing.T) { + fc := testingclock.NewFakeClock(time.Now()) + backoff := Backoff{Duration: 1, Cap: 10, Factor: 2.0, Jitter: 0.0, Steps: 10}.DelayWithReset(fc, 10) + durations := []time.Duration{1, 2, 4, 8, 10, 10, 10} + for i := 0; i < len(durations); i++ { + generatedBackoff := backoff() + if generatedBackoff != durations[i] { + t.Errorf("unexpected %d-th backoff: %d, expecting %d", i, generatedBackoff, durations[i]) + } + } + + fc.Step(11) + resetDuration := backoff() + if resetDuration != 1 { + t.Errorf("after reset, backoff should be 1, but got %d", resetDuration) + } +} + +func TestBackoffDelayWithResetEmpty(t *testing.T) { + fc := testingclock.NewFakeClock(time.Now()) + backoff := Backoff{Duration: 1, Cap: 10, Factor: 2.0, Jitter: 0.0, Steps: 10}.DelayWithReset(fc, 0) + // we reset to initial duration because the resetInterval is 0, immediate + durations := []time.Duration{1, 1, 1, 1, 1, 1, 1} + for i := 0; i < len(durations); i++ { + generatedBackoff := backoff() + if generatedBackoff != durations[i] { + t.Errorf("unexpected %d-th backoff: %d, expecting %d", i, generatedBackoff, durations[i]) + } + } + + fc.Step(11) + resetDuration := backoff() + if resetDuration != 1 { + t.Errorf("after reset, backoff should be 1, but got %d", resetDuration) + } +} + +func TestBackoffDelayWithResetJitter(t *testing.T) { + // positive jitter + backoff := Backoff{Duration: 1, Jitter: 1}.DelayWithReset(testingclock.NewFakeClock(time.Now()), 0) + for i := 0; i < 5; i++ { + value := backoff() + if value < 1 || value > 2 { + t.Errorf("backoff out of range: %d", value) + } + } + + // negative jitter, shall be a fixed backoff + backoff = Backoff{Duration: 1, Jitter: -1}.DelayWithReset(testingclock.NewFakeClock(time.Now()), 0) + value := backoff() + if value != 1 { + t.Errorf("backoff should be 1, but got %d", value) + } +} + +func TestBackoffDelayWithResetWithRealClockJitter(t *testing.T) { + backoff := Backoff{Duration: 1 * time.Millisecond, Jitter: 0}.DelayWithReset(&clock.RealClock{}, 0) + for i := 0; i < 5; i++ { + start := time.Now() + <-RealTimer(backoff()).C() + passed := time.Since(start) + if passed < 1*time.Millisecond { + t.Errorf("backoff should be at least 1ms, but got %s", passed.String()) + } + } +} + +func TestBackoffDelayWithResetWithRealClockExponential(t *testing.T) { + // backoff at least 1ms, 2ms, 4ms, 8ms, 10ms, 10ms, 10ms + durationFactors := []time.Duration{1, 2, 4, 8, 10, 10, 10} + backoff := Backoff{Duration: 1 * time.Millisecond, Cap: 10 * time.Millisecond, Factor: 2.0, Jitter: 0.0, Steps: 10}.DelayWithReset(&clock.RealClock{}, 1*time.Hour) + + for i := range durationFactors { + start := time.Now() + <-RealTimer(backoff()).C() + passed := time.Since(start) + if passed < durationFactors[i]*time.Millisecond { + t.Errorf("backoff should be at least %d ms, but got %s", durationFactors[i], passed.String()) + } + } +} + +func defaultContext() (context.Context, context.CancelFunc) { + return context.WithCancel(context.Background()) +} +func cancelledContext() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + return ctx, cancel +} +func deadlinedContext() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + for ctx.Err() != context.DeadlineExceeded { + time.Sleep(501 * time.Microsecond) + } + return ctx, cancel +} + +func TestExponentialBackoffWithContext(t *testing.T) { + defaultCallback := func(_ int) (bool, error) { + return false, nil + } + + conditionErr := errors.New("condition failed") + + tests := []struct { + name string + steps int + zeroDuration bool + context func() (context.Context, context.CancelFunc) + callback func(calls int) (bool, error) + cancelContextAfter int + attemptsExpected int + errExpected error + }{ + { + name: "no attempts expected with zero backoff steps", + steps: 0, + callback: defaultCallback, + attemptsExpected: 0, + errExpected: ErrWaitTimeout, + }, + { + name: "condition returns false with single backoff step", + steps: 1, + callback: defaultCallback, + attemptsExpected: 1, + errExpected: ErrWaitTimeout, + }, + { + name: "condition returns true with single backoff step", + steps: 1, + callback: func(_ int) (bool, error) { + return true, nil + }, + attemptsExpected: 1, + errExpected: nil, + }, + { + name: "condition always returns false with multiple backoff steps", + steps: 5, + callback: defaultCallback, + attemptsExpected: 5, + errExpected: ErrWaitTimeout, + }, + { + name: "condition returns true after certain attempts with multiple backoff steps", + steps: 5, + callback: func(attempts int) (bool, error) { + if attempts == 3 { + return true, nil + } + return false, nil + }, + attemptsExpected: 3, + errExpected: nil, + }, + { + name: "condition returns error no further attempts expected", + steps: 5, + callback: func(_ int) (bool, error) { + return true, conditionErr + }, + attemptsExpected: 1, + errExpected: conditionErr, + }, + { + name: "context already canceled no attempts expected", + steps: 5, + context: cancelledContext, + callback: defaultCallback, + attemptsExpected: 0, + errExpected: context.Canceled, + }, + { + name: "context at deadline no attempts expected", + steps: 5, + context: deadlinedContext, + callback: defaultCallback, + attemptsExpected: 0, + errExpected: context.DeadlineExceeded, + }, + { + name: "no attempts expected with zero backoff steps", + steps: 0, + callback: defaultCallback, + attemptsExpected: 0, + errExpected: ErrWaitTimeout, + }, + { + name: "condition returns false with single backoff step", + steps: 1, + callback: defaultCallback, + attemptsExpected: 1, + errExpected: ErrWaitTimeout, + }, + { + name: "condition returns true with single backoff step", + steps: 1, + callback: func(_ int) (bool, error) { + return true, nil + }, + attemptsExpected: 1, + errExpected: nil, + }, + { + name: "condition always returns false with multiple backoff steps but is cancelled at step 4", + steps: 5, + callback: defaultCallback, + attemptsExpected: 4, + cancelContextAfter: 4, + errExpected: context.Canceled, + }, + { + name: "condition returns true after certain attempts with multiple backoff steps and zero duration", + steps: 5, + zeroDuration: true, + callback: func(attempts int) (bool, error) { + if attempts == 3 { + return true, nil + } + return false, nil + }, + attemptsExpected: 3, + errExpected: nil, + }, + { + name: "condition returns error no further attempts expected", + steps: 5, + callback: func(_ int) (bool, error) { + return true, conditionErr + }, + attemptsExpected: 1, + errExpected: conditionErr, + }, + { + name: "context already canceled no attempts expected", + steps: 5, + context: cancelledContext, + callback: defaultCallback, + attemptsExpected: 0, + errExpected: context.Canceled, + }, + { + name: "context at deadline no attempts expected", + steps: 5, + context: deadlinedContext, + callback: defaultCallback, + attemptsExpected: 0, + errExpected: context.DeadlineExceeded, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + backoff := Backoff{ + Duration: 1 * time.Microsecond, + Factor: 1.0, + Steps: test.steps, + } + if test.zeroDuration { + backoff.Duration = 0 + } + + contextFn := test.context + if contextFn == nil { + contextFn = defaultContext + } + ctx, cancel := contextFn() + defer cancel() + + attempts := 0 + err := ExponentialBackoffWithContext(ctx, backoff, func(_ context.Context) (bool, error) { + attempts++ + defer func() { + if test.cancelContextAfter > 0 && test.cancelContextAfter == attempts { + cancel() + } + }() + return test.callback(attempts) + }) + + if test.errExpected != err { + t.Errorf("expected error: %v but got: %v", test.errExpected, err) + } + + if test.attemptsExpected != attempts { + t.Errorf("expected attempts count: %d but got: %d", test.attemptsExpected, attempts) + } + }) + } +} + +func BenchmarkExponentialBackoffWithContext(b *testing.B) { + backoff := Backoff{ + Duration: 0, + Factor: 0, + Steps: 101, + } + ctx := context.Background() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + attempts := 0 + if err := ExponentialBackoffWithContext(ctx, backoff, func(_ context.Context) (bool, error) { + attempts++ + return attempts >= 100, nil + }); err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} + +func TestPollImmediateUntilWithContext(t *testing.T) { + fakeErr := errors.New("my error") + tests := []struct { + name string + condition func(int) ConditionWithContextFunc + context func() (context.Context, context.CancelFunc) + cancelContextAfterNthAttempt int + errExpected error + attemptsExpected int + }{ + { + name: "condition throws error on immediate attempt, no retry is attempted", + condition: func(int) ConditionWithContextFunc { + return func(context.Context) (done bool, err error) { + return false, fakeErr + } + }, + errExpected: fakeErr, + attemptsExpected: 1, + }, + { + name: "condition returns done=true on immediate attempt, no retry is attempted", + condition: func(int) ConditionWithContextFunc { + return func(context.Context) (done bool, err error) { + return true, nil + } + }, + errExpected: nil, + attemptsExpected: 1, + }, + { + name: "condition returns done=false on immediate attempt, context is already cancelled, no retry is attempted", + condition: func(int) ConditionWithContextFunc { + return func(context.Context) (done bool, err error) { + return false, nil + } + }, + context: cancelledContext, + errExpected: ErrWaitTimeout, // this should be context.Canceled but that would break callers that assume all errors are ErrWaitTimeout + attemptsExpected: 1, + }, + { + name: "condition returns done=false on immediate attempt, context is not cancelled, retry is attempted", + condition: func(attempts int) ConditionWithContextFunc { + return func(context.Context) (done bool, err error) { + // let first 3 attempts fail and the last one succeed + if attempts <= 3 { + return false, nil + } + return true, nil + } + }, + errExpected: nil, + attemptsExpected: 4, + }, + { + name: "condition always returns done=false, context gets cancelled after N attempts", + condition: func(attempts int) ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { + return false, nil + } + }, + cancelContextAfterNthAttempt: 4, + errExpected: ErrWaitTimeout, // this should be context.Canceled, but this method cannot change + attemptsExpected: 4, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + contextFn := test.context + if contextFn == nil { + contextFn = defaultContext + } + ctx, cancel := contextFn() + defer cancel() + + var attempts int + conditionWrapper := func(ctx context.Context) (done bool, err error) { + attempts++ + defer func() { + if test.cancelContextAfterNthAttempt == attempts { + cancel() + } + }() + + c := test.condition(attempts) + return c(ctx) + } + + err := PollImmediateUntilWithContext(ctx, time.Millisecond, conditionWrapper) + if test.errExpected != err { + t.Errorf("Expected error: %v, but got: %v", test.errExpected, err) + } + if test.attemptsExpected != attempts { + t.Errorf("Expected ConditionFunc to be invoked: %d times, but got: %d", test.attemptsExpected, attempts) + } + }) + } +} + +func Test_waitForWithContext(t *testing.T) { + fakeErr := errors.New("fake error") + tests := []struct { + name string + context func() (context.Context, context.CancelFunc) + condition ConditionWithContextFunc + waitFunc func() waitFunc + attemptsExpected int + errExpected error + }{ + { + name: "condition returns done=true on first attempt, no retry is attempted", + context: defaultContext, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return true, nil + }), + waitFunc: func() waitFunc { return fakeTicker(2, nil, func() {}) }, + attemptsExpected: 1, + errExpected: nil, + }, + { + name: "condition always returns done=false, timeout error expected", + context: defaultContext, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, nil + }), + waitFunc: func() waitFunc { return fakeTicker(2, nil, func() {}) }, + // the contract of waitForWithContext() says the func is called once more at the end of the wait + attemptsExpected: 3, + errExpected: ErrWaitTimeout, + }, + { + name: "condition returns an error on first attempt, the error is returned", + context: defaultContext, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, fakeErr + }), + waitFunc: func() waitFunc { return fakeTicker(2, nil, func() {}) }, + attemptsExpected: 1, + errExpected: fakeErr, + }, + { + name: "context is cancelled, context cancelled error expected", + context: cancelledContext, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, nil + }), + waitFunc: func() waitFunc { + return func(done <-chan struct{}) <-chan struct{} { + ch := make(chan struct{}) + // never tick on this channel + return ch + } + }, + attemptsExpected: 0, + errExpected: ErrWaitTimeout, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var attempts int + conditionWrapper := func(ctx context.Context) (done bool, err error) { + attempts++ + return test.condition(ctx) + } + + ticker := test.waitFunc() + err := func() error { + contextFn := test.context + if contextFn == nil { + contextFn = defaultContext + } + ctx, cancel := contextFn() + defer cancel() + + return waitForWithContext(ctx, ticker.WithContext(), conditionWrapper) + }() + + if test.errExpected != err { + t.Errorf("Expected error: %v, but got: %v", test.errExpected, err) + } + if test.attemptsExpected != attempts { + t.Errorf("Expected %d invocations, got %d", test.attemptsExpected, attempts) + } + }) + } +} + +func Test_poll(t *testing.T) { + fakeErr := errors.New("fake error") + tests := []struct { + name string + context func() (context.Context, context.CancelFunc) + immediate bool + waitFunc func() waitFunc + condition ConditionWithContextFunc + cancelContextAfter int + attemptsExpected int + errExpected error + }{ + { + name: "immediate is true, condition returns an error", + immediate: true, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, fakeErr + }), + waitFunc: nil, + attemptsExpected: 1, + errExpected: fakeErr, + }, + { + name: "immediate is true, condition returns true", + immediate: true, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return true, nil + }), + waitFunc: nil, + attemptsExpected: 1, + errExpected: nil, + }, + { + name: "immediate is true, context is cancelled, condition return false", + immediate: true, + context: cancelledContext, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, nil + }), + waitFunc: nil, + attemptsExpected: 1, + errExpected: ErrWaitTimeout, + }, + { + name: "immediate is false, context is cancelled", + immediate: false, + context: cancelledContext, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, nil + }), + waitFunc: nil, + attemptsExpected: 0, + errExpected: ErrWaitTimeout, + }, + { + name: "immediate is false, condition returns an error", + immediate: false, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, fakeErr + }), + waitFunc: func() waitFunc { return fakeTicker(5, nil, func() {}) }, + attemptsExpected: 1, + errExpected: fakeErr, + }, + { + name: "immediate is false, condition returns true", + immediate: false, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return true, nil + }), + waitFunc: func() waitFunc { return fakeTicker(5, nil, func() {}) }, + attemptsExpected: 1, + errExpected: nil, + }, + { + name: "immediate is false, ticker channel is closed, condition returns true", + immediate: false, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return true, nil + }), + waitFunc: func() waitFunc { + return func(done <-chan struct{}) <-chan struct{} { + ch := make(chan struct{}) + close(ch) + return ch + } + }, + attemptsExpected: 1, + errExpected: nil, + }, + { + name: "immediate is false, ticker channel is closed, condition returns error", + immediate: false, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, fakeErr + }), + waitFunc: func() waitFunc { + return func(done <-chan struct{}) <-chan struct{} { + ch := make(chan struct{}) + close(ch) + return ch + } + }, + attemptsExpected: 1, + errExpected: fakeErr, + }, + { + name: "immediate is false, ticker channel is closed, condition returns false", + immediate: false, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, nil + }), + waitFunc: func() waitFunc { + return func(done <-chan struct{}) <-chan struct{} { + ch := make(chan struct{}) + close(ch) + return ch + } + }, + attemptsExpected: 1, + errExpected: ErrWaitTimeout, + }, + { + name: "condition always returns false, timeout error expected", + immediate: false, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, nil + }), + waitFunc: func() waitFunc { return fakeTicker(2, nil, func() {}) }, + // the contract of waitForWithContext() says the func is called once more at the end of the wait + attemptsExpected: 3, + errExpected: ErrWaitTimeout, + }, + { + name: "context is cancelled after N attempts, timeout error expected", + immediate: false, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, nil + }), + waitFunc: func() waitFunc { + return func(done <-chan struct{}) <-chan struct{} { + ch := make(chan struct{}) + // just tick twice + go func() { + ch <- struct{}{} + ch <- struct{}{} + }() + return ch + } + }, + cancelContextAfter: 2, + attemptsExpected: 2, + errExpected: ErrWaitTimeout, + }, + { + name: "context is cancelled after N attempts, context error not expected (legacy behavior)", + immediate: false, + condition: ConditionWithContextFunc(func(context.Context) (bool, error) { + return false, nil + }), + waitFunc: func() waitFunc { + return func(done <-chan struct{}) <-chan struct{} { + ch := make(chan struct{}) + // just tick twice + go func() { + ch <- struct{}{} + ch <- struct{}{} + }() + return ch + } + }, + cancelContextAfter: 2, + attemptsExpected: 2, + errExpected: ErrWaitTimeout, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var attempts int + ticker := waitFunc(func(done <-chan struct{}) <-chan struct{} { + return nil + }) + if test.waitFunc != nil { + ticker = test.waitFunc() + } + err := func() error { + contextFn := test.context + if contextFn == nil { + contextFn = defaultContext + } + ctx, cancel := contextFn() + defer cancel() + + conditionWrapper := func(ctx context.Context) (done bool, err error) { + attempts++ + + defer func() { + if test.cancelContextAfter == attempts { + cancel() + } + }() + + return test.condition(ctx) + } + + return poll(ctx, test.immediate, ticker.WithContext(), conditionWrapper) + }() + + if test.errExpected != err { + t.Errorf("Expected error: %v, but got: %v", test.errExpected, err) + } + if test.attemptsExpected != attempts { + t.Errorf("Expected %d invocations, got %d", test.attemptsExpected, attempts) + } + }) + } +} + +func Benchmark_poll(b *testing.B) { + ctx := context.Background() + b.ResetTimer() + for i := 0; i < b.N; i++ { + attempts := 0 + if err := poll(ctx, true, poller(time.Microsecond, 0), func(_ context.Context) (bool, error) { + attempts++ + return attempts >= 100, nil + }); err != nil { + b.Fatalf("unexpected err: %v", err) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/doc.go new file mode 100644 index 0000000000..6eb7903a73 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2017 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 waitgroup implements SafeWaitGroup wrap of sync.WaitGroup. +// Add with positive delta when waiting will fail, to prevent sync.WaitGroup race issue. +package waitgroup diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup.go new file mode 100644 index 0000000000..8766390fc2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup.go @@ -0,0 +1,134 @@ +/* +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 waitgroup + +import ( + "context" + "fmt" + "sync" +) + +// RateLimiter abstracts the rate limiter used by RateLimitedSafeWaitGroup. +// The implementation must be thread-safe. +type RateLimiter interface { + Wait(ctx context.Context) error +} + +// RateLimiterFactoryFunc is used by the RateLimitedSafeWaitGroup to create a new +// instance of a RateLimiter that will be used to rate limit the return rate +// of the active number of request(s). 'count' is the number of requests in +// flight that are expected to invoke 'Done' on this wait group. +type RateLimiterFactoryFunc func(count int) (RateLimiter, context.Context, context.CancelFunc) + +// RateLimitedSafeWaitGroup must not be copied after first use. +type RateLimitedSafeWaitGroup struct { + wg sync.WaitGroup + // Once Wait is initiated, all consecutive Done invocation will be + // rate limited using this rate limiter. + limiter RateLimiter + stopCtx context.Context + + mu sync.Mutex + // wait indicate whether Wait is called, if true, + // then any Add with positive delta will return error. + wait bool + // number of request(s) currently using the wait group + count int +} + +// Add adds delta, which may be negative, similar to sync.WaitGroup. +// If Add with a positive delta happens after Wait, it will return error, +// which prevent unsafe Add. +func (wg *RateLimitedSafeWaitGroup) Add(delta int) error { + wg.mu.Lock() + defer wg.mu.Unlock() + + if wg.wait && delta > 0 { + return fmt.Errorf("add with positive delta after Wait is forbidden") + } + wg.wg.Add(delta) + wg.count += delta + return nil +} + +// Done decrements the WaitGroup counter, rate limiting is applied only +// when the wait group is in waiting mode. +func (wg *RateLimitedSafeWaitGroup) Done() { + var limiter RateLimiter + func() { + wg.mu.Lock() + defer wg.mu.Unlock() + + wg.count -= 1 + if wg.wait { + // we are using the limiter outside the scope of the lock + limiter = wg.limiter + } + }() + + defer wg.wg.Done() + if limiter != nil { + limiter.Wait(wg.stopCtx) + } +} + +// Wait blocks until the WaitGroup counter is zero or a hard limit has elapsed. +// It returns the number of active request(s) accounted for at the time Wait +// has been invoked, number of request(s) that have drianed (done using the +// wait group immediately before Wait returns). +// Ideally, the both numbers returned should be equal, to indicate that all +// request(s) using the wait group have released their lock. +func (wg *RateLimitedSafeWaitGroup) Wait(limiterFactory RateLimiterFactoryFunc) (int, int, error) { + if limiterFactory == nil { + return 0, 0, fmt.Errorf("rate limiter factory must be specified") + } + + var cancel context.CancelFunc + var countNow, countAfter int + func() { + wg.mu.Lock() + defer wg.mu.Unlock() + + wg.limiter, wg.stopCtx, cancel = limiterFactory(wg.count) + countNow = wg.count + wg.wait = true + }() + + defer cancel() + // there should be a hard stop, in case request(s) are not responsive + // enough to invoke Done before the grace period is over. + waitDoneCh := make(chan struct{}) + go func() { + defer close(waitDoneCh) + wg.wg.Wait() + }() + + var err error + select { + case <-wg.stopCtx.Done(): + err = wg.stopCtx.Err() + case <-waitDoneCh: + } + + func() { + wg.mu.Lock() + defer wg.mu.Unlock() + + countAfter = wg.count + }() + return countNow, countAfter, err +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup_test.go new file mode 100644 index 0000000000..dcaefe44a4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup_test.go @@ -0,0 +1,320 @@ +/* +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 waitgroup + +import ( + "context" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/time/rate" + "k8s.io/apimachinery/pkg/util/wait" +) + +func TestRateLimitedSafeWaitGroup(t *testing.T) { + // we want to keep track of how many times rate limiter Wait method is + // being invoked, both before and after the wait group is in waiting mode. + limiter := &limiterWrapper{} + + // we expect the context passed by the factory to be used + var cancelInvoked int + factory := &factory{ + limiter: limiter, + grace: 2 * time.Second, + ctx: context.Background(), + cancel: func() { + cancelInvoked++ + }, + } + target := &rateLimitedSafeWaitGroupWrapper{ + RateLimitedSafeWaitGroup: &RateLimitedSafeWaitGroup{limiter: limiter}, + } + + // two set of requests + // - n1: this set will finish using this waitgroup before Wait is invoked + // - n2: this set will be in flight after Wait is invoked + n1, n2 := 100, 101 + + // so we know when all requests in n1 are done using the waitgroup + n1DoneWG := sync.WaitGroup{} + + // so we know when all requests in n2 have called Add, + // but not finished with the waitgroup yet. + // this will allow the test to invoke 'Wait' once all requests + // in n2 have called `Add`, but none has called `Done` yet. + n2BeforeWaitWG := sync.WaitGroup{} + // so we know when all requests in n2 have called Done and + // are finished using the waitgroup + n2DoneWG := sync.WaitGroup{} + + startCh, blockedCh := make(chan struct{}), make(chan struct{}) + n1DoneWG.Add(n1) + for i := 0; i < n1; i++ { + go func() { + defer n1DoneWG.Done() + <-startCh + + target.Add(1) + // let's finish using the waitgroup immediately + target.Done() + }() + } + + n2BeforeWaitWG.Add(n2) + n2DoneWG.Add(n2) + for i := 0; i < n2; i++ { + go func() { + func() { + defer n2BeforeWaitWG.Done() + <-startCh + + target.Add(1) + }() + + func() { + defer n2DoneWG.Done() + // let's wait for the test to instruct the requests in n2 + // that it is time to finish using the waitgroup. + <-blockedCh + + target.Done() + }() + }() + } + + // initially the count should be zero + if count := target.Count(); count != 0 { + t.Errorf("expected count to be zero, but got: %d", count) + } + // start the test + close(startCh) + // wait for the first set of requests (n1) to be done + n1DoneWG.Wait() + + // after the first set of requests (n1) are done, the count should be zero + if invoked := limiter.invoked(); invoked != 0 { + t.Errorf("expected no call to rate limiter before Wait is called, but got: %d", invoked) + } + + // make sure all requetss in the second group (n2) have started using the + // waitgroup (Add invoked) but no request is done using the waitgroup yet. + n2BeforeWaitWG.Wait() + + // count should be n2, since every request in n2 is still using the waitgroup + if count := target.Count(); count != n2 { + t.Errorf("expected count to be: %d, but got: %d", n2, count) + } + + // time for us to mark the waitgroup as `Waiting` + waitDoneCh := make(chan waitResult) + go func() { + factory.grace = 2 * time.Second + before, after, err := target.Wait(factory.NewRateLimiter) + waitDoneCh <- waitResult{before: before, after: after, err: err} + }() + + // make sure there is no flake in the test due to this race condition + var waitingGot bool + wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout, func() (done bool, err error) { + if waiting := target.Waiting(); waiting { + waitingGot = true + return true, nil + } + return false, nil + }) + // verify that the waitgroup is in 'Waiting' mode + if !waitingGot { + t.Errorf("expected to be in waiting") + } + + // we should not allow any new request to use this waitgroup any longer + if err := target.Add(1); err == nil || + !strings.Contains(err.Error(), "add with positive delta after Wait is forbidden") { + t.Errorf("expected Add to return error while in waiting mode: %v", err) + } + + // make sure that RateLimitedSafeWaitGroup passes the right + // request count to the limiter factory. + if factory.countGot != n2 { + t.Errorf("expected count passed to factory to be: %d, but got: %d", n2, factory.countGot) + } + + // indicate to all requests (each request in n2) that are + // currently using this waitgroup that they can go ahead + // and invoke 'Done' to finish using this waitgroup. + close(blockedCh) + n2DoneWG.Wait() + + if invoked := limiter.invoked(); invoked != n2 { + t.Errorf("expected rate limiter to be called %d times, but got: %d", n2, invoked) + } + + waitResult := <-waitDoneCh + if count := target.Count(); count != 0 { + t.Errorf("expected count to be zero, but got: %d", count) + } + if waitResult.before != n2 { + t.Errorf("expected count before Wait to be: %d, but got: %d", n2, waitResult.before) + } + if waitResult.after != 0 { + t.Errorf("expected count after Wait to be zero, but got: %d", waitResult.after) + } + if cancelInvoked != 1 { + t.Errorf("expected context cancel to be invoked once, but got: %d", cancelInvoked) + } +} + +func TestRateLimitedSafeWaitGroupWithHardTimeout(t *testing.T) { + target := &rateLimitedSafeWaitGroupWrapper{ + RateLimitedSafeWaitGroup: &RateLimitedSafeWaitGroup{}, + } + n := 10 + wg := sync.WaitGroup{} + wg.Add(n) + for i := 0; i < n; i++ { + go func() { + defer wg.Done() + target.Add(1) + }() + } + + wg.Wait() + if count := target.Count(); count != n { + t.Errorf("expected count to be: %d, but got: %d", n, count) + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + activeAt, activeNow, err := target.Wait(func(count int) (RateLimiter, context.Context, context.CancelFunc) { + return nil, ctx, cancel + }) + if activeAt != n { + t.Errorf("expected active at Wait to be: %d, but got: %d", n, activeAt) + } + if activeNow != n { + t.Errorf("expected active after Wait to be: %d, but got: %d", n, activeNow) + } + if err != context.Canceled { + t.Errorf("expected error: %v, but got: %v", context.Canceled, err) + } +} + +func TestRateLimitedSafeWaitGroupWithBurstOfOne(t *testing.T) { + target := &rateLimitedSafeWaitGroupWrapper{ + RateLimitedSafeWaitGroup: &RateLimitedSafeWaitGroup{}, + } + n := 200 + grace := 5 * time.Second + wg := sync.WaitGroup{} + wg.Add(n) + for i := 0; i < n; i++ { + go func() { + defer wg.Done() + target.Add(1) + }() + } + wg.Wait() + + waitingCh := make(chan struct{}) + wg.Add(n) + for i := 0; i < n; i++ { + go func() { + defer wg.Done() + + <-waitingCh + target.Done() + }() + } + defer wg.Wait() + + now := time.Now() + t.Logf("Wait starting, N=%d, grace: %s, at: %s", n, grace, now) + activeAt, activeNow, err := target.Wait(func(count int) (RateLimiter, context.Context, context.CancelFunc) { + defer close(waitingCh) + // no deadline in context, Wait will wait forever, we want to measure + // how long it takes for the requests to drain. + return rate.NewLimiter(rate.Limit(n/int(grace.Seconds())), 1), context.Background(), func() {} + }) + took := time.Since(now) + t.Logf("Wait finished, count(before): %d, count(after): %d, took: %s, err: %v", activeAt, activeNow, took, err) + + // in CPU starved environment, the go routines may not finish in time + if took > 2*grace { + t.Errorf("expected Wait to take: %s, but it took: %s", grace, took) + } +} + +type waitResult struct { + before, after int + err error +} + +type rateLimitedSafeWaitGroupWrapper struct { + *RateLimitedSafeWaitGroup +} + +// used by test only +func (wg *rateLimitedSafeWaitGroupWrapper) Count() int { + wg.mu.Lock() + defer wg.mu.Unlock() + + return wg.count +} +func (wg *rateLimitedSafeWaitGroupWrapper) Waiting() bool { + wg.mu.Lock() + defer wg.mu.Unlock() + + return wg.wait +} + +type limiterWrapper struct { + delegate RateLimiter + lock sync.Mutex + invokedN int +} + +func (w *limiterWrapper) invoked() int { + w.lock.Lock() + defer w.lock.Unlock() + return w.invokedN +} +func (w *limiterWrapper) Wait(ctx context.Context) error { + w.lock.Lock() + w.invokedN++ + w.lock.Unlock() + + if w.delegate != nil { + w.delegate.Wait(ctx) + } + return nil +} + +type factory struct { + limiter *limiterWrapper + grace time.Duration + ctx context.Context + cancel context.CancelFunc + countGot int +} + +func (f *factory) NewRateLimiter(count int) (RateLimiter, context.Context, context.CancelFunc) { + f.countGot = count + f.limiter.delegate = rate.NewLimiter(rate.Limit(count/int(f.grace.Seconds())), 20) + return f.limiter, f.ctx, f.cancel +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go new file mode 100644 index 0000000000..e080a5e92f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go @@ -0,0 +1,57 @@ +/* +Copyright 2017 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 waitgroup + +import ( + "fmt" + "sync" +) + +// SafeWaitGroup must not be copied after first use. +type SafeWaitGroup struct { + wg sync.WaitGroup + mu sync.RWMutex + // wait indicate whether Wait is called, if true, + // then any Add with positive delta will return error. + wait bool +} + +// Add adds delta, which may be negative, similar to sync.WaitGroup. +// If Add with a positive delta happens after Wait, it will return error, +// which prevent unsafe Add. +func (wg *SafeWaitGroup) Add(delta int) error { + wg.mu.RLock() + defer wg.mu.RUnlock() + if wg.wait && delta > 0 { + return fmt.Errorf("add with positive delta after Wait is forbidden") + } + wg.wg.Add(delta) + return nil +} + +// Done decrements the WaitGroup counter. +func (wg *SafeWaitGroup) Done() { + wg.wg.Done() +} + +// Wait blocks until the WaitGroup counter is zero. +func (wg *SafeWaitGroup) Wait() { + wg.mu.Lock() + wg.wait = true + wg.mu.Unlock() + wg.wg.Wait() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup_test.go new file mode 100644 index 0000000000..b5b7557b85 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2017 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 waitgroup test cases reference golang sync.WaitGroup https://golang.org/src/sync/waitgroup_test.go. +package waitgroup + +import ( + "testing" +) + +func TestWaitGroup(t *testing.T) { + wg1 := &SafeWaitGroup{} + wg2 := &SafeWaitGroup{} + n := 16 + wg1.Add(n) + wg2.Add(n) + exited := make(chan bool, n) + for i := 0; i != n; i++ { + go func(i int) { + wg1.Done() + wg2.Wait() + exited <- true + }(i) + } + wg1.Wait() + for i := 0; i != n; i++ { + select { + case <-exited: + t.Fatal("SafeWaitGroup released group too soon") + default: + } + wg2.Done() + } + for i := 0; i != n; i++ { + <-exited // Will block if barrier fails to unlock someone. + } +} + +func TestWaitGroupAddFail(t *testing.T) { + wg := &SafeWaitGroup{} + wg.Add(1) + wg.Done() + wg.Wait() + if err := wg.Add(1); err == nil { + t.Errorf("Should return error when add positive after Wait") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/decoder.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/decoder.go new file mode 100644 index 0000000000..66bf31eea1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/decoder.go @@ -0,0 +1,485 @@ +/* +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 yaml + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "strings" + "unicode" + "unicode/utf8" + + jsonutil "k8s.io/apimachinery/pkg/util/json" + + "sigs.k8s.io/yaml" +) + +// Unmarshal unmarshals the given data +// If v is a *map[string]interface{}, *[]interface{}, or *interface{} numbers +// are converted to int64 or float64 +func Unmarshal(data []byte, v interface{}) error { + preserveIntFloat := func(d *json.Decoder) *json.Decoder { + d.UseNumber() + return d + } + switch v := v.(type) { + case *map[string]interface{}: + if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil { + return err + } + return jsonutil.ConvertMapNumbers(*v, 0) + case *[]interface{}: + if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil { + return err + } + return jsonutil.ConvertSliceNumbers(*v, 0) + case *interface{}: + if err := yaml.Unmarshal(data, v, preserveIntFloat); err != nil { + return err + } + return jsonutil.ConvertInterfaceNumbers(v, 0) + default: + return yaml.Unmarshal(data, v) + } +} + +// UnmarshalStrict unmarshals the given data +// strictly (erroring when there are duplicate fields). +func UnmarshalStrict(data []byte, v interface{}) error { + preserveIntFloat := func(d *json.Decoder) *json.Decoder { + d.UseNumber() + return d + } + switch v := v.(type) { + case *map[string]interface{}: + if err := yaml.UnmarshalStrict(data, v, preserveIntFloat); err != nil { + return err + } + return jsonutil.ConvertMapNumbers(*v, 0) + case *[]interface{}: + if err := yaml.UnmarshalStrict(data, v, preserveIntFloat); err != nil { + return err + } + return jsonutil.ConvertSliceNumbers(*v, 0) + case *interface{}: + if err := yaml.UnmarshalStrict(data, v, preserveIntFloat); err != nil { + return err + } + return jsonutil.ConvertInterfaceNumbers(v, 0) + default: + return yaml.UnmarshalStrict(data, v) + } +} + +// ToJSON converts a single YAML document into a JSON document +// or returns an error. If the document appears to be JSON the +// YAML decoding path is not used (so that error messages are +// JSON specific). +func ToJSON(data []byte) ([]byte, error) { + if IsJSONBuffer(data) { + return data, nil + } + return yaml.YAMLToJSON(data) +} + +// YAMLToJSONDecoder decodes YAML documents from an io.Reader by +// separating individual documents. It first converts the YAML +// body to JSON, then unmarshals the JSON. +type YAMLToJSONDecoder struct { + reader Reader + inputOffset int +} + +// NewYAMLToJSONDecoder decodes YAML documents from the provided +// stream in chunks by converting each document (as defined by +// the YAML spec) into its own chunk, converting it to JSON via +// yaml.YAMLToJSON, and then passing it to json.Decoder. +func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder { + reader := bufio.NewReader(r) + return &YAMLToJSONDecoder{ + reader: NewYAMLReader(reader), + } +} + +// Decode reads a YAML document as JSON from the stream or returns +// an error. The decoding rules match json.Unmarshal, not +// yaml.Unmarshal. +func (d *YAMLToJSONDecoder) Decode(into interface{}) error { + bytes, err := d.reader.Read() + if err != nil && err != io.EOF { //nolint:errorlint + return err + } + + if len(bytes) != 0 { + err := yaml.Unmarshal(bytes, into) + if err != nil { + return YAMLSyntaxError{err} + } + } + d.inputOffset += len(bytes) + return err +} + +func (d *YAMLToJSONDecoder) InputOffset() int { + return d.inputOffset +} + +// YAMLDecoder reads chunks of objects and returns ErrShortBuffer if +// the data is not sufficient. +type YAMLDecoder struct { + r io.ReadCloser + scanner *bufio.Scanner + remaining []byte +} + +// NewDocumentDecoder decodes YAML documents from the provided +// stream in chunks by converting each document (as defined by +// the YAML spec) into its own chunk. io.ErrShortBuffer will be +// returned if the entire buffer could not be read to assist +// the caller in framing the chunk. +func NewDocumentDecoder(r io.ReadCloser) io.ReadCloser { + scanner := bufio.NewScanner(r) + // the size of initial allocation for buffer 4k + buf := make([]byte, 4*1024) + // the maximum size used to buffer a token 5M + scanner.Buffer(buf, 5*1024*1024) + scanner.Split(splitYAMLDocument) + return &YAMLDecoder{ + r: r, + scanner: scanner, + } +} + +// Read reads the previous slice into the buffer, or attempts to read +// the next chunk. +// TODO: switch to readline approach. +func (d *YAMLDecoder) Read(data []byte) (n int, err error) { + left := len(d.remaining) + if left == 0 { + // return the next chunk from the stream + if !d.scanner.Scan() { + err := d.scanner.Err() + if err == nil { + err = io.EOF + } + return 0, err + } + out := d.scanner.Bytes() + d.remaining = out + left = len(out) + } + + // fits within data + if left <= len(data) { + copy(data, d.remaining) + d.remaining = nil + return left, nil + } + + // caller will need to reread + copy(data, d.remaining[:len(data)]) + d.remaining = d.remaining[len(data):] + return len(data), io.ErrShortBuffer +} + +func (d *YAMLDecoder) Close() error { + return d.r.Close() +} + +const yamlSeparator = "\n---" +const separator = "---" + +// splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents. +func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + sep := len([]byte(yamlSeparator)) + if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 { + // We have a potential document terminator + i += sep + after := data[i:] + if len(after) == 0 { + // we can't read any more characters + if atEOF { + return len(data), data[:len(data)-sep], nil + } + return 0, nil, nil + } + if j := bytes.IndexByte(after, '\n'); j >= 0 { + return i + j + 1, data[0 : i-sep], nil + } + return 0, nil, nil + } + // If we're at EOF, we have a final, non-terminated line. Return it. + if atEOF { + return len(data), data, nil + } + // Request more data. + return 0, nil, nil +} + +// YAMLOrJSONDecoder attempts to decode a stream of JSON or YAML documents. +// While JSON is YAML, the way Go's JSON decode defines a multi-document stream +// is a series of JSON objects (e.g. {}{}), but YAML defines a multi-document +// stream as a series of documents separated by "---". +// +// This decoder will attempt to decode the stream as JSON first, and if that +// fails, it will switch to YAML. Once it determines the stream is JSON (by +// finding a non-YAML-delimited series of objects), it will not switch to YAML. +// Once it switches to YAML it will not switch back to JSON. +type YAMLOrJSONDecoder struct { + json *json.Decoder + jsonConsumed int64 // of the stream total, how much was JSON? + yaml *YAMLToJSONDecoder + yamlConsumed int64 // of the stream total, how much was YAML? + stream *StreamReader + count int // how many objects have been decoded +} + +type JSONSyntaxError struct { + Offset int64 + Err error +} + +func (e JSONSyntaxError) Error() string { + return fmt.Sprintf("json: offset %d: %s", e.Offset, e.Err.Error()) +} + +type YAMLSyntaxError struct { + err error +} + +func (e YAMLSyntaxError) Error() string { + return e.err.Error() +} + +// NewYAMLOrJSONDecoder returns a decoder that will process YAML documents +// or JSON documents from the given reader as a stream. bufferSize determines +// how far into the stream the decoder will look to figure out whether this +// is a JSON stream (has whitespace followed by an open brace). +func NewYAMLOrJSONDecoder(r io.Reader, bufferSize int) *YAMLOrJSONDecoder { + d := &YAMLOrJSONDecoder{} + + reader, _, mightBeJSON := GuessJSONStream(r, bufferSize) + d.stream = reader + if mightBeJSON { + d.json = json.NewDecoder(reader) + } else { + d.yaml = NewYAMLToJSONDecoder(reader) + } + return d +} + +// Decode unmarshals the next object from the underlying stream into the +// provide object, or returns an error. +func (d *YAMLOrJSONDecoder) Decode(into interface{}) error { + // Because we don't know if this is a JSON or YAML stream, a failure from + // both decoders is ambiguous. When in doubt, it will return the error from + // the JSON decoder. Unfortunately, this means that if the first document + // is invalid YAML, the error won't be awesome. + // TODO: the errors from YAML are not great, we could improve them a lot. + var firstErr error + if d.json != nil { + err := d.json.Decode(into) + if err == nil { + d.count++ + consumed := d.json.InputOffset() - d.jsonConsumed + d.stream.Consume(int(consumed)) + d.jsonConsumed += consumed + return nil + } + if err == io.EOF { //nolint:errorlint + return err + } + var syntax *json.SyntaxError + if ok := errors.As(err, &syntax); ok { + firstErr = JSONSyntaxError{ + Offset: syntax.Offset, + Err: syntax, + } + } else { + firstErr = err + } + if d.count > 1 { + // If we found 0 or 1 JSON object(s), this stream is still + // ambiguous. But if we found more than 1 JSON object, then this + // is an unambiguous JSON stream, and we should not switch to YAML. + return err + } + // If JSON decoding hits the end of one object and then fails on the + // next, it leaves any leading whitespace in the buffer, which can + // confuse the YAML decoder. We just eat any whitespace we find, up to + // and including the first newline. + d.stream.Rewind() + if err := d.consumeWhitespace(); err == nil { + d.yaml = NewYAMLToJSONDecoder(d.stream) + } + d.json = nil + } + if d.yaml != nil { + err := d.yaml.Decode(into) + if err == nil { + d.count++ + consumed := int64(d.yaml.InputOffset()) - d.yamlConsumed + d.stream.Consume(int(consumed)) + d.yamlConsumed += consumed + return nil + } + if err == io.EOF { //nolint:errorlint + return err + } + if firstErr == nil { + firstErr = err + } + } + if firstErr != nil { + return firstErr + } + return fmt.Errorf("decoding failed as both JSON and YAML") +} + +func (d *YAMLOrJSONDecoder) consumeWhitespace() error { + consumed := 0 + for { + buf, err := d.stream.ReadN(4) + if err != nil && err == io.EOF { //nolint:errorlint + return err + } + r, sz := utf8.DecodeRune(buf) + if r == utf8.RuneError || sz == 0 { + return fmt.Errorf("invalid utf8 rune") + } + d.stream.RewindN(len(buf) - sz) + if !unicode.IsSpace(r) { + d.stream.RewindN(sz) + d.stream.Consume(consumed) + return nil + } + consumed += sz + if r == '\n' { + d.stream.Consume(consumed) + return nil + } + if err == io.EOF { //nolint:errorlint + break + } + } + return io.EOF +} + +type Reader interface { + Read() ([]byte, error) +} + +type YAMLReader struct { + reader Reader +} + +func NewYAMLReader(r *bufio.Reader) *YAMLReader { + return &YAMLReader{ + reader: &LineReader{reader: r}, + } +} + +// Read returns a full YAML document. +func (r *YAMLReader) Read() ([]byte, error) { + var buffer bytes.Buffer + for { + line, err := r.reader.Read() + if err != nil && err != io.EOF { //nolint:errorlint + return nil, err + } + + sep := len([]byte(separator)) + if i := bytes.Index(line, []byte(separator)); i == 0 { + // We have a potential document terminator + i += sep + trimmed := strings.TrimSpace(string(line[i:])) + // We only allow comments and spaces following the yaml doc separator, otherwise we'll return an error + if len(trimmed) > 0 && string(trimmed[0]) != "#" { + return nil, YAMLSyntaxError{ + err: fmt.Errorf("invalid Yaml document separator: %s", trimmed), + } + } + if buffer.Len() != 0 { + return buffer.Bytes(), nil + } + if err == io.EOF { //nolint:errorlint + return nil, err + } + } + if err == io.EOF { //nolint:errorlint + if buffer.Len() != 0 { + // If we're at EOF, we have a final, non-terminated line. Return it. + return buffer.Bytes(), nil + } + return nil, err + } + buffer.Write(line) + } +} + +type LineReader struct { + reader *bufio.Reader +} + +// Read returns a single line (with '\n' ended) from the underlying reader. +// An error is returned iff there is an error with the underlying reader. +func (r *LineReader) Read() ([]byte, error) { + var ( + isPrefix bool = true + err error = nil + line []byte + buffer bytes.Buffer + ) + + for isPrefix && err == nil { + line, isPrefix, err = r.reader.ReadLine() + buffer.Write(line) + } + buffer.WriteByte('\n') + return buffer.Bytes(), err +} + +// GuessJSONStream scans the provided reader up to size, looking +// for an open brace indicating this is JSON. It will return the +// bufio.Reader it creates for the consumer. +func GuessJSONStream(r io.Reader, size int) (*StreamReader, []byte, bool) { + buffer := NewStreamReader(r, size) + b, _ := buffer.Peek(size) + return buffer, b, IsJSONBuffer(b) +} + +// IsJSONBuffer scans the provided buffer, looking +// for an open brace indicating this is JSON. +func IsJSONBuffer(buf []byte) bool { + return hasPrefix(buf, jsonPrefix) +} + +var jsonPrefix = []byte("{") + +// Return true if the first non-whitespace bytes in buf is +// prefix. +func hasPrefix(buf []byte, prefix []byte) bool { + trim := bytes.TrimLeftFunc(buf, unicode.IsSpace) + return bytes.HasPrefix(trim, prefix) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/decoder_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/decoder_test.go new file mode 100644 index 0000000000..60a5ec80f4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/decoder_test.go @@ -0,0 +1,559 @@ +/* +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 yaml + +import ( + "bufio" + "bytes" + "fmt" + "io" + "math/rand" + "reflect" + "regexp" + "strings" + "testing" +) + +func TestYAMLDecoderReadBytesLength(t *testing.T) { + d := `--- +stuff: 1 + test-foo: 1 +` + testCases := []struct { + bufLen int + expectLen int + expectErr error + }{ + {len(d), len(d), nil}, + {len(d) + 10, len(d), nil}, + {len(d) - 10, len(d) - 10, io.ErrShortBuffer}, + } + + for i, testCase := range testCases { + r := NewDocumentDecoder(io.NopCloser(bytes.NewReader([]byte(d)))) + b := make([]byte, testCase.bufLen) + n, err := r.Read(b) + if err != testCase.expectErr || n != testCase.expectLen { + t.Fatalf("%d: unexpected body: %d / %v", i, n, err) + } + } +} + +func TestBigYAML(t *testing.T) { + d := ` +stuff: 1 +` + maxLen := 5 * 1024 * 1024 + bufferLen := 4 * 1024 + // maxLen 5 M + dd := strings.Repeat(d, 512*1024) + r := NewDocumentDecoder(io.NopCloser(bytes.NewReader([]byte(dd[:maxLen-1])))) + b := make([]byte, bufferLen) + n, err := r.Read(b) + if err != io.ErrShortBuffer { + t.Fatalf("expected ErrShortBuffer: %d / %v", n, err) + } + b = make([]byte, maxLen) + n, err = r.Read(b) + if err != nil { + t.Fatalf("expected nil: %d / %v", n, err) + } + r = NewDocumentDecoder(io.NopCloser(bytes.NewReader([]byte(dd)))) + b = make([]byte, maxLen) + n, err = r.Read(b) + if err != bufio.ErrTooLong { + t.Fatalf("bufio.Scanner: token too long: %d / %v", n, err) + } +} + +func TestYAMLDecoderCallsAfterErrShortBufferRestOfFrame(t *testing.T) { + d := `--- +stuff: 1 + test-foo: 1` + r := NewDocumentDecoder(io.NopCloser(bytes.NewReader([]byte(d)))) + b := make([]byte, 12) + n, err := r.Read(b) + if err != io.ErrShortBuffer || n != 12 { + t.Fatalf("expected ErrShortBuffer: %d / %v", n, err) + } + expected := "---\nstuff: 1" + if string(b) != expected { + t.Fatalf("expected bytes read to be: %s got: %s", expected, string(b)) + } + b = make([]byte, 13) + n, err = r.Read(b) + if err != nil || n != 13 { + t.Fatalf("expected nil: %d / %v", n, err) + } + expected = "\n\ttest-foo: 1" + if string(b) != expected { + t.Fatalf("expected bytes read to be: '%s' got: '%s'", expected, string(b)) + } + b = make([]byte, 15) + n, err = r.Read(b) + if err != io.EOF || n != 0 { //nolint:errorlint + t.Fatalf("expected EOF: %d / %v", n, err) + } +} + +func TestSplitYAMLDocument(t *testing.T) { + testCases := []struct { + input string + atEOF bool + expect string + adv int + }{ + {"foo", true, "foo", 3}, + {"fo", false, "", 0}, + + {"---", true, "---", 3}, + {"---\n", true, "---\n", 4}, + {"---\n", false, "", 0}, + + {"\n---\n", false, "", 5}, + {"\n---\n", true, "", 5}, + + {"abc\n---\ndef", true, "abc", 8}, + {"def", true, "def", 3}, + {"", true, "", 0}, + } + for i, testCase := range testCases { + adv, token, err := splitYAMLDocument([]byte(testCase.input), testCase.atEOF) + if err != nil { + t.Errorf("%d: unexpected error: %v", i, err) + continue + } + if adv != testCase.adv { + t.Errorf("%d: advance did not match: %d %d", i, testCase.adv, adv) + } + if testCase.expect != string(token) { + t.Errorf("%d: token did not match: %q %q", i, testCase.expect, string(token)) + } + } +} + +func TestGuessJSON(t *testing.T) { + if r, _, isJSON := GuessJSONStream(bytes.NewReader([]byte(" \n{}")), 100); !isJSON { + t.Fatalf("expected stream to be JSON") + } else { + b := make([]byte, 30) + n, err := r.Read(b) + if err != nil || n != 4 { + t.Fatalf("unexpected body: %d / %v", n, err) + } + if string(b[:n]) != " \n{}" { + t.Fatalf("unexpected body: %q", string(b[:n])) + } + } +} + +func TestScanYAML(t *testing.T) { + s := bufio.NewScanner(bytes.NewReader([]byte(`--- +stuff: 1 + +--- + `))) + s.Split(splitYAMLDocument) + if !s.Scan() { + t.Fatalf("should have been able to scan") + } + t.Logf("scan: %s", s.Text()) + if !s.Scan() { + t.Fatalf("should have been able to scan") + } + t.Logf("scan: %s", s.Text()) + if s.Scan() { + t.Fatalf("scan should have been done") + } + if s.Err() != nil { + t.Fatalf("err should have been nil: %v", s.Err()) + } +} + +func TestDecodeYAML(t *testing.T) { + s := NewYAMLToJSONDecoder(bytes.NewReader([]byte(`--- +stuff: 1 + +--- + `))) + obj := generic{} + if err := s.Decode(&obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if fmt.Sprintf("%#v", obj) != `yaml.generic{"stuff":1}` { + t.Errorf("unexpected object: %#v", obj) + } + obj = generic{} + if err := s.Decode(&obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(obj) != 0 { + t.Fatalf("unexpected object: %#v", obj) + } + obj = generic{} + if err := s.Decode(&obj); err != io.EOF { //nolint:errorlint + t.Fatalf("unexpected error: %v", err) + } +} + +func TestDecodeYAMLSeparatorValidation(t *testing.T) { + s := NewYAMLToJSONDecoder(bytes.NewReader([]byte(`--- +stuff: 1 +--- # Make sure termination happen with inline comment +stuff: 2 +--- +stuff: 3 +--- Make sure uncommented content results YAMLSyntaxError + + `))) + obj := generic{} + if err := s.Decode(&obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if fmt.Sprintf("%#v", obj) != `yaml.generic{"stuff":1}` { + t.Errorf("unexpected object: %#v", obj) + } + obj = generic{} + if err := s.Decode(&obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if fmt.Sprintf("%#v", obj) != `yaml.generic{"stuff":2}` { + t.Errorf("unexpected object: %#v", obj) + } + obj = generic{} + err := s.Decode(&obj) + if err == nil { + t.Fatalf("expected YamlSyntaxError, got nil instead") + } + if _, ok := err.(YAMLSyntaxError); !ok { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestDecodeBrokenYAML(t *testing.T) { + s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`--- +stuff: 1 + test-foo: 1 + +--- + `)), 100) + obj := generic{} + err := s.Decode(&obj) + if err == nil { + t.Fatal("expected error with yaml: violate, got no error") + } + fmt.Printf("err: %s\n", err.Error()) + if !strings.Contains(err.Error(), "yaml: line 3:") { + t.Fatalf("expected %q to have 'yaml: line 3:' found a tab character", err.Error()) + } +} + +func TestDecodeBrokenJSON(t *testing.T) { + s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`{ + "foo": { + "stuff": 1 + "otherStuff": 2 + } +} + `)), 100) + obj := generic{} + err := s.Decode(&obj) + if err == nil { + t.Fatal("expected error with json: prefix, got no error") + } + const msg = `json: .*invalid character.*".*after object key:value pair` + if matched, _ := regexp.MatchString(msg, err.Error()); !matched { + t.Fatalf("expected string matching %q, got %q", msg, err.Error()) + } +} + +type generic map[string]interface{} + +func TestYAMLOrJSONDecoder(t *testing.T) { + testCases := []struct { + input string + buffer int + isJSON bool + err bool + out []generic + }{ + {` {"1":2}{"3":4}`, 2, true, false, []generic{ + {"1": 2}, + {"3": 4}, + }}, + {" \n{}", 3, true, false, []generic{ + {}, + }}, + {" \na: b", 2, false, false, []generic{ + {"a": "b"}, + }}, + {" \n{\"a\": \"b\"}", 2, false, true, []generic{ + {"a": "b"}, + }}, + {" \n{\"a\": \"b\"}", 3, true, false, []generic{ + {"a": "b"}, + }}, + {` {"a":"b"}`, 100, true, false, []generic{ + {"a": "b"}, + }}, + {"", 1, false, false, []generic{}}, + {"foo: bar\n---\nbaz: biz", 100, false, false, []generic{ + {"foo": "bar"}, + {"baz": "biz"}, + }}, + {"---\nfoo: bar\n--- # with Comment\nbaz: biz", 100, false, false, []generic{ + {"foo": "bar"}, + {"baz": "biz"}, + }}, + // Spaces for indent, tabs are not allowed in YAML. + {"foo:\n field: bar\n---\nbaz:\n field: biz", 100, false, false, []generic{ + {"foo": map[string]any{"field": "bar"}}, + {"baz": map[string]any{"field": "biz"}}, + }}, + {"foo: bar\n---\n", 100, false, false, []generic{ + {"foo": "bar"}, + }}, + {"foo: bar\n---", 100, false, false, []generic{ + {"foo": "bar"}, + }}, + {"foo: bar\n--", 100, false, true, []generic{ + {"foo": "bar"}, + }}, + {"foo: bar\n-", 100, false, true, []generic{ + {"foo": "bar"}, + }}, + {"foo: bar\n", 100, false, false, []generic{ + {"foo": "bar"}, + }}, + // First document is JSON, second is YAML + {"{\"foo\": \"bar\"}\n---\n{baz: biz}", 100, false, false, []generic{ + {"foo": "bar"}, + {"baz": "biz"}, + }}, + // First document is JSON, second is YAML but with smaller size. + {"{\"foo\": \"bar\"}\n---\na: b", 100, false, false, []generic{ + {"foo": "bar"}, + {"a": "b"}, + }}, + // First document is JSON, second is YAML,but with smaller size and + // trailing whitespace. + {"{\"foo\": \"bar\"} \n---\na: b", 100, false, false, []generic{ + {"foo": "bar"}, + {"a": "b"}, + }}, + // First document is JSON, second is YAML, longer than the buffer + {"{\"foo\": \"bar\"}\n---\n{baz: biz0123456780123456780123456780123456780123456789}", 20, false, false, []generic{ + {"foo": "bar"}, + {"baz": "biz0123456780123456780123456780123456780123456789"}, + }}, + // First document is JSON, then whitespace, then YAML + {"{\"foo\": \"bar\"} \n---\n{baz: biz}", 100, false, false, []generic{ + {"foo": "bar"}, + {"baz": "biz"}, + }}, + // First document is YAML, second is JSON + {"{foo: bar}\n---\n{\"baz\": \"biz\"}", 100, false, false, []generic{ + {"foo": "bar"}, + {"baz": "biz"}, + }}, + // First document is JSON, second is YAML, using spaces + {"{\n \"foo\": \"bar\"\n}\n---\n{\n baz: biz\n}", 100, false, false, []generic{ + {"foo": "bar"}, + {"baz": "biz"}, + }}, + // First document is JSON, second is YAML, using tabs + {"{\n\t\"foo\": \"bar\"\n}\n---\n{\n\tbaz: biz\n}", 100, false, false, []generic{ + {"foo": "bar"}, + {"baz": "biz"}, + }}, + // First 2 documents are JSON, third is YAML (stream is JSON) + {"{\"foo\": \"bar\"}\n{\"baz\": \"biz\"}\n---\n{qux: zrb}", 100, true, true, nil}, + } + for i, testCase := range testCases { + decoder := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(testCase.input)), testCase.buffer) + objs := []generic{} + + var err error + for { + out := make(generic) + err = decoder.Decode(&out) + if err != nil { + break + } + objs = append(objs, out) + } + if err != io.EOF { //nolint:errorlint + switch { + case testCase.err && err == nil: + t.Errorf("%d: unexpected non-error", i) + continue + case !testCase.err && err != nil: + t.Errorf("%d: unexpected error: %v", i, err) + continue + case err != nil: + continue + } + } + switch { + case decoder.yaml != nil: + if testCase.isJSON { + t.Errorf("%d: expected JSON decoder, got YAML", i) + } + case decoder.json != nil: + if !testCase.isJSON { + t.Errorf("%d: expected YAML decoder, got JSON", i) + } + } + if fmt.Sprintf("%#v", testCase.out) != fmt.Sprintf("%#v", objs) { + t.Errorf("%d: objects were not equal: \n%#v\n%#v", i, testCase.out, objs) + } + } +} + +func TestReadSingleLongLine(t *testing.T) { + testReadLines(t, []int{128 * 1024}) +} + +func TestReadRandomLineLengths(t *testing.T) { + minLength := 100 + maxLength := 96 * 1024 + maxLines := 100 + + lineLengths := make([]int, maxLines) + for i := 0; i < maxLines; i++ { + lineLengths[i] = rand.Intn(maxLength-minLength) + minLength + } + + testReadLines(t, lineLengths) +} + +func testReadLines(t *testing.T, lineLengths []int) { + var ( + lines [][]byte + inputStream []byte + ) + for _, lineLength := range lineLengths { + inputLine := make([]byte, lineLength+1) + for i := 0; i < lineLength; i++ { + char := rand.Intn('z'-'A') + 'A' + inputLine[i] = byte(char) + } + inputLine[len(inputLine)-1] = '\n' + lines = append(lines, inputLine) + } + for _, line := range lines { + inputStream = append(inputStream, line...) + } + + // init Reader + reader := bufio.NewReader(bytes.NewReader(inputStream)) + lineReader := &LineReader{reader: reader} + + // read lines + var readLines [][]byte + for range lines { + bytes, err := lineReader.Read() + if err != nil && err != io.EOF { //nolint:errorlint + t.Fatalf("failed to read lines: %v", err) + } + readLines = append(readLines, bytes) + } + + // validate + for i := range lines { + if len(lines[i]) != len(readLines[i]) { + t.Fatalf("expected line length: %d, but got %d", len(lines[i]), len(readLines[i])) + } + if !reflect.DeepEqual(lines[i], readLines[i]) { + t.Fatalf("expected line: %v, but got %v", lines[i], readLines[i]) + } + } +} + +func TestTypedJSONOrYamlErrors(t *testing.T) { + s := NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`{ + "foo": { + "stuff": 1 + "otherStuff": 2 + } +} + `)), 100) + obj := generic{} + err := s.Decode(&obj) + if err == nil { + t.Fatal("expected error with json: prefix, got no error") + } + if _, ok := err.(JSONSyntaxError); !ok { + t.Fatalf("expected %q to be of type JSONSyntaxError", err.Error()) + } + + s = NewYAMLOrJSONDecoder(bytes.NewReader([]byte(`--- +stuff: 1 + test-foo: 1 + +--- + `)), 100) + obj = generic{} + err = s.Decode(&obj) + if err == nil { + t.Fatal("expected error with yaml: prefix, got no error") + } + if _, ok := err.(YAMLSyntaxError); !ok { + t.Fatalf("expected %q to be of type YAMLSyntaxError", err.Error()) + } +} + +func TestUnmarshal(t *testing.T) { + mapWithIntegerBytes := []byte(`replicas: 1`) + mapWithInteger := make(map[string]interface{}) + if err := Unmarshal(mapWithIntegerBytes, &mapWithInteger); err != nil { + t.Fatalf("unexpected error unmarshaling yaml: %v", err) + } + if _, ok := mapWithInteger["replicas"].(int64); !ok { + t.Fatalf(`Expected number in map to be int64 but got "%T"`, mapWithInteger["replicas"]) + } + + sliceWithIntegerBytes := []byte(`- 1`) + var sliceWithInteger []interface{} + if err := Unmarshal(sliceWithIntegerBytes, &sliceWithInteger); err != nil { + t.Fatalf("unexpected error unmarshaling yaml: %v", err) + } + if _, ok := sliceWithInteger[0].(int64); !ok { + t.Fatalf(`Expected number in slice to be int64 but got "%T"`, sliceWithInteger[0]) + } + + integerBytes := []byte(`1`) + var integer interface{} + if err := Unmarshal(integerBytes, &integer); err != nil { + t.Fatalf("unexpected error unmarshaling yaml: %v", err) + } + if _, ok := integer.(int64); !ok { + t.Fatalf(`Expected number to be int64 but got "%T"`, integer) + } + + otherTypeBytes := []byte(`123: 2`) + otherType := make(map[int]interface{}) + if err := Unmarshal(otherTypeBytes, &otherType); err != nil { + t.Fatalf("unexpected error unmarshaling yaml: %v", err) + } + if _, ok := otherType[123].(int64); ok { + t.Fatalf(`Expected number not to be converted to int64`) + } + if _, ok := otherType[123].(float64); !ok { + t.Fatalf(`Expected number to be float64 but got "%T"`, otherType[123]) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/stream_reader.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/stream_reader.go new file mode 100644 index 0000000000..d06991057f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/stream_reader.go @@ -0,0 +1,130 @@ +/* +Copyright 2025 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 yaml + +import "io" + +// StreamReader is a reader designed for consuming streams of variable-length +// messages. It buffers data until it is explicitly consumed, and can be +// rewound to re-read previous data. +type StreamReader struct { + r io.Reader + buf []byte + head int // current read offset into buf + ttlConsumed int // number of bytes which have been consumed +} + +// NewStreamReader creates a new StreamReader wrapping the provided +// io.Reader. +func NewStreamReader(r io.Reader, size int) *StreamReader { + if size == 0 { + size = 4096 + } + return &StreamReader{ + r: r, + buf: make([]byte, 0, size), // Start with a reasonable capacity + } +} + +// Read implements io.Reader. It first returns any buffered data after the +// current offset, and if that's exhausted, reads from the underlying reader +// and buffers the data. The returned data is not considered consumed until the +// Consume method is called. +func (r *StreamReader) Read(p []byte) (n int, err error) { + // If we have buffered data, return it + if r.head < len(r.buf) { + n = copy(p, r.buf[r.head:]) + r.head += n + return n, nil + } + + // If we've already hit EOF, return it + if r.r == nil { + return 0, io.EOF + } + + // Read from the underlying reader + n, err = r.r.Read(p) + if n > 0 { + r.buf = append(r.buf, p[:n]...) + r.head += n + } + if err == nil { + return n, nil + } + if err == io.EOF { + // Store that we've hit EOF by setting r to nil + r.r = nil + } + return n, err +} + +// ReadN reads exactly n bytes from the reader, blocking until all bytes are +// read or an error occurs. If an error occurs, the number of bytes read is +// returned along with the error. If EOF is hit before n bytes are read, this +// will return the bytes read so far, along with io.EOF. The returned data is +// not considered consumed until the Consume method is called. +func (r *StreamReader) ReadN(want int) ([]byte, error) { + ret := make([]byte, want) + off := 0 + for off < want { + n, err := r.Read(ret[off:]) + if err != nil { + return ret[:off+n], err + } + off += n + } + return ret, nil +} + +// Peek returns the next n bytes without advancing the reader. The returned +// bytes are valid until the next call to Consume. +func (r *StreamReader) Peek(n int) ([]byte, error) { + buf, err := r.ReadN(n) + r.RewindN(len(buf)) + if err != nil { + return buf, err + } + return buf, nil +} + +// Rewind resets the reader to the beginning of the buffered data. +func (r *StreamReader) Rewind() { + r.head = 0 +} + +// RewindN rewinds the reader by n bytes. If n is greater than the current +// buffer, the reader is rewound to the beginning of the buffer. +func (r *StreamReader) RewindN(n int) { + r.head -= min(n, r.head) +} + +// Consume discards up to n bytes of previously read data from the beginning of +// the buffer. Once consumed, that data is no longer available for rewinding. +// If n is greater than the current buffer, the buffer is cleared. Consume +// never consume data from the underlying reader. +func (r *StreamReader) Consume(n int) { + n = min(n, len(r.buf)) + r.buf = r.buf[n:] + r.head -= n + r.ttlConsumed += n +} + +// Consumed returns the number of bytes consumed from the input reader. +func (r *StreamReader) Consumed() int { + return r.ttlConsumed +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/stream_reader_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/stream_reader_test.go new file mode 100644 index 0000000000..b010610c47 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/util/yaml/stream_reader_test.go @@ -0,0 +1,388 @@ +/* +Copyright 2025 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 yaml + +import ( + "io" + "strings" + "testing" +) + +// srt = StreamReaderTest +type srtStep struct { + op string + expected string + err error + size int +} + +func srtRead(size int, expected string, err error) srtStep { + return srtStep{op: "Read", size: size, expected: expected, err: err} +} +func srtReadN(size int, expected string, err error) srtStep { + return srtStep{op: "ReadN", size: size, expected: expected, err: err} +} +func srtPeek(size int, expected string, err error) srtStep { + return srtStep{op: "Peek", size: size, expected: expected, err: err} +} +func srtRewind() srtStep { + return srtStep{op: "Rewind"} +} +func srtRewindN(size int) srtStep { + return srtStep{op: "RewindN", size: size} +} +func srtConsume(size int) srtStep { + return srtStep{op: "Consume", size: size} +} +func srtConsumed(exp int) srtStep { + return srtStep{op: "Consumed", size: exp} +} + +func srtRun(t *testing.T, reader *StreamReader, steps []srtStep) { + t.Helper() + + checkRead := func(i int, step srtStep, buf []byte, err error) { + t.Helper() + if err != nil && step.err == nil { + t.Errorf("step %d: unexpected error: %v", i, err) + } else if err == nil && step.err != nil { + t.Errorf("step %d: expected error %v", i, step.err) + } else if err != nil && err != step.err { //nolint:errorlint + t.Errorf("step %d: expected error %v, got %v", i, step.err, err) + } + if got := string(buf); got != step.expected { + t.Errorf("step %d: expected %q, got %q", i, step.expected, got) + } + } + + for i, step := range steps { + switch step.op { + case "Read": + buf := make([]byte, step.size) + n, err := reader.Read(buf) + buf = buf[:n] + checkRead(i, step, buf, err) + case "ReadN": + buf, err := reader.ReadN(step.size) + checkRead(i, step, buf, err) + case "Peek": + buf, err := reader.Peek(step.size) + checkRead(i, step, buf, err) + case "Rewind": + reader.Rewind() + case "RewindN": + reader.RewindN(step.size) + case "Consume": + reader.Consume(step.size) + case "Consumed": + if n := reader.Consumed(); n != step.size { + t.Errorf("step %d: expected %d consumed, got %d", i, step.size, n) + } + default: + t.Fatalf("step %d: unknown operation %q", i, step.op) + } + } +} + +func TestStreamReader_Read(t *testing.T) { + tests := []struct { + name string + input string + steps []srtStep + }{{ + name: "empty input", + input: "", + steps: []srtStep{ + srtRead(1, "", io.EOF), + srtRead(1, "", io.EOF), // still EOF + }, + }, { + name: "simple reads", + input: "0123456789", + steps: []srtStep{ + srtRead(5, "01234", nil), + srtRead(5, "56789", nil), + srtRead(1, "", io.EOF), + }, + }, { + name: "short read at EOF", + input: "0123456789", + steps: []srtStep{ + srtRead(8, "01234567", nil), + srtRead(8, "89", nil), // short read, no error + srtRead(1, "", io.EOF), + }, + }, { + name: "short reads from buffer", + input: "0123456789", + steps: []srtStep{ + srtRead(3, "012", nil), // fill buffer + srtRewind(), + srtRead(4, "012", nil), // short read from buffer + srtRewind(), + srtRead(4, "012", nil), // still short + srtRead(4, "3456", nil), // from reader + srtRewind(), + srtRead(10, "0123456", nil), // short read from buffer + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := NewStreamReader(strings.NewReader(tt.input), 4) // small initial buffer + srtRun(t, reader, tt.steps) + }) + } +} + +func TestStreamReader_Rewind(t *testing.T) { + tests := []struct { + name string + input string + steps []srtStep + }{{ + name: "simple read and rewind", + input: "0123456789", + steps: []srtStep{ + srtRead(4, "0123", nil), + srtRead(4, "4567", nil), + srtRead(4, "89", nil), + srtRead(1, "", io.EOF), + srtRewind(), + srtRead(4, "0123", nil), + srtRead(4, "4567", nil), + srtRead(4, "89", nil), + srtRead(1, "", io.EOF), + }, + }, { + name: "multiple rewinds", + input: "01234", + steps: []srtStep{ + srtRead(2, "01", nil), + srtRewind(), + srtRead(2, "01", nil), + srtRead(2, "23", nil), + srtRewind(), + srtRead(2, "01", nil), + srtRead(2, "23", nil), + srtRead(2, "4", nil), + srtRead(1, "", io.EOF), + srtRewind(), + srtRead(100, "01234", nil), + srtRead(1, "", io.EOF), + }, + }, { + name: "empty input", + input: "", + steps: []srtStep{ + srtRead(1, "", io.EOF), + srtRewind(), + srtRead(1, "", io.EOF), + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := NewStreamReader(strings.NewReader(tt.input), 4) // small initial buffer + srtRun(t, reader, tt.steps) + }) + } +} + +func TestStreamReader_RewindN(t *testing.T) { + tests := []struct { + name string + input string + steps []srtStep + }{{ + name: "simple rewindn", + input: "0123456789", + steps: []srtStep{ + srtRead(4, "0123", nil), + srtRead(4, "4567", nil), + srtRead(4, "89", nil), + srtRead(1, "", io.EOF), + srtRewindN(4), + srtRead(2, "67", nil), + srtRewindN(4), + srtRead(10, "456789", nil), + srtRead(1, "", io.EOF), + }, + }, { + name: "empty input", + input: "", + steps: []srtStep{ + srtRead(1, "", io.EOF), + srtRewindN(100), + srtRead(1, "", io.EOF), + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := NewStreamReader(strings.NewReader(tt.input), 4) // small initial buffer + srtRun(t, reader, tt.steps) + }) + } +} + +func TestStreamReader_Consume(t *testing.T) { + tests := []struct { + name string + input string + steps []srtStep + }{{ + name: "simple consume", + input: "0123456789", + steps: []srtStep{ + srtConsumed(0), + srtRead(4, "0123", nil), + srtRead(4, "4567", nil), + srtConsume(2), // drops 01 + srtConsumed(2), + srtRead(4, "89", nil), + srtRead(1, "", io.EOF), + srtRewind(), + srtRead(5, "23456", nil), + srtRead(5, "789", nil), + srtRead(1, "", io.EOF), + srtConsumed(2), + }, + }, { + name: "consume too much", + input: "01234", + steps: []srtStep{ + srtConsumed(0), + srtRead(5, "01234", nil), + srtConsume(5), + srtConsumed(5), + srtConsume(5), + srtConsumed(5), + srtRead(1, "", io.EOF), + srtConsume(5), + srtConsumed(5), + srtRead(1, "", io.EOF), + srtConsumed(5), + }, + }, { + name: "empty input", + input: "", + steps: []srtStep{ + srtConsumed(0), + srtConsume(5), + srtRead(1, "", io.EOF), + srtConsumed(0), + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := NewStreamReader(strings.NewReader(tt.input), 4) // small initial buffer + srtRun(t, reader, tt.steps) + }) + } +} + +func TestStreamReader_ReadN(t *testing.T) { + tests := []struct { + name string + input string + steps []srtStep + }{{ + name: "short read full readN", + input: "0123456789", + steps: []srtStep{ + srtRead(3, "012", nil), // fill buffer + srtRewind(), + srtRead(5, "012", nil), // short read from buffer + srtRewind(), + srtReadN(5, "01234", nil), // full readN + srtRewind(), + srtRead(10, "01234", nil), // short read from buffer + srtRewind(), + srtReadN(10, "0123456789", nil), // full readN + srtRewind(), + srtRead(10, "0123456789", nil), // full read from buffer + srtRead(1, "", io.EOF), + }, + }, { + name: "short read consume readN", + input: "0123456789", + steps: []srtStep{ + srtRead(3, "012", nil), // fill buffer + srtRewind(), + srtRead(4, "012", nil), // short read from buffer + srtConsume(1), + srtRewind(), + srtRead(4, "12", nil), // short read from buffer + srtRewind(), + srtReadN(4, "1234", nil), // full read + srtConsume(1), + srtRewind(), + srtRead(4, "234", nil), // short read from buffer + srtRewind(), + srtReadN(10, "23456789", io.EOF), // short readN, EOF + srtRewind(), + srtRead(10, "23456789", nil), // full read from buffer + srtRead(1, "", io.EOF), + }, + }, { + name: "empty input", + input: "", + steps: []srtStep{ + srtReadN(1, "", io.EOF), + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := NewStreamReader(strings.NewReader(tt.input), 4) // small initial buffer + srtRun(t, reader, tt.steps) + }) + } +} + +func TestStreamReader_Peek(t *testing.T) { + tests := []struct { + name string + input string + steps []srtStep + }{{ + name: "simple peek", + input: "0123456789", + steps: []srtStep{ + srtPeek(3, "012", nil), // fill buffer + srtRead(5, "012", nil), // short read from buffer + srtRewind(), + srtPeek(6, "012345", nil), // fill buffer + srtRead(10, "012345", nil), // short read from buffer + }, + }, { + name: "empty input", + input: "", + steps: []srtStep{ + srtPeek(1, "", io.EOF), + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := NewStreamReader(strings.NewReader(tt.input), 0) + srtRun(t, reader, tt.steps) + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/doc.go new file mode 100644 index 0000000000..70e3f76b23 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/doc.go @@ -0,0 +1,22 @@ +/* +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. +*/ + +// +k8s:openapi-gen=true +// +k8s:openapi-model-package=io.k8s.apimachinery.pkg.version +// + +// Package version supplies the type for version information. +package version diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/helpers.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/helpers.go new file mode 100644 index 0000000000..5e041d6f3f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/helpers.go @@ -0,0 +1,88 @@ +/* +Copyright 2018 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 version + +import ( + "regexp" + "strconv" + "strings" +) + +type versionType int + +const ( + // Bigger the version type number, higher priority it is + versionTypeAlpha versionType = iota + versionTypeBeta + versionTypeGA +) + +var kubeVersionRegex = regexp.MustCompile("^v([\\d]+)(?:(alpha|beta)([\\d]+))?$") + +func parseKubeVersion(v string) (majorVersion int, vType versionType, minorVersion int, ok bool) { + var err error + submatches := kubeVersionRegex.FindStringSubmatch(v) + if len(submatches) != 4 { + return 0, 0, 0, false + } + switch submatches[2] { + case "alpha": + vType = versionTypeAlpha + case "beta": + vType = versionTypeBeta + case "": + vType = versionTypeGA + default: + return 0, 0, 0, false + } + if majorVersion, err = strconv.Atoi(submatches[1]); err != nil { + return 0, 0, 0, false + } + if vType != versionTypeGA { + if minorVersion, err = strconv.Atoi(submatches[3]); err != nil { + return 0, 0, 0, false + } + } + return majorVersion, vType, minorVersion, true +} + +// CompareKubeAwareVersionStrings compares two kube-like version strings. +// Kube-like version strings are starting with a v, followed by a major version, optional "alpha" or "beta" strings +// followed by a minor version (e.g. v1, v2beta1). Versions will be sorted based on GA/alpha/beta first and then major +// and minor versions. e.g. v2, v1, v1beta2, v1beta1, v1alpha1. +func CompareKubeAwareVersionStrings(v1, v2 string) int { + if v1 == v2 { + return 0 + } + v1major, v1type, v1minor, ok1 := parseKubeVersion(v1) + v2major, v2type, v2minor, ok2 := parseKubeVersion(v2) + switch { + case !ok1 && !ok2: + return strings.Compare(v2, v1) + case !ok1 && ok2: + return -1 + case ok1 && !ok2: + return 1 + } + if v1type != v2type { + return int(v1type) - int(v2type) + } + if v1major != v2major { + return v1major - v2major + } + return v1minor - v2minor +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/helpers_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/helpers_test.go new file mode 100644 index 0000000000..a98ed70e2a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/helpers_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2018 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 version + +import ( + "testing" +) + +func TestCompareKubeAwareVersionStrings(t *testing.T) { + tests := []*struct { + v1, v2 string + expectedGreater bool + }{ + {"v1", "v2", false}, + {"v2", "v1", true}, + {"v10", "v2", true}, + {"v1", "v2alpha1", true}, + {"v1", "v2beta1", true}, + {"v1alpha2", "v1alpha1", true}, + {"v1beta1", "v2alpha3", true}, + {"v1alpha10", "v1alpha2", true}, + {"v1beta10", "v1beta2", true}, + {"foo", "v1beta2", false}, + {"bar", "foo", true}, + {"version1", "version2", true}, // Non kube-like versions are sorted alphabetically + {"version1", "version10", true}, // Non kube-like versions are sorted alphabetically + } + + for _, tc := range tests { + if e, a := tc.expectedGreater, CompareKubeAwareVersionStrings(tc.v1, tc.v2) > 0; e != a { + if e { + t.Errorf("expected %s to be greater than %s", tc.v1, tc.v2) + } else { + t.Errorf("expected %s to be less than %s", tc.v1, tc.v2) + } + } + } +} + +func Test_parseKubeVersion(t *testing.T) { + tests := []struct { + name string + v string + wantMajorVersion int + wantVType versionType + wantMinorVersion int + wantOk bool + }{ + { + name: "invalid version for ga", + v: "v1.1", + wantMajorVersion: 0, + wantVType: 0, + wantMinorVersion: 0, + wantOk: false, + }, + { + name: "invalid version for alpha", + v: "v1alpha1.1", + wantMajorVersion: 0, + wantVType: 0, + wantMinorVersion: 0, + wantOk: false, + }, + { + name: "alpha version", + v: "v1alpha1", + wantMajorVersion: 1, + wantVType: 0, + wantMinorVersion: 1, + wantOk: true, + }, + { + name: "beta version", + v: "v2beta10", + wantMajorVersion: 2, + wantVType: 1, + wantMinorVersion: 10, + wantOk: true, + }, + { + name: "ga version", + v: "v3", + wantMajorVersion: 3, + wantVType: 2, + wantMinorVersion: 0, + wantOk: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotMajorVersion, gotVType, gotMinorVersion, gotOk := parseKubeVersion(tt.v) + if gotMajorVersion != tt.wantMajorVersion { + t.Errorf("parseKubeVersion() gotMajorVersion = %v, want %v", gotMajorVersion, tt.wantMajorVersion) + } + if gotVType != tt.wantVType { + t.Errorf("parseKubeVersion() gotVType = %v, want %v", gotVType, tt.wantVType) + } + if gotMinorVersion != tt.wantMinorVersion { + t.Errorf("parseKubeVersion() gotMinorVersion = %v, want %v", gotMinorVersion, tt.wantMinorVersion) + } + if gotOk != tt.wantOk { + t.Errorf("parseKubeVersion() gotOk = %v, want %v", gotOk, tt.wantOk) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/types.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/types.go new file mode 100644 index 0000000000..6a18f9e91d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/types.go @@ -0,0 +1,47 @@ +/* +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 version + +// Info contains versioning information. +// TODO: Add []string of api versions supported? It's still unclear +// how we'll want to distribute that information. +type Info struct { + // Major is the major version of the binary version + Major string `json:"major"` + // Minor is the minor version of the binary version + Minor string `json:"minor"` + // EmulationMajor is the major version of the emulation version + EmulationMajor string `json:"emulationMajor,omitempty"` + // EmulationMinor is the minor version of the emulation version + EmulationMinor string `json:"emulationMinor,omitempty"` + // MinCompatibilityMajor is the major version of the minimum compatibility version + MinCompatibilityMajor string `json:"minCompatibilityMajor,omitempty"` + // MinCompatibilityMinor is the minor version of the minimum compatibility version + MinCompatibilityMinor string `json:"minCompatibilityMinor,omitempty"` + GitVersion string `json:"gitVersion"` + GitCommit string `json:"gitCommit"` + GitTreeState string `json:"gitTreeState"` + BuildDate string `json:"buildDate"` + GoVersion string `json:"goVersion"` + Compiler string `json:"compiler"` + Platform string `json:"platform"` +} + +// String returns info as a human-friendly version string. +func (info Info) String() string { + return info.GitVersion +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/zz_generated.model_name.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/zz_generated.model_name.go new file mode 100644 index 0000000000..e5a6d395ad --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/version/zz_generated.model_name.go @@ -0,0 +1,27 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package version + +// OpenAPIModelName returns the OpenAPI model name for this type. +func (in Info) OpenAPIModelName() string { + return "io.k8s.apimachinery.pkg.version.Info" +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/doc.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/doc.go new file mode 100644 index 0000000000..5fde5e7427 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/doc.go @@ -0,0 +1,19 @@ +/* +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 watch contains a generic watchable interface, and a fake for +// testing code that uses the watch interface. +package watch diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/filter.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/filter.go new file mode 100644 index 0000000000..a5735a0b47 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/filter.go @@ -0,0 +1,104 @@ +/* +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 watch + +import ( + "sync" +) + +// FilterFunc should take an event, possibly modify it in some way, and return +// the modified event. If the event should be ignored, then return keep=false. +type FilterFunc func(in Event) (out Event, keep bool) + +// Filter passes all events through f before allowing them to pass on. +// Putting a filter on a watch, as an unavoidable side-effect due to the way +// go channels work, effectively causes the watch's event channel to have its +// queue length increased by one. +// +// WARNING: filter has a fatal flaw, in that it can't properly update the +// Type field (Add/Modified/Deleted) to reflect items beginning to pass the +// filter when they previously didn't. +func Filter(w Interface, f FilterFunc) Interface { + fw := &filteredWatch{ + incoming: w, + result: make(chan Event), + f: f, + } + go fw.loop() + return fw +} + +type filteredWatch struct { + incoming Interface + result chan Event + f FilterFunc +} + +// ResultChan returns a channel which will receive filtered events. +func (fw *filteredWatch) ResultChan() <-chan Event { + return fw.result +} + +// Stop stops the upstream watch, which will eventually stop this watch. +func (fw *filteredWatch) Stop() { + fw.incoming.Stop() +} + +// loop waits for new values, filters them, and resends them. +func (fw *filteredWatch) loop() { + defer close(fw.result) + for event := range fw.incoming.ResultChan() { + filtered, keep := fw.f(event) + if keep { + fw.result <- filtered + } + } +} + +// Recorder records all events that are sent from the watch until it is closed. +type Recorder struct { + Interface + + lock sync.Mutex + events []Event +} + +var _ Interface = &Recorder{} + +// NewRecorder wraps an Interface and records any changes sent across it. +func NewRecorder(w Interface) *Recorder { + r := &Recorder{} + r.Interface = Filter(w, r.record) + return r +} + +// record is a FilterFunc and tracks each received event. +func (r *Recorder) record(in Event) (Event, bool) { + r.lock.Lock() + defer r.lock.Unlock() + r.events = append(r.events, in) + return in, true +} + +// Events returns a copy of the events sent across this recorder. +func (r *Recorder) Events() []Event { + r.lock.Lock() + defer r.lock.Unlock() + copied := make([]Event, len(r.events)) + copy(copied, r.events) + return copied +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/filter_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/filter_test.go new file mode 100644 index 0000000000..106e06b46f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/filter_test.go @@ -0,0 +1,114 @@ +/* +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 watch_test + +import ( + "reflect" + "testing" + + . "k8s.io/apimachinery/pkg/watch" +) + +func TestFilter(t *testing.T) { + table := []Event{ + {Type: Added, Object: testType("foo")}, + {Type: Added, Object: testType("bar")}, + {Type: Added, Object: testType("baz")}, + {Type: Added, Object: testType("qux")}, + {Type: Added, Object: testType("zoo")}, + } + + source := NewFake() + filtered := Filter(source, func(e Event) (Event, bool) { + return e, e.Object.(testType)[0] != 'b' + }) + + go func() { + for _, item := range table { + source.Action(item.Type, item.Object) + } + source.Stop() + }() + + var got []string + for { + event, ok := <-filtered.ResultChan() + if !ok { + break + } + got = append(got, string(event.Object.(testType))) + } + + if e, a := []string{"foo", "qux", "zoo"}, got; !reflect.DeepEqual(e, a) { + t.Errorf("got %v, wanted %v", e, a) + } +} + +func TestFilterStop(t *testing.T) { + source := NewFake() + filtered := Filter(source, func(e Event) (Event, bool) { + return e, e.Object.(testType)[0] != 'b' + }) + + go func() { + source.Add(testType("foo")) + filtered.Stop() + }() + + var got []string + for { + event, ok := <-filtered.ResultChan() + if !ok { + break + } + got = append(got, string(event.Object.(testType))) + } + + if e, a := []string{"foo"}, got; !reflect.DeepEqual(e, a) { + t.Errorf("got %v, wanted %v", e, a) + } +} + +func TestRecorder(t *testing.T) { + events := []Event{ + {Type: Added, Object: testType("foo")}, + {Type: Added, Object: testType("bar")}, + {Type: Added, Object: testType("baz")}, + {Type: Added, Object: testType("qux")}, + {Type: Added, Object: testType("zoo")}, + } + + source := NewFake() + go func() { + for _, item := range events { + source.Action(item.Type, item.Object) + } + source.Stop() + }() + + recorder := NewRecorder(source) + for { + _, ok := <-recorder.Interface.ResultChan() + if !ok { + break + } + } + recordedEvents := recorder.Events() + if !reflect.DeepEqual(recordedEvents, events) { + t.Errorf("got %v, expected %v", recordedEvents, events) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/mux.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/mux.go new file mode 100644 index 0000000000..d51f9567e4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/mux.go @@ -0,0 +1,320 @@ +/* +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 watch + +import ( + "fmt" + "sync" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// FullChannelBehavior controls how the Broadcaster reacts if a watcher's watch +// channel is full. +type FullChannelBehavior int + +const ( + WaitIfChannelFull FullChannelBehavior = iota + DropIfChannelFull +) + +// Buffer the incoming queue a little bit even though it should rarely ever accumulate +// anything, just in case a few events are received in such a short window that +// Broadcaster can't move them onto the watchers' queues fast enough. +const incomingQueueLength = 25 + +// Broadcaster distributes event notifications among any number of watchers. Every event +// is delivered to every watcher. +type Broadcaster struct { + watchers map[int64]*broadcasterWatcher + nextWatcher int64 + distributing sync.WaitGroup + + // incomingBlock allows us to ensure we don't race and end up sending events + // to a closed channel following a broadcaster shutdown. + incomingBlock sync.Mutex + incoming chan Event + stopped chan struct{} + + // How large to make watcher's channel. + watchQueueLength int + // If one of the watch channels is full, don't wait for it to become empty. + // Instead just deliver it to the watchers that do have space in their + // channels and move on to the next event. + // It's more fair to do this on a per-watcher basis than to do it on the + // "incoming" channel, which would allow one slow watcher to prevent all + // other watchers from getting new events. + fullChannelBehavior FullChannelBehavior +} + +// NewBroadcaster creates a new Broadcaster. queueLength is the maximum number of events to queue per watcher. +// It is guaranteed that events will be distributed in the order in which they occur, +// but the order in which a single event is distributed among all of the watchers is unspecified. +func NewBroadcaster(queueLength int, fullChannelBehavior FullChannelBehavior) *Broadcaster { + m := &Broadcaster{ + watchers: map[int64]*broadcasterWatcher{}, + incoming: make(chan Event, incomingQueueLength), + stopped: make(chan struct{}), + watchQueueLength: queueLength, + fullChannelBehavior: fullChannelBehavior, + } + m.distributing.Add(1) + go m.loop() + return m +} + +// NewLongQueueBroadcaster functions nearly identically to NewBroadcaster, +// except that the incoming queue is the same size as the outgoing queues +// (specified by queueLength). +func NewLongQueueBroadcaster(queueLength int, fullChannelBehavior FullChannelBehavior) *Broadcaster { + m := &Broadcaster{ + watchers: map[int64]*broadcasterWatcher{}, + incoming: make(chan Event, queueLength), + stopped: make(chan struct{}), + watchQueueLength: queueLength, + fullChannelBehavior: fullChannelBehavior, + } + m.distributing.Add(1) + go m.loop() + return m +} + +const internalRunFunctionMarker = "internal-do-function" + +// a function type we can shoehorn into the queue. +type functionFakeRuntimeObject func() + +func (obj functionFakeRuntimeObject) GetObjectKind() schema.ObjectKind { + return schema.EmptyObjectKind +} +func (obj functionFakeRuntimeObject) DeepCopyObject() runtime.Object { + if obj == nil { + return nil + } + // funcs are immutable. Hence, just return the original func. + return obj +} + +// Execute f, blocking the incoming queue (and waiting for it to drain first). +// The purpose of this terrible hack is so that watchers added after an event +// won't ever see that event, and will always see any event after they are +// added. +func (m *Broadcaster) blockQueue(f func()) { + m.incomingBlock.Lock() + defer m.incomingBlock.Unlock() + select { + case <-m.stopped: + return + default: + } + var wg sync.WaitGroup + wg.Add(1) + m.incoming <- Event{ + Type: internalRunFunctionMarker, + Object: functionFakeRuntimeObject(func() { + defer wg.Done() + f() + }), + } + wg.Wait() +} + +// Watch adds a new watcher to the list and returns an Interface for it. +// Note: new watchers will only receive new events. They won't get an entire history +// of previous events. It will block until the watcher is actually added to the +// broadcaster. +func (m *Broadcaster) Watch() (Interface, error) { + var w *broadcasterWatcher + m.blockQueue(func() { + id := m.nextWatcher + m.nextWatcher++ + w = &broadcasterWatcher{ + result: make(chan Event, m.watchQueueLength), + stopped: make(chan struct{}), + id: id, + m: m, + } + m.watchers[id] = w + }) + if w == nil { + return nil, fmt.Errorf("broadcaster already stopped") + } + return w, nil +} + +// WatchWithPrefix adds a new watcher to the list and returns an Interface for it. It sends +// queuedEvents down the new watch before beginning to send ordinary events from Broadcaster. +// The returned watch will have a queue length that is at least large enough to accommodate +// all of the items in queuedEvents. It will block until the watcher is actually added to +// the broadcaster. +func (m *Broadcaster) WatchWithPrefix(queuedEvents []Event) (Interface, error) { + var w *broadcasterWatcher + m.blockQueue(func() { + id := m.nextWatcher + m.nextWatcher++ + length := m.watchQueueLength + if n := len(queuedEvents) + 1; n > length { + length = n + } + w = &broadcasterWatcher{ + result: make(chan Event, length), + stopped: make(chan struct{}), + id: id, + m: m, + } + m.watchers[id] = w + for _, e := range queuedEvents { + w.result <- e + } + }) + if w == nil { + return nil, fmt.Errorf("broadcaster already stopped") + } + return w, nil +} + +// stopWatching stops the given watcher and removes it from the list. +func (m *Broadcaster) stopWatching(id int64) { + m.blockQueue(func() { + w, ok := m.watchers[id] + if !ok { + // No need to do anything, it's already been removed from the list. + return + } + delete(m.watchers, id) + close(w.result) + }) +} + +// closeAll disconnects all watchers (presumably in response to a Shutdown call). +func (m *Broadcaster) closeAll() { + for _, w := range m.watchers { + close(w.result) + } + // Delete everything from the map, since presence/absence in the map is used + // by stopWatching to avoid double-closing the channel. + m.watchers = map[int64]*broadcasterWatcher{} +} + +// Action distributes the given event among all watchers. +func (m *Broadcaster) Action(action EventType, obj runtime.Object) error { + m.incomingBlock.Lock() + defer m.incomingBlock.Unlock() + select { + case <-m.stopped: + return fmt.Errorf("broadcaster already stopped") + default: + } + + m.incoming <- Event{action, obj} + return nil +} + +// Action distributes the given event among all watchers, or drops it on the floor +// if too many incoming actions are queued up. Returns true if the action was sent, +// false if dropped. +func (m *Broadcaster) ActionOrDrop(action EventType, obj runtime.Object) (bool, error) { + m.incomingBlock.Lock() + defer m.incomingBlock.Unlock() + + // Ensure that if the broadcaster is stopped we do not send events to it. + select { + case <-m.stopped: + return false, fmt.Errorf("broadcaster already stopped") + default: + } + + select { + case m.incoming <- Event{action, obj}: + return true, nil + default: + return false, nil + } +} + +// Shutdown disconnects all watchers (but any queued events will still be distributed). +// You must not call Action or Watch* after calling Shutdown. This call blocks +// until all events have been distributed through the outbound channels. Note +// that since they can be buffered, this means that the watchers might not +// have received the data yet as it can remain sitting in the buffered +// channel. It will block until the broadcaster stop request is actually executed +func (m *Broadcaster) Shutdown() { + m.blockQueue(func() { + close(m.stopped) + close(m.incoming) + }) + m.distributing.Wait() +} + +// loop receives from m.incoming and distributes to all watchers. +func (m *Broadcaster) loop() { + // Deliberately not catching crashes here. Yes, bring down the process if there's a + // bug in watch.Broadcaster. + for event := range m.incoming { + if event.Type == internalRunFunctionMarker { + event.Object.(functionFakeRuntimeObject)() + continue + } + m.distribute(event) + } + m.closeAll() + m.distributing.Done() +} + +// distribute sends event to all watchers. Blocking. +func (m *Broadcaster) distribute(event Event) { + if m.fullChannelBehavior == DropIfChannelFull { + for _, w := range m.watchers { + select { + case w.result <- event: + case <-w.stopped: + default: // Don't block if the event can't be queued. + } + } + } else { + for _, w := range m.watchers { + select { + case w.result <- event: + case <-w.stopped: + } + } + } +} + +// broadcasterWatcher handles a single watcher of a broadcaster +type broadcasterWatcher struct { + result chan Event + stopped chan struct{} + stop sync.Once + id int64 + m *Broadcaster +} + +// ResultChan returns a channel to use for waiting on events. +func (mw *broadcasterWatcher) ResultChan() <-chan Event { + return mw.result +} + +// Stop stops watching and removes mw from its list. +// It will block until the watcher stop request is actually executed +func (mw *broadcasterWatcher) Stop() { + mw.stop.Do(func() { + close(mw.stopped) + mw.m.stopWatching(mw.id) + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/mux_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/mux_test.go new file mode 100644 index 0000000000..9b46ce77e9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/mux_test.go @@ -0,0 +1,291 @@ +/* +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 watch + +import ( + "reflect" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" +) + +type myType struct { + ID string + Value string +} + +func (obj *myType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (obj *myType) DeepCopyObject() runtime.Object { + if obj == nil { + return nil + } + clone := *obj + return &clone +} + +func TestBroadcaster(t *testing.T) { + table := []Event{ + {Type: Added, Object: &myType{"foo", "hello world 1"}}, + {Type: Added, Object: &myType{"bar", "hello world 2"}}, + {Type: Modified, Object: &myType{"foo", "goodbye world 3"}}, + {Type: Deleted, Object: &myType{"bar", "hello world 4"}}, + } + + // The broadcaster we're testing + m := NewBroadcaster(0, WaitIfChannelFull) + + // Add a bunch of watchers + const testWatchers = 2 + wg := sync.WaitGroup{} + wg.Add(testWatchers) + for i := 0; i < testWatchers; i++ { + w, err := m.Watch() + if err != nil { + t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) + } + // Verify that each watcher gets the events in the correct order + go func(watcher int, w Interface) { + tableLine := 0 + for { + event, ok := <-w.ResultChan() + if !ok { + break + } + if e, a := table[tableLine], event; !reflect.DeepEqual(e, a) { + t.Errorf("Watcher %v, line %v: Expected (%v, %#v), got (%v, %#v)", + watcher, tableLine, e.Type, e.Object, a.Type, a.Object) + } else { + t.Logf("Got (%v, %#v)", event.Type, event.Object) + } + tableLine++ + } + wg.Done() + }(i, w) + } + + for i, item := range table { + t.Logf("Sending %v", i) + m.Action(item.Type, item.Object) + } + + m.Shutdown() + + wg.Wait() +} + +func TestBroadcasterWatcherClose(t *testing.T) { + m := NewBroadcaster(0, WaitIfChannelFull) + w, err := m.Watch() + if err != nil { + t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) + } + w2, err := m.Watch() + if err != nil { + t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) + } + w.Stop() + m.Shutdown() + if _, open := <-w.ResultChan(); open { + t.Errorf("Stop didn't work?") + } + if _, open := <-w2.ResultChan(); open { + t.Errorf("Shutdown didn't work?") + } + // Extra stops don't hurt things + w.Stop() + w2.Stop() +} + +func TestBroadcasterWatcherStopDeadlock(t *testing.T) { + done := make(chan bool) + m := NewBroadcaster(0, WaitIfChannelFull) + w, err := m.Watch() + if err != nil { + t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) + } + w2, err := m.Watch() + if err != nil { + t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) + } + go func(w0, w1 Interface) { + // We know Broadcaster is in the distribute loop once one watcher receives + // an event. Stop the other watcher while distribute is trying to + // send to it. + select { + case <-w0.ResultChan(): + w1.Stop() + case <-w1.ResultChan(): + w0.Stop() + } + close(done) + }(w, w2) + m.Action(Added, &myType{}) + select { + case <-time.After(wait.ForeverTestTimeout): + t.Error("timeout: deadlocked") + case <-done: + } + m.Shutdown() +} + +func TestBroadcasterDropIfChannelFull(t *testing.T) { + m := NewBroadcaster(1, DropIfChannelFull) + + event1 := Event{Type: Added, Object: &myType{"foo", "hello world 1"}} + event2 := Event{Type: Added, Object: &myType{"bar", "hello world 2"}} + + // Add a couple watchers + watches := make([]Interface, 2) + var err error + for i := range watches { + watches[i], err = m.Watch() + if err != nil { + t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) + } + } + + // Send a couple events before closing the broadcast channel. + t.Log("Sending event 1") + m.Action(event1.Type, event1.Object) + t.Log("Sending event 2") + m.Action(event2.Type, event2.Object) + m.Shutdown() + + // Pull events from the queue. + wg := sync.WaitGroup{} + wg.Add(len(watches)) + for i := range watches { + // Verify that each watcher only gets the first event because its watch + // queue of length one was full from the first one. + go func(watcher int, w Interface) { + defer wg.Done() + e1, ok := <-w.ResultChan() + if !ok { + t.Errorf("Watcher %v failed to retrieve first event.", watcher) + } + if e, a := event1, e1; !reflect.DeepEqual(e, a) { + t.Errorf("Watcher %v: Expected (%v, %#v), got (%v, %#v)", + watcher, e.Type, e.Object, a.Type, a.Object) + } + t.Logf("Got (%v, %#v)", e1.Type, e1.Object) + e2, ok := <-w.ResultChan() + if ok { + t.Errorf("Watcher %v received second event (%v, %#v) even though it shouldn't have.", + watcher, e2.Type, e2.Object) + } + }(i, watches[i]) + } + wg.Wait() +} + +func BenchmarkBroadCaster(b *testing.B) { + event1 := Event{Type: Added, Object: &myType{"foo", "hello world 1"}} + m := NewBroadcaster(0, WaitIfChannelFull) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + m.Action(event1.Type, event1.Object) + } + }) + b.StopTimer() +} + +func TestBroadcasterWatchAfterShutdown(t *testing.T) { + event1 := Event{Type: Added, Object: &myType{"foo", "hello world 1"}} + event2 := Event{Type: Added, Object: &myType{"bar", "hello world 2"}} + + m := NewBroadcaster(0, WaitIfChannelFull) + m.Shutdown() + + _, err := m.Watch() + assert.EqualError(t, err, "broadcaster already stopped", "Watch should report error id broadcaster is shutdown") + + _, err = m.WatchWithPrefix([]Event{event1, event2}) + assert.EqualError(t, err, "broadcaster already stopped", "WatchWithPrefix should report error id broadcaster is shutdown") +} + +func TestBroadcasterSendEventAfterShutdown(t *testing.T) { + m := NewBroadcaster(1, DropIfChannelFull) + + event := Event{Type: Added, Object: &myType{"foo", "hello world"}} + + // Add a couple watchers + watches := make([]Interface, 2) + for i := range watches { + watches[i], _ = m.Watch() + } + m.Shutdown() + + // Send a couple events after closing the broadcast channel. + t.Log("Sending event") + + err := m.Action(event.Type, event.Object) + assert.EqualError(t, err, "broadcaster already stopped", "ActionOrDrop should report error id broadcaster is shutdown") + + sendOnClosed, err := m.ActionOrDrop(event.Type, event.Object) + assert.False(t, sendOnClosed, "ActionOrDrop should return false if broadcaster is already shutdown") + assert.EqualError(t, err, "broadcaster already stopped", "ActionOrDrop should report error id broadcaster is shutdown") +} + +// Test this since we see usage patterns where the broadcaster and watchers are +// stopped simultaneously leading to races. +func TestBroadcasterShutdownRace(t *testing.T) { + m := NewBroadcaster(1, WaitIfChannelFull) + stopCh := make(chan struct{}) + + // Add a bunch of watchers + const testWatchers = 2 + for i := 0; i < testWatchers; i++ { + i := i + + _, err := m.Watch() + if err != nil { + t.Fatalf("Unable start event watcher: '%v' (will not retry!)", err) + } + // This is how we force the watchers to close down independently of the + // eventbroadcaster, see real usage pattern in startRecordingEvents() + go func() { + <-stopCh + t.Log("Stopping Watchers") + m.stopWatching(int64(i)) + }() + } + + event := Event{Type: Added, Object: &myType{"foo", "hello world"}} + err := m.Action(event.Type, event.Object) + if err != nil { + t.Fatalf("error sending event: %v", err) + } + + // Manually simulate m.Shutdown() but change it to force a race scenario + // 1. Close watcher stopchannel, so watchers are closed independently of the + // eventBroadcaster + // 2. Shutdown the m.incoming slightly Before m.stopped so that the watcher's + // call of Blockqueue can pass the m.stopped check. + m.blockQueue(func() { + close(stopCh) + close(m.incoming) + time.Sleep(1 * time.Millisecond) + close(m.stopped) + }) + m.distributing.Wait() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/streamwatcher.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/streamwatcher.go new file mode 100644 index 0000000000..b422ca9f55 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/streamwatcher.go @@ -0,0 +1,145 @@ +/* +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 watch + +import ( + "fmt" + "io" + "sync" + + "k8s.io/klog/v2" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/net" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +// Decoder allows StreamWatcher to watch any stream for which a Decoder can be written. +type Decoder interface { + // Decode should return the type of event, the decoded object, or an error. + // An error will cause StreamWatcher to call Close(). Decode should block until + // it has data or an error occurs. + Decode() (action EventType, object runtime.Object, err error) + + // Close should close the underlying io.Reader, signalling to the source of + // the stream that it is no longer being watched. Close() must cause any + // outstanding call to Decode() to return with an error of some sort. + Close() +} + +// Reporter hides the details of how an error is turned into a runtime.Object for +// reporting on a watch stream since this package may not import a higher level report. +type Reporter interface { + // AsObject must convert err into a valid runtime.Object for the watch stream. + AsObject(err error) runtime.Object +} + +// StreamWatcher turns any stream for which you can write a Decoder interface +// into a watch.Interface. +type StreamWatcher struct { + logger klog.Logger + sync.Mutex + source Decoder + reporter Reporter + result chan Event + done chan struct{} +} + +// NewStreamWatcher creates a StreamWatcher from the given decoder. +// +// Contextual logging: NewStreamWatcherWithLogger should be used instead of NewStreamWatcher in code which supports contextual logging. +func NewStreamWatcher(d Decoder, r Reporter) *StreamWatcher { + return NewStreamWatcherWithLogger(klog.Background(), d, r) +} + +// NewStreamWatcherWithLogger creates a StreamWatcher from the given decoder and logger. +func NewStreamWatcherWithLogger(logger klog.Logger, d Decoder, r Reporter) *StreamWatcher { + sw := &StreamWatcher{ + logger: logger, + source: d, + reporter: r, + // It's easy for a consumer to add buffering via an extra + // goroutine/channel, but impossible for them to remove it, + // so nonbuffered is better. + result: make(chan Event), + // If the watcher is externally stopped there is no receiver anymore + // and the send operations on the result channel, especially the + // error reporting might block forever. + // Therefore a dedicated stop channel is used to resolve this blocking. + done: make(chan struct{}), + } + go sw.receive() + return sw +} + +// ResultChan implements Interface. +func (sw *StreamWatcher) ResultChan() <-chan Event { + return sw.result +} + +// Stop implements Interface. +func (sw *StreamWatcher) Stop() { + // Call Close() exactly once by locking and setting a flag. + sw.Lock() + defer sw.Unlock() + // closing a closed channel always panics, therefore check before closing + select { + case <-sw.done: + default: + close(sw.done) + sw.source.Close() + } +} + +// receive reads result from the decoder in a loop and sends down the result channel. +func (sw *StreamWatcher) receive() { + defer utilruntime.HandleCrashWithLogger(sw.logger) + defer close(sw.result) + defer sw.Stop() + for { + action, obj, err := sw.source.Decode() + if err != nil { + switch err { + case io.EOF: + // watch closed normally + case io.ErrUnexpectedEOF: + sw.logger.V(1).Info("Unexpected EOF during watch stream event decoding", "err", err) + default: + if net.IsProbableEOF(err) || net.IsTimeout(err) { + sw.logger.V(5).Info("Unable to decode an event from the watch stream", "err", err) + } else { + select { + case <-sw.done: + case sw.result <- Event{ + Type: Error, + Object: sw.reporter.AsObject(fmt.Errorf("unable to decode an event from the watch stream: %v", err)), + }: + } + } + } + return + } + select { + case <-sw.done: + return + case sw.result <- Event{ + Type: action, + Object: obj, + }: + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/streamwatcher_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/streamwatcher_test.go new file mode 100644 index 0000000000..e6f68f6679 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/streamwatcher_test.go @@ -0,0 +1,124 @@ +/* +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 watch_test + +import ( + "fmt" + "io" + "reflect" + "testing" + "time" + + "k8s.io/apimachinery/pkg/runtime" + . "k8s.io/apimachinery/pkg/watch" +) + +type fakeDecoder struct { + items chan Event + err error +} + +func (f fakeDecoder) Decode() (action EventType, object runtime.Object, err error) { + if f.err != nil { + return "", nil, f.err + } + item, open := <-f.items + if !open { + return action, nil, io.EOF + } + return item.Type, item.Object, nil +} + +func (f fakeDecoder) Close() { + if f.items != nil { + close(f.items) + } +} + +type fakeReporter struct { + err error +} + +func (f *fakeReporter) AsObject(err error) runtime.Object { + f.err = err + return runtime.Unstructured(nil) +} + +func TestStreamWatcher(t *testing.T) { + table := []Event{ + {Type: Added, Object: testType("foo")}, + } + + fd := fakeDecoder{items: make(chan Event, 5)} + //nolint:logcheck // Intentionally uses the old API. + sw := NewStreamWatcher(fd, nil) + + for _, item := range table { + fd.items <- item + got, open := <-sw.ResultChan() + if !open { + t.Errorf("unexpected early close") + } + if e, a := item, got; !reflect.DeepEqual(e, a) { + t.Errorf("expected %v, got %v", e, a) + } + } + + sw.Stop() + _, open := <-sw.ResultChan() + if open { + t.Errorf("Unexpected failure to close") + } +} + +func TestStreamWatcherError(t *testing.T) { + fd := fakeDecoder{err: fmt.Errorf("test error")} + fr := &fakeReporter{} + //nolint:logcheck // Intentionally uses the old API. + sw := NewStreamWatcher(fd, fr) + evt, ok := <-sw.ResultChan() + if !ok { + t.Fatalf("unexpected close") + } + if evt.Type != Error || evt.Object != runtime.Unstructured(nil) { + t.Fatalf("unexpected object: %#v", evt) + } + _, ok = <-sw.ResultChan() + if ok { + t.Fatalf("unexpected open channel") + } + + sw.Stop() + _, ok = <-sw.ResultChan() + if ok { + t.Fatalf("unexpected open channel") + } +} + +func TestStreamWatcherRace(t *testing.T) { + fd := fakeDecoder{err: fmt.Errorf("test error")} + fr := &fakeReporter{} + //nolint:logcheck // Intentionally uses the old API. + sw := NewStreamWatcher(fd, fr) + time.Sleep(10 * time.Millisecond) + sw.Stop() + time.Sleep(10 * time.Millisecond) + _, ok := <-sw.ResultChan() + if ok { + t.Fatalf("unexpected pending send") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/watch.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/watch.go new file mode 100644 index 0000000000..251459834b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/watch.go @@ -0,0 +1,377 @@ +/* +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 watch + +import ( + "fmt" + "sync" + + "k8s.io/klog/v2" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" +) + +// Interface can be implemented by anything that knows how to watch and report changes. +type Interface interface { + // Stop tells the producer that the consumer is done watching, so the + // producer should stop sending events and close the result channel. The + // consumer should keep watching for events until the result channel is + // closed. + // + // Because some implementations may create channels when constructed, Stop + // must always be called, even if the consumer has not yet called + // ResultChan(). + // + // Only the consumer should call Stop(), not the producer. If the producer + // errors and needs to stop the watch prematurely, it should instead send + // an error event and close the result channel. + Stop() + + // ResultChan returns a channel which will receive events from the event + // producer. If an error occurs or Stop() is called, the producer must + // close this channel and release any resources used by the watch. + // Closing the result channel tells the consumer that no more events will be + // sent. + ResultChan() <-chan Event +} + +// EventType defines the possible types of events. +type EventType string + +const ( + Added EventType = "ADDED" + Modified EventType = "MODIFIED" + Deleted EventType = "DELETED" + Bookmark EventType = "BOOKMARK" + Error EventType = "ERROR" +) + +var ( + DefaultChanSize int32 = 100 +) + +// Event represents a single event to a watched resource. +// +k8s:deepcopy-gen=true +type Event struct { + Type EventType + + // Object is: + // * If Type is Added or Modified: the new state of the object. + // * If Type is Deleted: the state of the object immediately before deletion. + // * If Type is Bookmark: the object (instance of a type being watched) where + // only ResourceVersion field is set. On successful restart of watch from a + // bookmark resourceVersion, client is guaranteed to not get repeat event + // nor miss any events. + // * If Type is Error: *api.Status is recommended; other types may make sense + // depending on context. + Object runtime.Object +} + +type emptyWatch chan Event + +// NewEmptyWatch returns a watch interface that returns no results and is closed. +// May be used in certain error conditions where no information is available but +// an error is not warranted. +func NewEmptyWatch() Interface { + ch := make(chan Event) + close(ch) + return emptyWatch(ch) +} + +// Stop implements Interface +func (w emptyWatch) Stop() { +} + +// ResultChan implements Interface +func (w emptyWatch) ResultChan() <-chan Event { + return chan Event(w) +} + +// FakeWatcher lets you test anything that consumes a watch.Interface; threadsafe. +type FakeWatcher struct { + logger klog.Logger + result chan Event + stopped bool + sync.Mutex +} + +var _ Interface = &FakeWatcher{} + +// Contextual logging: NewFakeWithOptions and a logger in the FakeOptions should be used instead in code which supports contextual logging. +func NewFake() *FakeWatcher { + return NewFakeWithOptions(FakeOptions{}) +} + +// Contextual logging: NewFakeWithOptions and a logger in the FakeOptions should be used instead in code which supports contextual logging. +func NewFakeWithChanSize(size int, blocking bool) *FakeWatcher { + return NewFakeWithOptions(FakeOptions{ChannelSize: size}) +} + +func NewFakeWithOptions(options FakeOptions) *FakeWatcher { + return &FakeWatcher{ + logger: ptr.Deref(options.Logger, klog.Background()), + result: make(chan Event, options.ChannelSize), + } +} + +type FakeOptions struct { + Logger *klog.Logger + ChannelSize int +} + +// Stop implements Interface.Stop(). +func (f *FakeWatcher) Stop() { + f.Lock() + defer f.Unlock() + if !f.stopped { + f.logger.V(4).Info("Stopping fake watcher") + close(f.result) + f.stopped = true + } +} + +func (f *FakeWatcher) IsStopped() bool { + f.Lock() + defer f.Unlock() + return f.stopped +} + +// Reset prepares the watcher to be reused. +func (f *FakeWatcher) Reset() { + f.Lock() + defer f.Unlock() + f.stopped = false + f.result = make(chan Event) +} + +func (f *FakeWatcher) ResultChan() <-chan Event { + return f.result +} + +// Add sends an add event. +func (f *FakeWatcher) Add(obj runtime.Object) { + f.result <- Event{Added, obj} +} + +// Modify sends a modify event. +func (f *FakeWatcher) Modify(obj runtime.Object) { + f.result <- Event{Modified, obj} +} + +// Delete sends a delete event. +func (f *FakeWatcher) Delete(lastValue runtime.Object) { + f.result <- Event{Deleted, lastValue} +} + +// Error sends an Error event. +func (f *FakeWatcher) Error(errValue runtime.Object) { + f.result <- Event{Error, errValue} +} + +// Action sends an event of the requested type, for table-based testing. +func (f *FakeWatcher) Action(action EventType, obj runtime.Object) { + f.result <- Event{action, obj} +} + +// RaceFreeFakeWatcher lets you test anything that consumes a watch.Interface; threadsafe. +type RaceFreeFakeWatcher struct { + logger klog.Logger + result chan Event + Stopped bool + sync.Mutex +} + +var _ Interface = &RaceFreeFakeWatcher{} + +// Contextual logging: RaceFreeFakeWatcherWithLogger should be used instead of NewRaceFreeFake in code which supports contextual logging. +func NewRaceFreeFake() *RaceFreeFakeWatcher { + return NewRaceFreeFakeWithLogger(klog.Background()) +} + +func NewRaceFreeFakeWithLogger(logger klog.Logger) *RaceFreeFakeWatcher { + return &RaceFreeFakeWatcher{ + logger: logger, + result: make(chan Event, DefaultChanSize), + } +} + +// Stop implements Interface.Stop(). +func (f *RaceFreeFakeWatcher) Stop() { + f.Lock() + defer f.Unlock() + if !f.Stopped { + f.logger.V(4).Info("Stopping fake watcher") + close(f.result) + f.Stopped = true + } +} + +func (f *RaceFreeFakeWatcher) IsStopped() bool { + f.Lock() + defer f.Unlock() + return f.Stopped +} + +// Reset prepares the watcher to be reused. +func (f *RaceFreeFakeWatcher) Reset() { + f.Lock() + defer f.Unlock() + f.Stopped = false + f.result = make(chan Event, DefaultChanSize) +} + +func (f *RaceFreeFakeWatcher) ResultChan() <-chan Event { + f.Lock() + defer f.Unlock() + return f.result +} + +// Add sends an add event. +func (f *RaceFreeFakeWatcher) Add(obj runtime.Object) { + f.Lock() + defer f.Unlock() + if !f.Stopped { + select { + case f.result <- Event{Added, obj}: + return + default: + panic(fmt.Errorf("channel full")) + } + } +} + +// Modify sends a modify event. +func (f *RaceFreeFakeWatcher) Modify(obj runtime.Object) { + f.Lock() + defer f.Unlock() + if !f.Stopped { + select { + case f.result <- Event{Modified, obj}: + return + default: + panic(fmt.Errorf("channel full")) + } + } +} + +// Delete sends a delete event. +func (f *RaceFreeFakeWatcher) Delete(lastValue runtime.Object) { + f.Lock() + defer f.Unlock() + if !f.Stopped { + select { + case f.result <- Event{Deleted, lastValue}: + return + default: + panic(fmt.Errorf("channel full")) + } + } +} + +// Error sends an Error event. +func (f *RaceFreeFakeWatcher) Error(errValue runtime.Object) { + f.Lock() + defer f.Unlock() + if !f.Stopped { + select { + case f.result <- Event{Error, errValue}: + return + default: + panic(fmt.Errorf("channel full")) + } + } +} + +// Action sends an event of the requested type, for table-based testing. +func (f *RaceFreeFakeWatcher) Action(action EventType, obj runtime.Object) { + f.Lock() + defer f.Unlock() + if !f.Stopped { + select { + case f.result <- Event{action, obj}: + return + default: + panic(fmt.Errorf("channel full")) + } + } +} + +// ProxyWatcher lets you wrap your channel in watch Interface. threadsafe. +type ProxyWatcher struct { + result chan Event + stopCh chan struct{} + + mutex sync.Mutex + stopped bool +} + +var _ Interface = &ProxyWatcher{} + +// NewProxyWatcher creates new ProxyWatcher by wrapping a channel +func NewProxyWatcher(ch chan Event) *ProxyWatcher { + return &ProxyWatcher{ + result: ch, + stopCh: make(chan struct{}), + stopped: false, + } +} + +// Stop implements Interface +func (pw *ProxyWatcher) Stop() { + pw.mutex.Lock() + defer pw.mutex.Unlock() + if !pw.stopped { + pw.stopped = true + close(pw.stopCh) + } +} + +// Stopping returns true if Stop() has been called +func (pw *ProxyWatcher) Stopping() bool { + pw.mutex.Lock() + defer pw.mutex.Unlock() + return pw.stopped +} + +// ResultChan implements Interface +func (pw *ProxyWatcher) ResultChan() <-chan Event { + return pw.result +} + +// StopChan returns stop channel +func (pw *ProxyWatcher) StopChan() <-chan struct{} { + return pw.stopCh +} + +// MockWatcher implements watch.Interface with mockable functions. +type MockWatcher struct { + StopFunc func() + ResultChanFunc func() <-chan Event +} + +var _ Interface = &MockWatcher{} + +// Stop calls StopFunc +func (mw MockWatcher) Stop() { + mw.StopFunc() +} + +// ResultChan calls ResultChanFunc +func (mw MockWatcher) ResultChan() <-chan Event { + return mw.ResultChanFunc() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/watch_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/watch_test.go new file mode 100644 index 0000000000..4fb159b0dd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/watch_test.go @@ -0,0 +1,175 @@ +/* +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 watch_test + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + . "k8s.io/apimachinery/pkg/watch" +) + +type testType string + +func (obj testType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (obj testType) DeepCopyObject() runtime.Object { return obj } + +func TestFake(t *testing.T) { + f := NewFake() + + table := []struct { + t EventType + s testType + }{ + {Added, testType("foo")}, + {Modified, testType("qux")}, + {Modified, testType("bar")}, + {Deleted, testType("bar")}, + {Error, testType("error: blah")}, + } + + // Prove that f implements Interface by phrasing this as a function. + consumer := func(w Interface) { + for _, expect := range table { + got, ok := <-w.ResultChan() + if !ok { + t.Fatalf("closed early") + } + if e, a := expect.t, got.Type; e != a { + t.Fatalf("Expected %v, got %v", e, a) + } + if a, ok := got.Object.(testType); !ok || a != expect.s { + t.Fatalf("Expected %v, got %v", expect.s, a) + } + } + _, stillOpen := <-w.ResultChan() + if stillOpen { + t.Fatal("Never stopped") + } + } + + sender := func() { + f.Add(testType("foo")) + f.Action(Modified, testType("qux")) + f.Modify(testType("bar")) + f.Delete(testType("bar")) + f.Error(testType("error: blah")) + f.Stop() + } + + go sender() + consumer(f) +} + +func TestRaceFreeFake(t *testing.T) { + f := NewRaceFreeFake() + + table := []struct { + t EventType + s testType + }{ + {Added, testType("foo")}, + {Modified, testType("qux")}, + {Modified, testType("bar")}, + {Deleted, testType("bar")}, + {Error, testType("error: blah")}, + } + + // Prove that f implements Interface by phrasing this as a function. + consumer := func(w Interface) { + for _, expect := range table { + got, ok := <-w.ResultChan() + if !ok { + t.Fatalf("closed early") + } + if e, a := expect.t, got.Type; e != a { + t.Fatalf("Expected %v, got %v", e, a) + } + if a, ok := got.Object.(testType); !ok || a != expect.s { + t.Fatalf("Expected %v, got %v", expect.s, a) + } + } + _, stillOpen := <-w.ResultChan() + if stillOpen { + t.Fatal("Never stopped") + } + } + + sender := func() { + f.Add(testType("foo")) + f.Action(Modified, testType("qux")) + f.Modify(testType("bar")) + f.Delete(testType("bar")) + f.Error(testType("error: blah")) + f.Stop() + } + + go sender() + consumer(f) +} + +func TestEmpty(t *testing.T) { + w := NewEmptyWatch() + _, ok := <-w.ResultChan() + if ok { + t.Errorf("unexpected result channel result") + } + w.Stop() + _, ok = <-w.ResultChan() + if ok { + t.Errorf("unexpected result channel result") + } +} + +func TestProxyWatcher(t *testing.T) { + events := []Event{ + {Added, testType("foo")}, + {Modified, testType("qux")}, + {Modified, testType("bar")}, + {Deleted, testType("bar")}, + {Error, testType("error: blah")}, + } + + ch := make(chan Event, len(events)) + w := NewProxyWatcher(ch) + + for _, e := range events { + ch <- e + } + + for _, e := range events { + g := <-w.ResultChan() + if !reflect.DeepEqual(e, g) { + t.Errorf("Expected %#v, got %#v", e, g) + continue + } + } + + w.Stop() + + select { + // Closed channel always reads immediately + case <-w.StopChan(): + default: + t.Error("Channel isn't closed") + } + + // Test double close + w.Stop() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/zz_generated.deepcopy.go new file mode 100644 index 0000000000..dd27d4526f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/pkg/watch/zz_generated.deepcopy.go @@ -0,0 +1,41 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package watch + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Event) DeepCopyInto(out *Event) { + *out = *in + if in.Object != nil { + out.Object = in.Object.DeepCopyObject() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Event. +func (in *Event) DeepCopy() *Event { + if in == nil { + return nil + } + out := new(Event) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/LICENSE b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/PATENTS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/PATENTS new file mode 100644 index 0000000000..733099041f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google 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, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/OWNERS b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/OWNERS new file mode 100644 index 0000000000..349bc69d65 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/OWNERS @@ -0,0 +1,6 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - pwittrock +reviewers: + - apelisse diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/fields.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/fields.go new file mode 100644 index 0000000000..5b8514b3fa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/fields.go @@ -0,0 +1,513 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package json is forked from the Go standard library to enable us to find the +// field of a struct that a given JSON key maps to. +package json + +import ( + "bytes" + "fmt" + "reflect" + "sort" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +const ( + patchStrategyTagKey = "patchStrategy" + patchMergeKeyTagKey = "patchMergeKey" +) + +// Finds the patchStrategy and patchMergeKey struct tag fields on a given +// struct field given the struct type and the JSON name of the field. +// It returns field type, a slice of patch strategies, merge key and error. +// TODO: fix the returned errors to be introspectable. +func LookupPatchMetadataForStruct(t reflect.Type, jsonField string) ( + elemType reflect.Type, patchStrategies []string, patchMergeKey string, e error) { + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + e = fmt.Errorf("merging an object in json but data type is not struct, instead is: %s", + t.Kind().String()) + return + } + jf := []byte(jsonField) + // Find the field that the JSON library would use. + var f *field + fields := cachedTypeFields(t) + for i := range fields { + ff := &fields[i] + if bytes.Equal(ff.nameBytes, jf) { + f = ff + break + } + // Do case-insensitive comparison. + if f == nil && ff.equalFold(ff.nameBytes, jf) { + f = ff + } + } + if f != nil { + // Find the reflect.Value of the most preferential struct field. + tjf := t.Field(f.index[0]) + // we must navigate down all the anonymously included structs in the chain + for i := 1; i < len(f.index); i++ { + tjf = tjf.Type.Field(f.index[i]) + } + patchStrategy := tjf.Tag.Get(patchStrategyTagKey) + patchMergeKey = tjf.Tag.Get(patchMergeKeyTagKey) + patchStrategies = strings.Split(patchStrategy, ",") + elemType = tjf.Type + return + } + e = fmt.Errorf("unable to find api field in struct %s for the json field %q", t.Name(), jsonField) + return +} + +// A field represents a single field found in a struct. +type field struct { + name string + nameBytes []byte // []byte(name) + equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent + + tag bool + // index is the sequence of indexes from the containing type fields to this field. + // it is a slice because anonymous structs will need multiple navigation steps to correctly + // resolve the proper fields + index []int + typ reflect.Type + omitEmpty bool + quoted bool +} + +func (f field) String() string { + return fmt.Sprintf("{name: %s, type: %v, tag: %v, index: %v, omitEmpty: %v, quoted: %v}", f.name, f.typ, f.tag, f.index, f.omitEmpty, f.quoted) +} + +func fillField(f field) field { + f.nameBytes = []byte(f.name) + f.equalFold = foldFunc(f.nameBytes) + return f +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from json tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that JSON should recognize for the given type. +// The algorithm is breadth-first search over the set of structs to include - the top struct +// and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + count := map[reflect.Type]int{} + nextCount := map[reflect.Type]int{} + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + tag := sf.Tag.Get("json") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if !isValidTag(name) { + name = "" + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Pointer { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, fillField(field{ + name: name, + tag: tagged, + index: index, + typ: ft, + omitEmpty: opts.Contains("omitempty"), + quoted: opts.Contains("string"), + })) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with JSON tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// JSON tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + default: + if !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + return true +} + +const ( + caseMask = ^byte(0x20) // Mask to ignore case in ASCII. + kelvin = '\u212a' + smallLongEss = '\u017f' +) + +// foldFunc returns one of four different case folding equivalence +// functions, from most general (and slow) to fastest: +// +// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 +// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') +// 3) asciiEqualFold, no special, but includes non-letters (including _) +// 4) simpleLetterEqualFold, no specials, no non-letters. +// +// The letters S and K are special because they map to 3 runes, not just 2: +// * S maps to s and to U+017F 'ſ' Latin small letter long s +// * k maps to K and to U+212A 'K' Kelvin sign +// See http://play.golang.org/p/tTxjOc0OGo +// +// The returned function is specialized for matching against s and +// should only be given s. It's not curried for performance reasons. +func foldFunc(s []byte) func(s, t []byte) bool { + nonLetter := false + special := false // special letter + for _, b := range s { + if b >= utf8.RuneSelf { + return bytes.EqualFold + } + upper := b & caseMask + if upper < 'A' || upper > 'Z' { + nonLetter = true + } else if upper == 'K' || upper == 'S' { + // See above for why these letters are special. + special = true + } + } + if special { + return equalFoldRight + } + if nonLetter { + return asciiEqualFold + } + return simpleLetterEqualFold +} + +// equalFoldRight is a specialization of bytes.EqualFold when s is +// known to be all ASCII (including punctuation), but contains an 's', +// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. +// See comments on foldFunc. +func equalFoldRight(s, t []byte) bool { + for _, sb := range s { + if len(t) == 0 { + return false + } + tb := t[0] + if tb < utf8.RuneSelf { + if sb != tb { + sbUpper := sb & caseMask + if 'A' <= sbUpper && sbUpper <= 'Z' { + if sbUpper != tb&caseMask { + return false + } + } else { + return false + } + } + t = t[1:] + continue + } + // sb is ASCII and t is not. t must be either kelvin + // sign or long s; sb must be s, S, k, or K. + tr, size := utf8.DecodeRune(t) + switch sb { + case 's', 'S': + if tr != smallLongEss { + return false + } + case 'k', 'K': + if tr != kelvin { + return false + } + default: + return false + } + t = t[size:] + + } + if len(t) > 0 { + return false + } + return true +} + +// asciiEqualFold is a specialization of bytes.EqualFold for use when +// s is all ASCII (but may contain non-letters) and contains no +// special-folding letters. +// See comments on foldFunc. +func asciiEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, sb := range s { + tb := t[i] + if sb == tb { + continue + } + if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { + if sb&caseMask != tb&caseMask { + return false + } + } else { + return false + } + } + return true +} + +// simpleLetterEqualFold is a specialization of bytes.EqualFold for +// use when s is all ASCII letters (no underscores, etc) and also +// doesn't contain 'k', 'K', 's', or 'S'. +// See comments on foldFunc. +func simpleLetterEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, b := range s { + if b&caseMask != t[i]&caseMask { + return false + } + } + return true +} + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +// Contains reports whether a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/fields_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/fields_test.go new file mode 100644 index 0000000000..33b78bc43c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/json/fields_test.go @@ -0,0 +1,30 @@ +package json + +import ( + "reflect" + "testing" +) + +func TestLookupPtrToStruct(t *testing.T) { + type Elem struct { + Key string + Value string + } + type Outer struct { + Inner []Elem `json:"inner" patchStrategy:"merge" patchMergeKey:"key"` + } + outer := &Outer{} + elemType, patchStrategies, patchMergeKey, err := LookupPatchMetadataForStruct(reflect.TypeOf(outer), "inner") + if err != nil { + t.Fatal(err) + } + if elemType != reflect.TypeOf([]Elem{}) { + t.Errorf("elemType = %v, want: %v", elemType, reflect.TypeOf([]Elem{})) + } + if !reflect.DeepEqual(patchStrategies, []string{"merge"}) { + t.Errorf("patchStrategies = %v, want: %v", patchStrategies, []string{"merge"}) + } + if patchMergeKey != "key" { + t.Errorf("patchMergeKey = %v, want: %v", patchMergeKey, "key") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go new file mode 100644 index 0000000000..bd26f427e3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go @@ -0,0 +1,28 @@ +package netutil + +import ( + "net/url" + "strings" +) + +// FROM: http://golang.org/src/net/http/client.go +// Given a string of the form "host", "host:port", or "[ipv6::address]:port", +// return true if the string includes a port. +func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } + +// FROM: http://golang.org/src/net/http/transport.go +var portMap = map[string]string{ + "http": "80", + "https": "443", + "socks5": "1080", +} + +// FROM: http://golang.org/src/net/http/transport.go +// canonicalAddr returns url.Host but always with a ":port" suffix +func CanonicalAddr(url *url.URL) string { + addr := url.Host + if !hasPort(addr) { + return addr + ":" + portMap[url.Scheme] + } + return addr +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal.go new file mode 100644 index 0000000000..bb0fa55f21 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal.go @@ -0,0 +1,434 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package reflect is a fork of go's standard library reflection package, which +// allows for deep equal with equality functions defined. +package reflect + +import ( + "fmt" + "reflect" + "strings" +) + +// Equalities is a map from type to a function comparing two values of +// that type. +type Equalities map[reflect.Type]reflect.Value + +// For convenience, panics on errors +func EqualitiesOrDie(funcs ...interface{}) Equalities { + e := Equalities{} + if err := e.AddFuncs(funcs...); err != nil { + panic(err) + } + return e +} + +// AddFuncs is a shortcut for multiple calls to AddFunc. +func (e Equalities) AddFuncs(funcs ...interface{}) error { + for _, f := range funcs { + if err := e.AddFunc(f); err != nil { + return err + } + } + return nil +} + +// AddFunc uses func as an equality function: it must take +// two parameters of the same type, and return a boolean. +func (e Equalities) AddFunc(eqFunc interface{}) error { + fv := reflect.ValueOf(eqFunc) + ft := fv.Type() + if ft.Kind() != reflect.Func { + return fmt.Errorf("expected func, got: %v", ft) + } + if ft.NumIn() != 2 { + return fmt.Errorf("expected two 'in' params, got: %v", ft) + } + if ft.NumOut() != 1 { + return fmt.Errorf("expected one 'out' param, got: %v", ft) + } + if ft.In(0) != ft.In(1) { + return fmt.Errorf("expected arg 1 and 2 to have same type, but got %v", ft) + } + var forReturnType bool + boolType := reflect.TypeOf(forReturnType) + if ft.Out(0) != boolType { + return fmt.Errorf("expected bool return, got: %v", ft) + } + e[ft.In(0)] = fv + return nil +} + +// Below here is forked from go's reflect/deepequal.go + +// During deepValueEqual, must keep track of checks that are +// in progress. The comparison algorithm assumes that all +// checks in progress are true when it reencounters them. +// Visited comparisons are stored in a map indexed by visit. +type visit struct { + a1 uintptr + a2 uintptr + typ reflect.Type +} + +// unexportedTypePanic is thrown when you use this DeepEqual on something that has an +// unexported type. It indicates a programmer error, so should not occur at runtime, +// which is why it's not public and thus impossible to catch. +type unexportedTypePanic []reflect.Type + +func (u unexportedTypePanic) Error() string { return u.String() } +func (u unexportedTypePanic) String() string { + strs := make([]string, len(u)) + for i, t := range u { + strs[i] = fmt.Sprintf("%v", t) + } + return "an unexported field was encountered, nested like this: " + strings.Join(strs, " -> ") +} + +func makeUsefulPanic(v reflect.Value) { + if x := recover(); x != nil { + if u, ok := x.(unexportedTypePanic); ok { + u = append(unexportedTypePanic{v.Type()}, u...) + x = u + } + panic(x) + } +} + +// Tests for deep equality using reflected types. The map argument tracks +// comparisons that have already been seen, which allows short circuiting on +// recursive types. +// equateNilAndEmpty controls whether empty maps/slices are equivalent to nil +func (e Equalities) deepValueEqual(v1, v2 reflect.Value, visited map[visit]bool, equateNilAndEmpty bool, depth int) bool { + defer makeUsefulPanic(v1) + + if !v1.IsValid() || !v2.IsValid() { + return v1.IsValid() == v2.IsValid() + } + if v1.Type() != v2.Type() { + return false + } + if fv, ok := e[v1.Type()]; ok { + return fv.Call([]reflect.Value{v1, v2})[0].Bool() + } + + hard := func(k reflect.Kind) bool { + switch k { + case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: + return true + } + return false + } + + if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) { + addr1 := v1.UnsafeAddr() + addr2 := v2.UnsafeAddr() + if addr1 > addr2 { + // Canonicalize order to reduce number of entries in visited. + addr1, addr2 = addr2, addr1 + } + + // Short circuit if references are identical ... + if addr1 == addr2 { + return true + } + + // ... or already seen + typ := v1.Type() + v := visit{addr1, addr2, typ} + if visited[v] { + return true + } + + // Remember for later. + visited[v] = true + } + + switch v1.Kind() { + case reflect.Array: + // We don't need to check length here because length is part of + // an array's type, which has already been filtered for. + for i := 0; i < v1.Len(); i++ { + if !e.deepValueEqual(v1.Index(i), v2.Index(i), visited, equateNilAndEmpty, depth+1) { + return false + } + } + return true + case reflect.Slice: + if equateNilAndEmpty { + if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) { + return false + } + + if v1.IsNil() || v1.Len() == 0 { + return true + } + } else { + if v1.IsNil() != v2.IsNil() { + return false + } + + // Optimize nil and empty cases + // Two lists that are BOTH nil are equal + // No need to check v2 is nil since v1.IsNil == v2.IsNil from above + if v1.IsNil() { + return true + } + + // Two lists that are both empty and both non nil are equal + if v1.Len() == 0 && v2.Len() == 0 { + return true + } + } + if v1.Len() != v2.Len() { + return false + } + if v1.Pointer() == v2.Pointer() { + return true + } + for i := 0; i < v1.Len(); i++ { + if !e.deepValueEqual(v1.Index(i), v2.Index(i), visited, equateNilAndEmpty, depth+1) { + return false + } + } + return true + case reflect.Interface: + if v1.IsNil() || v2.IsNil() { + return v1.IsNil() == v2.IsNil() + } + return e.deepValueEqual(v1.Elem(), v2.Elem(), visited, equateNilAndEmpty, depth+1) + case reflect.Ptr: + return e.deepValueEqual(v1.Elem(), v2.Elem(), visited, equateNilAndEmpty, depth+1) + case reflect.Struct: + for i, n := 0, v1.NumField(); i < n; i++ { + if !e.deepValueEqual(v1.Field(i), v2.Field(i), visited, equateNilAndEmpty, depth+1) { + return false + } + } + return true + case reflect.Map: + if equateNilAndEmpty { + if (v1.IsNil() || v1.Len() == 0) != (v2.IsNil() || v2.Len() == 0) { + return false + } + if v1.IsNil() || v1.Len() == 0 { + return true + } + } else { + if v1.IsNil() != v2.IsNil() { + return false + } + + // Optimize nil and empty cases + // Two maps that are BOTH nil are equal + // No need to check v2 is nil since v1.IsNil == v2.IsNil from above + if v1.IsNil() { + return true + } + + // Two maps that are both empty and both non nil are equal + if v1.Len() == 0 && v2.Len() == 0 { + return true + } + } + if v1.Len() != v2.Len() { + return false + } + if v1.Pointer() == v2.Pointer() { + return true + } + for _, k := range v1.MapKeys() { + if !e.deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, equateNilAndEmpty, depth+1) { + return false + } + } + return true + case reflect.Func: + if v1.IsNil() && v2.IsNil() { + return true + } + // Can't do better than this: + return false + default: + // Normal equality suffices + if !v1.CanInterface() || !v2.CanInterface() { + panic(unexportedTypePanic{}) + } + return v1.Interface() == v2.Interface() + } +} + +// DeepEqual is like reflect.DeepEqual, but focused on semantic equality +// instead of memory equality. +// +// It will use e's equality functions if it finds types that match. +// +// An empty slice *is* equal to a nil slice for our purposes; same for maps. +// +// Unexported field members cannot be compared and will cause an informative panic; you must add an Equality +// function for these types. +func (e Equalities) DeepEqual(a1, a2 interface{}) bool { + return e.deepEqual(a1, a2, true) +} + +func (e Equalities) DeepEqualWithNilDifferentFromEmpty(a1, a2 interface{}) bool { + return e.deepEqual(a1, a2, false) +} + +func (e Equalities) deepEqual(a1, a2 interface{}, equateNilAndEmpty bool) bool { + if a1 == nil || a2 == nil { + return a1 == a2 + } + v1 := reflect.ValueOf(a1) + v2 := reflect.ValueOf(a2) + if v1.Type() != v2.Type() { + return false + } + return e.deepValueEqual(v1, v2, make(map[visit]bool), equateNilAndEmpty, 0) +} + +func (e Equalities) deepValueDerive(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { + defer makeUsefulPanic(v1) + + if !v1.IsValid() || !v2.IsValid() { + return v1.IsValid() == v2.IsValid() + } + if v1.Type() != v2.Type() { + return false + } + if fv, ok := e[v1.Type()]; ok { + return fv.Call([]reflect.Value{v1, v2})[0].Bool() + } + + hard := func(k reflect.Kind) bool { + switch k { + case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: + return true + } + return false + } + + if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) { + addr1 := v1.UnsafeAddr() + addr2 := v2.UnsafeAddr() + if addr1 > addr2 { + // Canonicalize order to reduce number of entries in visited. + addr1, addr2 = addr2, addr1 + } + + // Short circuit if references are identical ... + if addr1 == addr2 { + return true + } + + // ... or already seen + typ := v1.Type() + v := visit{addr1, addr2, typ} + if visited[v] { + return true + } + + // Remember for later. + visited[v] = true + } + + switch v1.Kind() { + case reflect.Array: + // We don't need to check length here because length is part of + // an array's type, which has already been filtered for. + for i := 0; i < v1.Len(); i++ { + if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) { + return false + } + } + return true + case reflect.Slice: + if v1.IsNil() || v1.Len() == 0 { + return true + } + if v1.Len() > v2.Len() { + return false + } + if v1.Pointer() == v2.Pointer() { + return true + } + for i := 0; i < v1.Len(); i++ { + if !e.deepValueDerive(v1.Index(i), v2.Index(i), visited, depth+1) { + return false + } + } + return true + case reflect.String: + if v1.Len() == 0 { + return true + } + if v1.Len() > v2.Len() { + return false + } + return v1.String() == v2.String() + case reflect.Interface: + if v1.IsNil() { + return true + } + return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1) + case reflect.Pointer: + if v1.IsNil() { + return true + } + return e.deepValueDerive(v1.Elem(), v2.Elem(), visited, depth+1) + case reflect.Struct: + for i, n := 0, v1.NumField(); i < n; i++ { + if !e.deepValueDerive(v1.Field(i), v2.Field(i), visited, depth+1) { + return false + } + } + return true + case reflect.Map: + if v1.IsNil() || v1.Len() == 0 { + return true + } + if v1.Len() > v2.Len() { + return false + } + if v1.Pointer() == v2.Pointer() { + return true + } + for _, k := range v1.MapKeys() { + if !e.deepValueDerive(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) { + return false + } + } + return true + case reflect.Func: + if v1.IsNil() && v2.IsNil() { + return true + } + // Can't do better than this: + return false + default: + // Normal equality suffices + if !v1.CanInterface() || !v2.CanInterface() { + panic(unexportedTypePanic{}) + } + return v1.Interface() == v2.Interface() + } +} + +// DeepDerivative is similar to DeepEqual except that unset fields in a1 are +// ignored (not compared). This allows us to focus on the fields that matter to +// the semantic comparison. +// +// The unset fields include a nil pointer and an empty string. +func (e Equalities) DeepDerivative(a1, a2 interface{}) bool { + if a1 == nil { + return true + } + v1 := reflect.ValueOf(a1) + v2 := reflect.ValueOf(a2) + if v1.Type() != v2.Type() { + return false + } + return e.deepValueDerive(v1, v2, make(map[visit]bool), 0) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal_test.go b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal_test.go new file mode 100644 index 0000000000..6e1e6d0d38 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/apimachinery/third_party/forked/golang/reflect/deep_equal_test.go @@ -0,0 +1,163 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflect + +import ( + "testing" +) + +func TestEqualities(t *testing.T) { + e := Equalities{} + type Bar struct { + X int + } + type Baz struct { + Y Bar + } + type Zap struct { + A []int + B map[string][]int + } + err := e.AddFuncs( + func(a, b int) bool { + return a+1 == b + }, + func(a, b Bar) bool { + return a.X*10 == b.X + }, + ) + if err != nil { + t.Fatalf("Unexpected: %v", err) + } + + type Foo struct { + X int + } + + table := []struct { + a, b interface{} + deepEqual bool // Expected result for DeepEqual + deepEqualWithNilDifferentFromEmpty bool // Expected result for DeepEqualWithNilDifferentFromEmpty + }{ + // Custom equality functions + {1, 2, true, true}, + {2, 1, false, false}, + {"foo", "fo", false, false}, + {"foo", "foo", true, true}, + {"foo", "foobar", false, false}, + {Foo{1}, Foo{2}, true, true}, + {Foo{2}, Foo{1}, false, false}, + {Bar{1}, Bar{10}, true, true}, + {&Bar{1}, &Bar{10}, true, true}, + {Baz{Bar{1}}, Baz{Bar{10}}, true, true}, + // Arrays + {[...]string{}, [...]string{"1", "2", "3"}, false, false}, + {[...]string{"1"}, [...]string{"1", "2", "3"}, false, false}, + {[...]string{"1", "2", "3"}, [...]string{}, false, false}, + {[...]string{"1", "2", "3"}, [...]string{"1", "2", "3"}, true, true}, + // Maps with custom equality + {map[string]int{"foo": 1}, map[string]int{}, false, false}, + {map[string]int{"foo": 1}, map[string]int{"foo": 2}, true, true}, + {map[string]int{"foo": 2}, map[string]int{"foo": 1}, false, false}, + {map[string]int{"foo": 1}, map[string]int{"foo": 2, "bar": 6}, false, false}, + {map[string]int{"foo": 1, "bar": 6}, map[string]int{"foo": 2}, false, false}, + // Nil vs empty (DeepEqual treats as equal, DeepEqualWithNilDifferentFromEmpty treats as different) + {map[string]int{}, map[string]int(nil), true, false}, + {[]string(nil), []string(nil), true, true}, + {[]string{}, []string(nil), true, false}, + {[]string(nil), []string{}, true, false}, + {[]int{}, []int(nil), true, false}, + // Nil vs filled (both functions should return false) + {[]string{"1"}, []string(nil), false, false}, + // Empty vs filled (both functions should return false) + {[]string{}, []string{"1"}, false, false}, + {[]string{}, []string{"1", "2", "3"}, false, false}, + {[]int{}, []int{1, 2, 3}, false, false}, + {map[string]int{}, map[string]int{"foo": 1}, false, false}, + // Filled vs empty (both functions should return false) + {[]string{"1"}, []string{}, false, false}, + {[]string{"1"}, []string{"1", "2", "3"}, false, false}, + {[]string{"1", "2", "3"}, []string{}, false, false}, + {[]int{1, 2, 3}, []int{}, false, false}, + {map[string]int{"foo": 1}, map[string]int{}, false, false}, + // Nested nil/empty (DeepEqual treats as equal, DeepEqualWithNilDifferentFromEmpty treats as different) + {map[string][]int{}, map[string][]int(nil), true, false}, + {map[string][]int{"foo": nil}, map[string][]int{"foo": {}}, true, false}, + {Zap{A: nil, B: map[string][]int{"foo": nil}}, Zap{A: []int{}, B: map[string][]int{"foo": {}}}, true, false}, + } + + for _, item := range table { + if e, a := item.deepEqual, e.DeepEqual(item.a, item.b); e != a { + t.Errorf("DeepEqual: Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) + } + if e, a := item.deepEqualWithNilDifferentFromEmpty, e.DeepEqualWithNilDifferentFromEmpty(item.a, item.b); e != a { + t.Errorf("DeepEqualWithNilDifferentFromEmpty: Expected (%+v == %+v) == %v, but got %v", item.a, item.b, e, a) + } + } +} + +func TestDerivatives(t *testing.T) { + e := Equalities{} + type Bar struct { + X int + } + type Baz struct { + Y Bar + } + err := e.AddFuncs( + func(a, b int) bool { + return a+1 == b + }, + func(a, b Bar) bool { + return a.X*10 == b.X + }, + ) + if err != nil { + t.Fatalf("Unexpected: %v", err) + } + + type Foo struct { + X int + } + + table := []struct { + a, b interface{} + equal bool + }{ + {1, 2, true}, + {2, 1, false}, + {"foo", "fo", false}, + {"foo", "foo", true}, + {"foo", "foobar", false}, + {Foo{1}, Foo{2}, true}, + {Foo{2}, Foo{1}, false}, + {Bar{1}, Bar{10}, true}, + {&Bar{1}, &Bar{10}, true}, + {Baz{Bar{1}}, Baz{Bar{10}}, true}, + {[...]string{}, [...]string{"1", "2", "3"}, false}, + {[...]string{"1"}, [...]string{"1", "2", "3"}, false}, + {[...]string{"1", "2", "3"}, [...]string{}, false}, + {[...]string{"1", "2", "3"}, [...]string{"1", "2", "3"}, true}, + {map[string]int{"foo": 1}, map[string]int{}, false}, + {map[string]int{"foo": 1}, map[string]int{"foo": 2}, true}, + {map[string]int{"foo": 2}, map[string]int{"foo": 1}, false}, + {map[string]int{"foo": 1}, map[string]int{"foo": 2, "bar": 6}, true}, + {map[string]int{"foo": 1, "bar": 6}, map[string]int{"foo": 2}, false}, + {map[string]int{}, map[string]int(nil), true}, + {[]string(nil), []string(nil), true}, + {[]string{}, []string(nil), true}, + {[]string(nil), []string{}, true}, + {[]string{"1"}, []string(nil), false}, + {[]string{}, []string{"1", "2", "3"}, true}, + {[]string{"1"}, []string{"1", "2", "3"}, true}, + {[]string{"1", "2", "3"}, []string{}, false}, + } + + for _, item := range table { + if e, a := item.equal, e.DeepDerivative(item.a, item.b); e != a { + t.Errorf("Expected (%+v ~ %+v) == %v, but got %v", item.a, item.b, e, a) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/.github/PULL_REQUEST_TEMPLATE.md b/hack/tools/code-generator/third_party/k8s.io/code-generator/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..e7e5eb834b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,2 @@ +Sorry, we do not accept changes directly against this repository. Please see +CONTRIBUTING.md for information on where and how to contribute instead. diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/CONTRIBUTING.md b/hack/tools/code-generator/third_party/k8s.io/code-generator/CONTRIBUTING.md new file mode 100644 index 0000000000..76625b7bc9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# Contributing guidelines + +Do not open pull requests directly against this repository, they will be ignored. Instead, please open pull requests against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/). Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes. + +This repository is published from [kubernetes/kubernetes/staging/src/k8s.io/code-generator](https://git.k8s.io/kubernetes/staging/src/k8s.io/code-generator) by the [kubernetes publishing-bot](https://git.k8s.io/publishing-bot). + +Please see [Staging Directory and Publishing](https://git.k8s.io/community/contributors/devel/sig-architecture/staging.md) for more information diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/LICENSE b/hack/tools/code-generator/third_party/k8s.io/code-generator/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/LICENSE @@ -0,0 +1,202 @@ + + 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: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) 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 + + (d) 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/hack/tools/code-generator/third_party/k8s.io/code-generator/OWNERS b/hack/tools/code-generator/third_party/k8s.io/code-generator/OWNERS new file mode 100644 index 0000000000..d16e47e85d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/OWNERS @@ -0,0 +1,16 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - deads2k + - jpbetz + - wojtek-t + - sttts +reviewers: + - deads2k + - wojtek-t + - sttts +labels: + - sig/api-machinery + - area/code-generation +emeritus_approvers: + - lavalamp diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/README.md b/hack/tools/code-generator/third_party/k8s.io/code-generator/README.md new file mode 100644 index 0000000000..aacc4f816f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/README.md @@ -0,0 +1,34 @@ +> ⚠️ **This is an automatically published [staged repository](https://git.k8s.io/kubernetes/staging#external-repository-staging-area) for Kubernetes**. +> Contributions, including issues and pull requests, should be made to the main Kubernetes repository: [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes). +> This repository is read-only for importing, and not used for direct contributions. +> See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. + +# code-generator + +Golang code-generators used to implement [Kubernetes-style API types](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md). + +## Purpose + +These code-generators can be used +- in the context of [CustomResourceDefinition](https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/) to build native, versioned clients, + informers and other helpers +- in the context of [User-provider API Servers](https://github.com/kubernetes/apiserver) to build conversions between internal and versioned types, defaulters, protobuf codecs, + internal and versioned clients and informers. + +## Resources +- The example [sample controller](https://github.com/kubernetes/sample-controller) shows a code example of a controller that uses the clients, listers and informers generated by this library. +- The article [Kubernetes Deep Dive: Code Generation for CustomResources](https://cloud.redhat.com/blog/kubernetes-deep-dive-code-generation-customresources/) gives a step by step instruction on how to use this library. + +## Usage + +The examples above are dated. The current recommended script to use is [kube_codegen.sh](kube_codegen.sh). + +## Compatibility + +HEAD of this repo will match HEAD of k8s.io/apiserver, k8s.io/apimachinery, and k8s.io/client-go. + +## Where does it come from? + +`code-generator` is synced from https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/code-generator. +Code changes are made in that location, merged into `k8s.io/kubernetes` and later synced here. + diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/SECURITY_CONTACTS b/hack/tools/code-generator/third_party/k8s.io/code-generator/SECURITY_CONTACTS new file mode 100644 index 0000000000..f6003980fc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/SECURITY_CONTACTS @@ -0,0 +1,16 @@ +# 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/ + +cheftako +deads2k +lavalamp +sttts diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/args/args.go new file mode 100644 index 0000000000..487b936e6f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/args/args.go @@ -0,0 +1,95 @@ +/* +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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2/types" +) + +// Args is a wrapper for arguments to applyconfiguration-gen. +type Args struct { + OutputDir string // must be a directory path + OutputPkg string // must be a Go import-path + + GoHeaderFile string + + // ExternalApplyConfigurations provides the locations of externally generated + // apply configuration types for types referenced by the go structs provided as input. + // Locations are provided as a comma separated list of .: + // entries. + // + // E.g. if a type references appsv1.Deployment, the location of its apply configuration should + // be provided: + // k8s.io/api/apps/v1.Deployment:k8s.io/client-go/applyconfigurations/apps/v1 + // + // meta/v1 types (TypeMeta and ObjectMeta) are always included and do not need to be passed in. + ExternalApplyConfigurations map[types.Name]string + + OpenAPISchemaFilePath string + + apidefinitions.LintArgs +} + +// New returns default arguments for the generator. +func New() *Args { + return &Args{ + ExternalApplyConfigurations: map[types.Name]string{ + // Always include the applyconfigurations we've generated in client-go. They are sufficient for the vast majority of use cases. + {Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "Condition"}: "k8s.io/client-go/applyconfigurations/meta/v1", + {Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "DeleteOptions"}: "k8s.io/client-go/applyconfigurations/meta/v1", + {Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "LabelSelector"}: "k8s.io/client-go/applyconfigurations/meta/v1", + {Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "LabelSelectorRequirement"}: "k8s.io/client-go/applyconfigurations/meta/v1", + {Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ManagedFieldsEntry"}: "k8s.io/client-go/applyconfigurations/meta/v1", + {Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ObjectMeta"}: "k8s.io/client-go/applyconfigurations/meta/v1", + {Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "OwnerReference"}: "k8s.io/client-go/applyconfigurations/meta/v1", + {Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "TypeMeta"}: "k8s.io/client-go/applyconfigurations/meta/v1", + }, + } +} + +func (args *Args) AddFlags(fs *pflag.FlagSet, inputBase string) { + fs.StringVar(&args.OutputDir, "output-dir", "", + "the base directory under which to generate results") + fs.StringVar(&args.OutputPkg, "output-pkg", args.OutputPkg, + "the Go import-path of the generated results") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + fs.Var(NewExternalApplyConfigurationValue(&args.ExternalApplyConfigurations, nil), "external-applyconfigurations", + "list of comma separated external apply configurations locations in .: form."+ + "For example: k8s.io/api/apps/v1.Deployment:k8s.io/client-go/applyconfigurations/apps/v1") + fs.StringVar(&args.OpenAPISchemaFilePath, "openapi-schema", "", + "path to the openapi schema containing all the types that apply configurations will be generated for") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputDir) == 0 { + return fmt.Errorf("--output-dir must be specified") + } + if len(args.OutputPkg) == 0 { + return fmt.Errorf("--output-pkg must be specified") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/args/externaltypes.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/args/externaltypes.go new file mode 100644 index 0000000000..fd9b609899 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/args/externaltypes.go @@ -0,0 +1,122 @@ +/* +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 args + +import ( + "bytes" + "encoding/csv" + "flag" + "fmt" + "strings" + + "k8s.io/gengo/v2/types" +) + +type externalApplyConfigurationValue struct { + externals *map[types.Name]string +} + +func NewExternalApplyConfigurationValue(externals *map[types.Name]string, def []string) *externalApplyConfigurationValue { + val := new(externalApplyConfigurationValue) + val.externals = externals + if def != nil { + if err := val.set(def); err != nil { + panic(err) + } + } + return val +} + +var _ flag.Value = &externalApplyConfigurationValue{} + +func (s *externalApplyConfigurationValue) set(vs []string) error { + for _, input := range vs { + typ, pkg, err := parseExternalMapping(input) + if err != nil { + return err + } + if _, ok := (*s.externals)[typ]; ok { + return fmt.Errorf("duplicate type found in --external-applyconfigurations: %v", typ) + } + (*s.externals)[typ] = pkg + } + + return nil +} + +func (s *externalApplyConfigurationValue) Set(val string) error { + vs, err := readAsCSV(val) + if err != nil { + return err + } + if err := s.set(vs); err != nil { + return err + } + + return nil +} + +func (s *externalApplyConfigurationValue) Type() string { + return "string" +} + +func (s *externalApplyConfigurationValue) String() string { + var strs []string + for k, v := range *s.externals { + strs = append(strs, fmt.Sprintf("%s.%s:%s", k.Package, k.Name, v)) + } + str, _ := writeAsCSV(strs) + return "[" + str + "]" +} + +func readAsCSV(val string) ([]string, error) { + if val == "" { + return []string{}, nil + } + stringReader := strings.NewReader(val) + csvReader := csv.NewReader(stringReader) + return csvReader.Read() +} + +func writeAsCSV(vals []string) (string, error) { + b := &bytes.Buffer{} + w := csv.NewWriter(b) + err := w.Write(vals) + if err != nil { + return "", err + } + w.Flush() + return strings.TrimSuffix(b.String(), "\n"), nil +} + +func parseExternalMapping(mapping string) (typ types.Name, pkg string, err error) { + parts := strings.Split(mapping, ":") + if len(parts) != 2 { + return types.Name{}, "", fmt.Errorf("expected string of the form .: but got %s", mapping) + } + packageTypeStr := parts[0] + pkg = parts[1] + // need to split on the *last* dot, since k8s.io (and other valid packages) have a dot in it + lastDot := strings.LastIndex(packageTypeStr, ".") + if lastDot == -1 || lastDot == len(packageTypeStr)-1 { + return types.Name{}, "", fmt.Errorf("expected package and type of the form . but got %s", packageTypeStr) + } + structPkg := packageTypeStr[:lastDot] + structType := packageTypeStr[lastDot+1:] + + return types.Name{Package: structPkg, Name: structType}, pkg, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/applyconfiguration.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/applyconfiguration.go new file mode 100644 index 0000000000..47834b4233 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/applyconfiguration.go @@ -0,0 +1,565 @@ +/* +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 generators + +import ( + "fmt" + "io" + "path" + "slices" + "sort" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/code-generator/cmd/client-gen/generators/util" + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" +) + +// applyConfigurationGenerator produces apply configurations for a given GroupVersion and type. +type applyConfigurationGenerator struct { + generator.GoGenerator + // outPkgBase is the base package, under which the "internal" and GV-specific subdirs live + outPkgBase string // must be a Go import-path + localPkg string + groupVersion clientgentypes.GroupVersion + applyConfig applyConfig + imports namer.ImportTracker + refGraph refGraph + openAPIType *string // if absent, extraction function cannot be generated +} + +var _ generator.Generator = &applyConfigurationGenerator{} + +func (g *applyConfigurationGenerator) Filter(_ *generator.Context, t *types.Type) bool { + return t == g.applyConfig.Type +} + +func (g *applyConfigurationGenerator) Namers(*generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.localPkg, g.imports), + "singularKind": namer.NewPublicNamer(0), + } +} + +func (g *applyConfigurationGenerator) Imports(*generator.Context) (imports []string) { + return g.imports.ImportLines() +} + +// TypeParams provides a struct that an apply configuration +// is generated for as well as the apply configuration details +// and types referenced by the struct. +type TypeParams struct { + Struct *types.Type + ApplyConfig applyConfig + Tags util.Tags + APIVersion string + ExtractInto *types.Type + ParserFunc *types.Type + OpenAPIType *string +} + +type memberParams struct { + TypeParams + Member types.Member + MemberType *types.Type + JSONTags JSONTags + ArgType *types.Type // only set for maps and slices + EmbeddedIn *memberParams // parent embedded member, if any +} + +func (g *applyConfigurationGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + klog.V(5).Infof("processing type %v", t) + typeParams := TypeParams{ + Struct: t, + ApplyConfig: g.applyConfig, + Tags: genclientTags(t), + APIVersion: g.groupVersion.ToAPIVersion(), + ExtractInto: extractInto, + ParserFunc: types.Ref(path.Join(g.outPkgBase, "internal"), "Parser"), + OpenAPIType: g.openAPIType, + } + + if err := g.generateStruct(sw, typeParams); err != nil { + return fmt.Errorf("failed to generate apply configuration struct for %s: %w", t.Name, err) + } + + if typeParams.Tags.GenerateClient { + if typeParams.Tags.NonNamespaced { + sw.Do(clientgenTypeConstructorNonNamespaced, typeParams) + } else { + sw.Do(clientgenTypeConstructorNamespaced, typeParams) + } + if typeParams.OpenAPIType != nil { + g.generateClientgenExtract(sw, typeParams) + } + } else { + if hasTypeMetaField(t) { + sw.Do(constructorWithTypeMeta, typeParams) + } else { + sw.Do(constructor, typeParams) + } + } + + if typeParams.Tags.GenerateClient || hasTypeMetaField(t) { + g.generateIsApplyConfiguration(typeParams.ApplyConfig.ApplyConfiguration, sw) + } + g.generateWithFuncs(t, typeParams, sw, nil, &[]string{}) + g.generateGetters(t, typeParams, sw, nil) + return sw.Error() +} + +func hasTypeMetaField(t *types.Type) bool { + for _, member := range t.Members { + if typeMeta.Name == member.Type.Name && member.Embedded { + return true + } + } + return false +} + +func blocklisted(t *types.Type, member types.Member) bool { + if objectMeta.Name == t.Name && member.Name == "ManagedFields" { + return true + } + if objectMeta.Name == t.Name && member.Name == "SelfLink" { + return true + } + // Hide any fields which are en route to deletion. + if strings.HasPrefix(member.Name, "ZZZ_") { + return true + } + return false +} + +func needsGetter(t *types.Type, member types.Member) bool { + // Needed when applying an ApplyConfiguration + return (objectMeta.Name == t.Name && (member.Name == "Name" || member.Name == "Namespace")) || + (typeMeta.Name == t.Name && (member.Name == "Kind" || member.Name == "APIVersion")) +} + +func (g *applyConfigurationGenerator) generateGetters(t *types.Type, typeParams TypeParams, sw *generator.SnippetWriter, embed *memberParams) { + for _, member := range t.Members { + if blocklisted(t, member) { + continue + } + memberType := g.refGraph.applyConfigForType(member.Type) + if g.refGraph.isApplyConfig(member.Type) { + memberType = &types.Type{Kind: types.Pointer, Elem: memberType} + } + if jsonTags, ok := lookupJSONTags(member); ok { + memberParams := memberParams{ + TypeParams: typeParams, + Member: member, + MemberType: memberType, + JSONTags: jsonTags, + EmbeddedIn: embed, + } + if memberParams.Member.Embedded { + g.generateGetters(member.Type, typeParams, sw, &memberParams) + continue + } + + if needsGetter(t, member) { + g.generateMemberGetter(sw, memberParams) + } + } + } +} + +func (g *applyConfigurationGenerator) generateWithFuncs(t *types.Type, typeParams TypeParams, sw *generator.SnippetWriter, embed *memberParams, + generated *[]string) { + for _, member := range t.Members { + if blocklisted(t, member) { + continue + } + memberType := g.refGraph.applyConfigForType(member.Type) + if g.refGraph.isApplyConfig(member.Type) { + memberType = &types.Type{Kind: types.Pointer, Elem: memberType} + } + if jsonTags, ok := lookupJSONTags(member); ok { + if slices.Contains(*generated, member.Name) { + klog.V(5).Infof("With%s already generated on %s, skipping\n", member.Name, t.Name) + continue + } + *generated = append(*generated, member.Name) + memberParams := memberParams{ + TypeParams: typeParams, + Member: member, + MemberType: memberType, + JSONTags: jsonTags, + EmbeddedIn: embed, + } + if memberParams.Member.Embedded { + g.generateWithFuncs(member.Type, typeParams, sw, &memberParams, generated) + if !jsonTags.inline { + // non-inlined embeds are nillable and need a "ensure exists" utility function + sw.Do(ensureEmbedExists, memberParams) + } + continue + } + + // For slices where the items are generated apply configuration types, accept varargs of + // pointers of the type as "with" function arguments so the "with" function can be used like so: + // WithFoos(Foo().WithName("x"), Foo().WithName("y")) + if t := deref(member.Type); t.Kind == types.Slice && g.refGraph.isApplyConfig(t.Elem) { + memberParams.ArgType = &types.Type{Kind: types.Pointer, Elem: memberType.Elem} + g.generateMemberWithForSlice(sw, member, memberParams) + continue + } + // Note: There are no maps where the values are generated apply configurations (because + // associative lists are used instead). So if a type like this is ever introduced, the + // default "with" function generator will produce a working (but not entirely convenient "with" function) + // that would be used like so: + // WithMap(map[string]FooApplyConfiguration{*Foo().WithName("x")}) + + switch memberParams.Member.Type.Kind { + case types.Slice: + memberParams.ArgType = memberType.Elem + g.generateMemberWithForSlice(sw, member, memberParams) + case types.Map: + g.generateMemberWithForMap(sw, memberParams) + default: + g.generateMemberWith(sw, memberParams) + } + } + } +} + +func (g *applyConfigurationGenerator) generateStruct(sw *generator.SnippetWriter, typeParams TypeParams) error { + sw.Do("// $.ApplyConfig.ApplyConfiguration|public$ represents a declarative configuration of the $.ApplyConfig.Type|public$ type for use\n", typeParams) + sw.Do("// with apply.\n", typeParams) + structComments := commentsWithoutMarkers(append(typeParams.Struct.SecondClosestCommentLines, typeParams.Struct.CommentLines...)) + if len(structComments) > 0 { + sw.Do("//\n", typeParams) + if err := sw.Append(strings.NewReader(structComments)); err != nil { + return fmt.Errorf("failed to write comments for struct %s: %w", typeParams.Struct.Name, err) + } + } + sw.Do("type $.ApplyConfig.ApplyConfiguration|public$ struct {\n", typeParams) + for _, structMember := range typeParams.Struct.Members { + if blocklisted(typeParams.Struct, structMember) { + continue + } + if structMemberTags, ok := lookupJSONTags(structMember); ok { + if !structMemberTags.inline { + structMemberTags.omitempty = true + } + params := memberParams{ + TypeParams: typeParams, + Member: structMember, + MemberType: g.refGraph.applyConfigForType(structMember.Type), + JSONTags: structMemberTags, + } + + if err := sw.Append(strings.NewReader(commentsWithoutMarkers(structMember.CommentLines))); err != nil { + return fmt.Errorf("failed to write comments for member %s: %w", structMember.Name, err) + } + if structMember.Embedded { + if structMemberTags.inline { + sw.Do("$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params) + } else { + sw.Do("*$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params) + } + } else if isNillable(structMember.Type) { + sw.Do("$.Member.Name$ $.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params) + } else { + sw.Do("$.Member.Name$ *$.MemberType|raw$ `json:\"$.JSONTags$\"`\n", params) + } + } + } + sw.Do("}\n", typeParams) + + return nil +} + +func (g *applyConfigurationGenerator) generateIsApplyConfiguration(t *types.Type, sw *generator.SnippetWriter) { + sw.Do(` +func (b $.|public$) IsApplyConfiguration() {} +`, t) +} + +func deref(t *types.Type) *types.Type { + for t.Kind == types.Pointer { + t = t.Elem + } + return t +} + +func isNillable(t *types.Type) bool { + return t.Kind == types.Slice || t.Kind == types.Map +} + +// commentsWithoutMarkers removes comment lines that start with '+' as they are codegen markers +// and ensures all comments have the proper // prefix +func commentsWithoutMarkers(comments []string) string { + b := strings.Builder{} + for _, comment := range comments { + trimmed := strings.TrimSpace(comment) + if strings.HasPrefix(trimmed, "+") { + continue + } + + b.WriteString("// " + trimmed + "\n") + } + return b.String() +} + +func (g *applyConfigurationGenerator) generateMemberWith(sw *generator.SnippetWriter, memberParams memberParams) { + sw.Do("// With$.Member.Name$ sets the $.Member.Name$ field in the declarative configuration to the given value\n", memberParams) + sw.Do("// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n", memberParams) + sw.Do("// If called multiple times, the $.Member.Name$ field is set to the value of the last call.\n", memberParams) + sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(value $.MemberType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams) + g.ensureEmbedExistsIfApplicable(sw, memberParams) + if g.refGraph.isApplyConfig(memberParams.Member.Type) || isNillable(memberParams.Member.Type) { + sw.Do("b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$ = value\n", memberParams) + } else { + sw.Do("b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$ = &value\n", memberParams) + } + sw.Do(" return b\n", memberParams) + sw.Do("}\n", memberParams) +} + +func (g *applyConfigurationGenerator) generateMemberGetter(sw *generator.SnippetWriter, memberParams memberParams) { + sw.Do("// Get$.Member.Name$ retrieves the value of the $.Member.Name$ field in the declarative configuration.\n", memberParams) + if g.refGraph.isApplyConfig(memberParams.Member.Type) || isNillable(memberParams.Member.Type) { + sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) Get$.Member.Name$() $.MemberType|raw$ {\n", memberParams) + } else { + sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) Get$.Member.Name$() *$.MemberType|raw$ {\n", memberParams) + } + g.ensureEmbedExistsIfApplicable(sw, memberParams) + sw.Do(" return b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$\n", memberParams) + sw.Do("}\n", memberParams) +} + +func (g *applyConfigurationGenerator) generateMemberWithForSlice(sw *generator.SnippetWriter, member types.Member, memberParams memberParams) { + memberIsPointerToSlice := member.Type.Kind == types.Pointer + if memberIsPointerToSlice { + sw.Do(ensureNonEmbedSliceExists, memberParams) + } + + sw.Do("// With$.Member.Name$ adds the given value to the $.Member.Name$ field in the declarative configuration\n", memberParams) + sw.Do("// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n", memberParams) + sw.Do("// If called multiple times, values provided by each call will be appended to the $.Member.Name$ field.\n", memberParams) + sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(values ...$.ArgType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams) + g.ensureEmbedExistsIfApplicable(sw, memberParams) + + if memberIsPointerToSlice { + sw.Do("b.ensure$.MemberType.Elem|public$Exists()\n", memberParams) + } + + sw.Do(" for i := range values {\n", memberParams) + if memberParams.ArgType.Kind == types.Pointer { + sw.Do("if values[i] == nil {\n", memberParams) + sw.Do(" panic(\"nil value passed to With$.Member.Name$\")\n", memberParams) + sw.Do("}\n", memberParams) + + if memberIsPointerToSlice { + sw.Do("*b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$ = append(*b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$, *values[i])\n", memberParams) + } else { + sw.Do("b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$ = append(b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$, *values[i])\n", memberParams) + } + } else { + if memberIsPointerToSlice { + sw.Do("*b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$ = append(*b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$, values[i])\n", memberParams) + } else { + sw.Do("b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$ = append(b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$, values[i])\n", memberParams) + } + } + sw.Do(" }\n", memberParams) + sw.Do(" return b\n", memberParams) + sw.Do("}\n", memberParams) +} + +func (g *applyConfigurationGenerator) generateMemberWithForMap(sw *generator.SnippetWriter, memberParams memberParams) { + sw.Do("// With$.Member.Name$ puts the entries into the $.Member.Name$ field in the declarative configuration\n", memberParams) + sw.Do("// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n", memberParams) + sw.Do("// If called multiple times, the entries provided by each call will be put on the $.Member.Name$ field,\n", memberParams) + sw.Do("// overwriting an existing map entries in $.Member.Name$ field with the same key.\n", memberParams) + sw.Do("func (b *$.ApplyConfig.ApplyConfiguration|public$) With$.Member.Name$(entries $.MemberType|raw$) *$.ApplyConfig.ApplyConfiguration|public$ {\n", memberParams) + g.ensureEmbedExistsIfApplicable(sw, memberParams) + sw.Do(" if b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$ == nil && len(entries) > 0 {\n", memberParams) + sw.Do(" b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$ = make($.MemberType|raw$, len(entries))\n", memberParams) + sw.Do(" }\n", memberParams) + sw.Do(" for k, v := range entries {\n", memberParams) + sw.Do(" b$if ne .EmbeddedIn nil$$if ne .EmbeddedIn.MemberType.Name.Name \"\"$.$.EmbeddedIn.MemberType.Name.Name$$else if ne .EmbeddedIn.MemberType.Elem nil$.$.EmbeddedIn.MemberType.Elem.Name.Name$$end$$end$.$.Member.Name$[k] = v\n", memberParams) + sw.Do(" }\n", memberParams) + sw.Do(" return b\n", memberParams) + sw.Do("}\n", memberParams) +} + +func (g *applyConfigurationGenerator) ensureEmbedExistsIfApplicable(sw *generator.SnippetWriter, memberParams memberParams) { + // Embedded types that are not inlined must be nillable so they are not included in the apply configuration + // when all their fields are omitted. + if memberParams.EmbeddedIn != nil && !memberParams.EmbeddedIn.JSONTags.inline { + sw.Do("b.ensure$.MemberType.Elem|public$Exists()\n", memberParams.EmbeddedIn) + } +} + +var ensureEmbedExists = ` +func (b *$.ApplyConfig.ApplyConfiguration|public$) ensure$.MemberType.Elem|public$Exists() { + if b.$.MemberType.Elem|public$ == nil { + b.$.MemberType.Elem|public$ = &$.MemberType.Elem|raw${} + } +} +` + +var ensureNonEmbedSliceExists = ` +func (b *$.ApplyConfig.ApplyConfiguration|public$) ensure$.MemberType.Elem|public$Exists() { + if b.$.Member.Name$ == nil { + b.$.Member.Name$ = &[]$.MemberType.Elem|raw${} + } +} +` + +var clientgenTypeConstructorNamespaced = ` +// $.ApplyConfig.Type|public$ constructs a declarative configuration of the $.ApplyConfig.Type|public$ type for use with +// apply. +func $.ApplyConfig.Type|public$(name, namespace string) *$.ApplyConfig.ApplyConfiguration|public$ { + b := &$.ApplyConfig.ApplyConfiguration|public${} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("$.ApplyConfig.Type|singularKind$") + b.WithAPIVersion("$.APIVersion$") + return b +} +` + +var clientgenTypeConstructorNonNamespaced = ` +// $.ApplyConfig.Type|public$ constructs a declarative configuration of the $.ApplyConfig.Type|public$ type for use with +// apply. +func $.ApplyConfig.Type|public$(name string) *$.ApplyConfig.ApplyConfiguration|public$ { + b := &$.ApplyConfig.ApplyConfiguration|public${} + b.WithName(name) + b.WithKind("$.ApplyConfig.Type|singularKind$") + b.WithAPIVersion("$.APIVersion$") + return b +} +` + +var constructorWithTypeMeta = ` +// $.ApplyConfig.ApplyConfiguration|public$ constructs a declarative configuration of the $.ApplyConfig.Type|public$ type for use with +// apply. +func $.ApplyConfig.Type|public$() *$.ApplyConfig.ApplyConfiguration|public$ { + b := &$.ApplyConfig.ApplyConfiguration|public${} + b.WithKind("$.ApplyConfig.Type|singularKind$") + b.WithAPIVersion("$.APIVersion$") + return b +} +` + +var titler = cases.Title(language.Und) + +var constructor = ` +// $.ApplyConfig.ApplyConfiguration|public$ constructs a declarative configuration of the $.ApplyConfig.Type|public$ type for use with +// apply. +func $.ApplyConfig.Type|public$() *$.ApplyConfig.ApplyConfiguration|public$ { + return &$.ApplyConfig.ApplyConfiguration|public${} +} +` + +func (g *applyConfigurationGenerator) generateClientgenExtract(sw *generator.SnippetWriter, typeParams TypeParams) { + subresources := g.collectSubresources(typeParams) + + sw.Do(` +// Extract$.ApplyConfig.Type|public$From extracts the applied configuration owned by fieldManager from +// $.Struct|private$ for the specified subresource. Pass an empty string for subresource to extract +// the main resource. Common subresources include "status", "scale", etc. +// $.Struct|private$ must be a unmodified $.Struct|public$ API object that was retrieved from the Kubernetes API. +// Extract$.ApplyConfig.Type|public$From provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func Extract$.ApplyConfig.Type|public$From($.Struct|private$ *$.Struct|raw$, fieldManager string, subresource string) (*$.ApplyConfig.ApplyConfiguration|public$, error) { + b := &$.ApplyConfig.ApplyConfiguration|public${} + err := $.ExtractInto|raw$($.Struct|private$, $.ParserFunc|raw$().Type("$.OpenAPIType$"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName($.Struct|private$.Name) +`, typeParams) + if !typeParams.Tags.NonNamespaced { + sw.Do(" b.WithNamespace($.Struct|private$.Namespace)\n", typeParams) + } + sw.Do(` + b.WithKind("$.ApplyConfig.Type|singularKind$") + b.WithAPIVersion("$.APIVersion$") + return b, nil +} +`, typeParams) + + sw.Do(` +// Extract$.ApplyConfig.Type|public$ extracts the applied configuration owned by fieldManager from +// $.Struct|private$. If no managedFields are found in $.Struct|private$ for fieldManager, a +// $.ApplyConfig.ApplyConfiguration|public$ is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// $.Struct|private$ must be a unmodified $.Struct|public$ API object that was retrieved from the Kubernetes API. +// Extract$.ApplyConfig.Type|public$ provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +func Extract$.ApplyConfig.Type|public$($.Struct|private$ *$.Struct|raw$, fieldManager string) (*$.ApplyConfig.ApplyConfiguration|public$, error) { + return Extract$.ApplyConfig.Type|public$From($.Struct|private$, fieldManager, "") +} +`, typeParams) + + for _, subresource := range subresources { + sw.Do(` +// Extract$.ApplyConfig.Type|public$$.SubresourceName$ extracts the applied configuration owned by fieldManager from +// $.Struct|private$ for the $.Subresource$ subresource. +func Extract$.ApplyConfig.Type|public$$.SubresourceName$($.Struct|private$ *$.Struct|raw$, fieldManager string) (*$.ApplyConfig.ApplyConfiguration|public$, error) { + return Extract$.ApplyConfig.Type|public$From($.Struct|private$, fieldManager, "$.Subresource$") +} +`, map[string]interface{}{ + "ApplyConfig": typeParams.ApplyConfig, + "Struct": typeParams.Struct, + "SubresourceName": titler.String(subresource), + "Subresource": subresource, + }) + } +} + +func (g *applyConfigurationGenerator) collectSubresources(typeParams TypeParams) []string { + subresources := sets.New[string]() + if !typeParams.Tags.NoStatus { + // Do we even have a status? + for _, member := range typeParams.Struct.Members { + if member.Name == "Status" { + subresources.Insert("status") + break + } + } + } + + for _, ext := range typeParams.Tags.Extensions { + if ext.SubResourcePath != "" { + subresources.Insert(ext.SubResourcePath) + } + } + + sorted := subresources.UnsortedList() + sort.Strings(sorted) + return sorted +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/internal.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/internal.go new file mode 100644 index 0000000000..b17d2a48ac --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/internal.go @@ -0,0 +1,98 @@ +/* +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 generators + +import ( + "io" + + yaml "go.yaml.in/yaml/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/kube-openapi/pkg/schemaconv" +) + +// utilGenerator generates the ForKind() utility function. +type internalGenerator struct { + generator.GoGenerator + outputPackage string + imports namer.ImportTracker + typeModels *typeModels + filtered bool +} + +var _ generator.Generator = &internalGenerator{} + +func (g *internalGenerator) Filter(*generator.Context, *types.Type) bool { + // generate file exactly once + if !g.filtered { + g.filtered = true + return true + } + return false +} + +func (g *internalGenerator) Namers(*generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + "singularKind": namer.NewPublicNamer(0), + } +} + +func (g *internalGenerator) Imports(*generator.Context) (imports []string) { + return g.imports.ImportLines() +} + +func (g *internalGenerator) GenerateType(c *generator.Context, _ *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "{{", "}}") + + schema, err := schemaconv.ToSchema(g.typeModels.models) + if err != nil { + return err + } + schemaYAML, err := yaml.Marshal(schema) + if err != nil { + return err + } + sw.Do(schemaBlock, map[string]interface{}{ + "schemaYAML": string(schemaYAML), + "smdParser": smdParser, + "smdNewParser": smdNewParser, + "fmtSprintf": fmtSprintf, + "syncOnce": syncOnce, + "yamlObject": yamlObject, + }) + + return sw.Error() +} + +var schemaBlock = ` +func Parser() *{{.smdParser|raw}} { + parserOnce.Do(func() { + var err error + parser, err = {{.smdNewParser|raw}}(schemaYAML) + if err != nil { + panic({{.fmtSprintf|raw}}("Failed to parse schema: %v", err)) + } + }) + return parser +} + +var parserOnce {{.syncOnce|raw}} +var parser *{{.smdParser|raw}} +var schemaYAML = {{.yamlObject|raw}}(` + "`{{.schemaYAML}}`" + `) +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/jsontagutil.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/jsontagutil.go new file mode 100644 index 0000000000..e400e0c750 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/jsontagutil.go @@ -0,0 +1,97 @@ +/* +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 generators + +import ( + "reflect" + "strings" + + "k8s.io/gengo/v2/types" +) + +// TODO: This implements the same functionality as https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L236 +// but is based on the highly efficient approach from https://golang.org/src/encoding/json/encode.go + +// JSONTags represents a go json field tag. +type JSONTags struct { + name string + omit bool + inline bool + omitempty bool +} + +func (t JSONTags) String() string { + var tag string + if !t.inline { + tag += t.name + } + if t.omitempty { + tag += ",omitempty" + } + return tag +} + +func lookupJSONTags(m types.Member) (JSONTags, bool) { + tag, exists := reflect.StructTag(m.Tags).Lookup("json") + if !exists || tag == "-" { + return JSONTags{}, false + } + name, opts := parseTag(tag) + inline := m.Embedded && name == "" + if name == "" { + name = m.Name + } + return JSONTags{ + name: name, + omit: false, + inline: inline, + omitempty: opts.Contains("omitempty"), + }, true +} + +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, "" +} + +// Contains reports whether a comma-separated listAlias of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/openapi.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/openapi.go new file mode 100644 index 0000000000..c67325b2f8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/openapi.go @@ -0,0 +1,196 @@ +/* +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 generators + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + openapiv2 "github.com/google/gnostic-models/openapiv2" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/gengo/v2/types" + "k8s.io/kube-openapi/pkg/util" + utilproto "k8s.io/kube-openapi/pkg/util/proto" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +type typeModels struct { + models utilproto.Models + gvkToOpenAPIType map[clientgentypes.GroupVersionKind]string +} + +func newTypeModels(openAPISchemaFilePath string, pkgTypes map[string]*types.Package) (*typeModels, error) { + if len(openAPISchemaFilePath) == 0 { + return emptyModels, nil // No Extract() functions will be generated. + } + + rawOpenAPISchema, err := os.ReadFile(openAPISchemaFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read openapi-schema file: %w", err) + } + + // Read in the provided openAPI schema. + openAPISchema := &spec.Swagger{} + err = json.Unmarshal(rawOpenAPISchema, openAPISchema) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal typeModels JSON: %w", err) + } + + // Build a mapping from openAPI type name to GVK. + // Find the root types needed by by client-go for apply. + gvkToOpenAPIType := map[clientgentypes.GroupVersionKind]string{} + rootDefs := map[string]spec.Schema{} + for _, p := range pkgTypes { + gv, err := groupVersion(p) + if err != nil { + return nil, fmt.Errorf("failed to parse comments of package %s: %w", p.Name, err) + } + for _, t := range p.Types { + tags := genclientTags(t) + hasApply := tags.HasVerb("apply") || tags.HasVerb("applyStatus") + if tags.GenerateClient && hasApply { + openAPIType := util.ToRESTFriendlyName(typeName(t)) + gvk := gv.WithKind(clientgentypes.Kind(t.Name.Name)) + rootDefs[openAPIType] = openAPISchema.Definitions[openAPIType] + gvkToOpenAPIType[gvk] = openAPIType + } + } + } + + // Trim the schema down to just the types needed by client-go for apply. + requiredDefs := make(map[string]spec.Schema) + for name, def := range rootDefs { + requiredDefs[name] = def + findReferenced(&def, openAPISchema.Definitions, requiredDefs) + } + openAPISchema.Definitions = requiredDefs + + // Convert the openAPI schema to the models format and validate it. + models, err := toValidatedModels(openAPISchema) + if err != nil { + return nil, err + } + return &typeModels{models: models, gvkToOpenAPIType: gvkToOpenAPIType}, nil +} + +var emptyModels = &typeModels{ + models: &utilproto.Definitions{}, + gvkToOpenAPIType: map[clientgentypes.GroupVersionKind]string{}, +} + +func toValidatedModels(openAPISchema *spec.Swagger) (utilproto.Models, error) { + // openapi_v2.ParseDocument only accepts a []byte of the JSON or YAML file to be parsed. + // so we do an inefficient marshal back to json and then read it back in as yaml + // but get the benefit of running the models through utilproto.NewOpenAPIData to + // validate all the references between types + rawMinimalOpenAPISchema, err := json.Marshal(openAPISchema) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal openAPI as JSON: %w", err) + } + + document, err := openapiv2.ParseDocument(rawMinimalOpenAPISchema) + if err != nil { + return nil, fmt.Errorf("failed to parse OpenAPI document for file: %w", err) + } + // Construct the models and validate all references are valid. + models, err := utilproto.NewOpenAPIData(document) + if err != nil { + return nil, fmt.Errorf("failed to create OpenAPI models for file: %w", err) + } + return models, nil +} + +// findReferenced recursively finds all schemas referenced from the given def. +// toValidatedModels makes sure no references get missed. +func findReferenced(def *spec.Schema, allSchemas, referencedOut map[string]spec.Schema) { + // follow $ref, if any + refPtr := def.Ref.GetPointer() + if refPtr != nil && !refPtr.IsEmpty() { + name := refPtr.String() + if !strings.HasPrefix(name, "/definitions/") { + return + } + name = strings.TrimPrefix(name, "/definitions/") + schema, ok := allSchemas[name] + if !ok { + panic(fmt.Sprintf("allSchemas schema is missing referenced type: %s", name)) + } + if _, ok := referencedOut[name]; !ok { + referencedOut[name] = schema + findReferenced(&schema, allSchemas, referencedOut) + } + } + + // follow any nested schemas + if def.Items != nil { + if def.Items.Schema != nil { + findReferenced(def.Items.Schema, allSchemas, referencedOut) + } + for _, item := range def.Items.Schemas { + findReferenced(&item, allSchemas, referencedOut) + } + } + if def.AllOf != nil { + for _, s := range def.AllOf { + findReferenced(&s, allSchemas, referencedOut) + } + } + if def.AnyOf != nil { + for _, s := range def.AnyOf { + findReferenced(&s, allSchemas, referencedOut) + } + } + if def.OneOf != nil { + for _, s := range def.OneOf { + findReferenced(&s, allSchemas, referencedOut) + } + } + if def.Not != nil { + findReferenced(def.Not, allSchemas, referencedOut) + } + if def.Properties != nil { + for _, prop := range def.Properties { + findReferenced(&prop, allSchemas, referencedOut) + } + } + if def.AdditionalProperties != nil && def.AdditionalProperties.Schema != nil { + findReferenced(def.AdditionalProperties.Schema, allSchemas, referencedOut) + } + if def.PatternProperties != nil { + for _, s := range def.PatternProperties { + findReferenced(&s, allSchemas, referencedOut) + } + } + if def.Dependencies != nil { + for _, d := range def.Dependencies { + if d.Schema != nil { + findReferenced(d.Schema, allSchemas, referencedOut) + } + } + } + if def.AdditionalItems != nil && def.AdditionalItems.Schema != nil { + findReferenced(def.AdditionalItems.Schema, allSchemas, referencedOut) + } + if def.Definitions != nil { + for _, s := range def.Definitions { + findReferenced(&s, allSchemas, referencedOut) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/refgraph.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/refgraph.go new file mode 100644 index 0000000000..8b46755298 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/refgraph.go @@ -0,0 +1,175 @@ +/* +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 generators + +import ( + "k8s.io/gengo/v2/types" + + "k8s.io/code-generator/cmd/client-gen/generators/util" +) + +// refGraph maps existing types to the package the corresponding applyConfig types will be generated in +// so that references between apply configurations can be correctly generated. +type refGraph map[types.Name]string + +// refGraphForReachableTypes returns a refGraph that contains all reachable types from +// the root clientgen types of the provided packages. +func refGraphForReachableTypes(universe types.Universe, pkgTypes map[string]*types.Package, initialTypes map[types.Name]string) refGraph { + var refs refGraph = initialTypes + + // Include only types that are reachable from the root clientgen types. + // We don't want to generate apply configurations for types that are not reachable from a root + // clientgen type. + reachableTypes := map[types.Name]*types.Type{} + for _, p := range pkgTypes { + for _, t := range p.Types { + tags := genclientTags(t) + hasApply := tags.HasVerb("apply") || tags.HasVerb("applyStatus") + if tags.GenerateClient && hasApply { + findReachableTypes(t, reachableTypes) + } + // If any apply extensions have custom inputs, add them. + for _, extension := range tags.Extensions { + if extension.HasVerb("apply") { + if len(extension.InputTypeOverride) > 0 { + inputType := *t + if name, pkg := extension.Input(); len(pkg) > 0 { + inputType = *(universe.Type(types.Name{Package: pkg, Name: name})) + } else { + inputType.Name.Name = extension.InputTypeOverride + } + findReachableTypes(&inputType, reachableTypes) + } + } + } + } + } + for pkg, p := range pkgTypes { + for _, t := range p.Types { + if _, ok := reachableTypes[t.Name]; !ok { + continue + } + if requiresApplyConfiguration(t) { + refs[t.Name] = pkg + } + } + } + + return refs +} + +// applyConfigForType find the type used in the generate apply configurations for a field. +// This may either be an existing type or one of the other generated applyConfig types. +func (t refGraph) applyConfigForType(field *types.Type) *types.Type { + switch field.Kind { + case types.Struct: + if pkg, ok := t[field.Name]; ok { // TODO(jpbetz): Refs to types defined in a separate system (e.g. TypeMeta if generating a 3rd party controller) end up referencing the go struct, not the apply configuration type + return types.Ref(pkg, field.Name.Name+ApplyConfigurationTypeSuffix) + } + return field + case types.Map: + if _, ok := t[field.Elem.Name]; ok { + return &types.Type{ + Kind: types.Map, + Elem: t.applyConfigForType(field.Elem), + Key: t.applyConfigForType(field.Key), + } + } + return field + case types.Slice: + if _, ok := t[field.Elem.Name]; ok { + return &types.Type{ + Kind: types.Slice, + Elem: t.applyConfigForType(field.Elem), + } + } + return field + case types.Pointer: + return t.applyConfigForType(field.Elem) + default: + return field + } +} + +func (t refGraph) isApplyConfig(field *types.Type) bool { + switch field.Kind { + case types.Struct: + _, ok := t[field.Name] + return ok + case types.Pointer: + return t.isApplyConfig(field.Elem) + } + return false +} + +// genclientTags returns the genclient Tags for the given type. +func genclientTags(t *types.Type) util.Tags { + return util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) +} + +// findReachableTypes finds all types transitively reachable from a given root type, including +// the root type itself. +func findReachableTypes(t *types.Type, referencedTypes map[types.Name]*types.Type) { + if _, ok := referencedTypes[t.Name]; ok { + return + } + referencedTypes[t.Name] = t + + if t.Elem != nil { + findReachableTypes(t.Elem, referencedTypes) + } + if t.Underlying != nil { + findReachableTypes(t.Underlying, referencedTypes) + } + if t.Key != nil { + findReachableTypes(t.Key, referencedTypes) + } + for _, m := range t.Members { + findReachableTypes(m.Type, referencedTypes) + } +} + +// excludeTypes contains well known types that we do not generate apply configurations for. +// Hard coding because we only have two, very specific types that serve a special purpose +// in the type system here. +var excludeTypes = map[types.Name]struct{}{ + rawExtension.Name: {}, + unknown.Name: {}, + // DO NOT ADD TO THIS LIST. If we need to exclude other types, we should consider allowing the + // go type declarations to be annotated as excluded from this generator. +} + +// requiresApplyConfiguration returns true if a type applyConfig should be generated for the given type. +// types applyConfig are only generated for struct types that contain fields with json tags. +func requiresApplyConfiguration(t *types.Type) bool { + for t.Kind == types.Alias { + t = t.Underlying + } + if t.Kind != types.Struct { + return false + } + if _, ok := excludeTypes[t.Name]; ok { + return false + } + var hasJSONTaggedMembers bool + for _, member := range t.Members { + if _, ok := lookupJSONTags(member); ok { + hasJSONTaggedMembers = true + } + } + return hasJSONTaggedMembers +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/targets.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/targets.go new file mode 100644 index 0000000000..73c3f2cb27 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/targets.go @@ -0,0 +1,332 @@ +/* +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 generators + +import ( + "fmt" + "path" + "path/filepath" + "sort" + "strings" + + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" + + "k8s.io/code-generator/cmd/applyconfiguration-gen/args" + "k8s.io/code-generator/cmd/client-gen/generators/util" + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/code-generator/pkg/apidefinitions" + genutil "k8s.io/code-generator/pkg/util" +) + +const ( + // ApplyConfigurationTypeSuffix is the suffix of generated apply configuration types. + ApplyConfigurationTypeSuffix = "ApplyConfiguration" +) + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{ + "public": namer.NewPublicNamer(0), + "private": namer.NewPrivateNamer(0), + "raw": namer.NewRawNamer("", nil), + } +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +// GetTargets makes the client target definition. +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, "", gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + pkgTypes := packageTypesForInputs(context, args.OutputPkg, idOpts) + initialTypes := args.ExternalApplyConfigurations + refs := refGraphForReachableTypes(context.Universe, pkgTypes, initialTypes) + typeModels, err := newTypeModels(args.OpenAPISchemaFilePath, pkgTypes) + if err != nil { + klog.Fatalf("Failed build type models from typeModels %s: %v", args.OpenAPISchemaFilePath, err) + } + + groupVersions := make(map[string]clientgentypes.GroupVersions) + groupGoNames := make(map[string]string) + applyConfigsForGroupVersion := make(map[clientgentypes.GroupVersion][]applyConfig) + + var targetList []generator.Target + for pkg, p := range pkgTypes { + gv, err := groupVersion(p) + if err != nil { + klog.Fatalf("Failed to parse comments of package %s: %v", p.Name, err) + } + + var toGenerate []applyConfig + for _, t := range p.Types { + // If we don't have an ObjectMeta field, we lack the information required to make the Apply or ApplyStatus call + // to the kube-apiserver, so we don't need to generate the type at all + clientTags := genclientTags(t) + if clientTags.GenerateClient && !hasObjectMetaField(t) { + klog.V(5).Infof("skipping type %v because does not have ObjectMeta", t) + continue + } + gvk := gv.WithKind(clientgentypes.Kind(t.Name.Name)) + openAPIName := typeModels.gvkToOpenAPIType[gvk] + + if typePkg, ok := refs[t.Name]; ok { + toGenerate = append(toGenerate, applyConfig{ + Type: t, + ApplyConfiguration: types.Ref(typePkg, t.Name.Name+ApplyConfigurationTypeSuffix), + OpenAPIName: openAPIName, + }) + } + } + if len(toGenerate) == 0 { + continue // Don't generate empty packages + } + sort.Sort(applyConfigSort(toGenerate)) + + // Apparently we allow the groupName to be overridden in a way that it + // no longer maps to a Go package by name. So we have to figure out + // the offset of this particular output package (pkg) from the base + // output package (args.OutputPkg). + pkgSubdir := strings.TrimPrefix(pkg, args.OutputPkg+"/") + + // generate the apply configurations + targetList = append(targetList, + targetForApplyConfigurationsPackage( + args.OutputDir, args.OutputPkg, pkgSubdir, + boilerplate, gv, toGenerate, refs, typeModels)) + + // group all the generated apply configurations by gv so ForKind() can be generated + groupPackageName := gv.Group.NonEmpty() + groupVersionsEntry, ok := groupVersions[groupPackageName] + if !ok { + groupVersionsEntry = clientgentypes.GroupVersions{ + PackageName: groupPackageName, + Group: gv.Group, + } + } + groupVersionsEntry.Versions = append(groupVersionsEntry.Versions, clientgentypes.PackageVersion{ + Version: gv.Version, + Package: path.Clean(p.Path), + }) + + groupGoNames[groupPackageName], err = goName(gv, p) + if err != nil { + klog.Fatalf("Failed to parse comments of group package %s: %v", groupPackageName, err) + } + applyConfigsForGroupVersion[gv] = toGenerate + groupVersions[groupPackageName] = groupVersionsEntry + } + + // generate ForKind() utility function + targetList = append(targetList, + targetForUtils(args.OutputDir, args.OutputPkg, + boilerplate, groupVersions, applyConfigsForGroupVersion, groupGoNames, typeModels)) + // generate internal embedded schema, required for generated Extract functions + targetList = append(targetList, + targetForInternal(args.OutputDir, args.OutputPkg, + boilerplate, typeModels)) + + return targetList +} + +func typeName(t *types.Type) string { + typePackage := t.Name.Package + return fmt.Sprintf("%s.%s", typePackage, t.Name.Name) +} + +func targetForApplyConfigurationsPackage(outputDirBase, outputPkgBase, pkgSubdir string, boilerplate []byte, gv clientgentypes.GroupVersion, typesToGenerate []applyConfig, refs refGraph, models *typeModels) generator.Target { + outputDir := filepath.Join(outputDirBase, pkgSubdir) + outputPkg := path.Join(outputPkgBase, pkgSubdir) + + return &generator.SimpleTarget{ + PkgName: gv.Version.PackageName(), + PkgPath: outputPkg, + PkgDir: outputDir, + HeaderComment: boilerplate, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + for _, toGenerate := range typesToGenerate { + var openAPIType *string + gvk := gv.WithKind(clientgentypes.Kind(toGenerate.Type.Name.Name)) + if v, ok := models.gvkToOpenAPIType[gvk]; ok { + openAPIType = &v + } + + generators = append(generators, &applyConfigurationGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: strings.ToLower(toGenerate.Type.Name.Name) + ".go", + }, + outPkgBase: outputPkgBase, + localPkg: outputPkg, + groupVersion: gv, + applyConfig: toGenerate, + imports: generator.NewImportTrackerForPackage(outputPkg), + refGraph: refs, + openAPIType: openAPIType, + }) + } + return generators + }, + } +} + +func targetForUtils(outputDirBase, outputPkgBase string, boilerplate []byte, groupVersions map[string]clientgentypes.GroupVersions, + applyConfigsForGroupVersion map[clientgentypes.GroupVersion][]applyConfig, groupGoNames map[string]string, models *typeModels) generator.Target { + return &generator.SimpleTarget{ + PkgName: path.Base(outputPkgBase), + PkgPath: outputPkgBase, + PkgDir: outputDirBase, + HeaderComment: boilerplate, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = append(generators, &utilGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "utils.go", + }, + outputPackage: outputPkgBase, + imports: generator.NewImportTrackerForPackage(outputPkgBase), + groupVersions: groupVersions, + typesForGroupVersion: applyConfigsForGroupVersion, + groupGoNames: groupGoNames, + typeModels: models, + }) + return generators + }, + } +} + +func targetForInternal(outputDirBase, outputPkgBase string, boilerplate []byte, models *typeModels) generator.Target { + outputDir := filepath.Join(outputDirBase, "internal") + outputPkg := path.Join(outputPkgBase, "internal") + return &generator.SimpleTarget{ + PkgName: path.Base(outputPkg), + PkgPath: outputPkg, + PkgDir: outputDir, + HeaderComment: boilerplate, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = append(generators, &internalGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "internal.go", + }, + outputPackage: outputPkgBase, + imports: generator.NewImportTrackerForPackage(outputPkg), + typeModels: models, + }) + return generators + }, + } +} + +func goName(gv clientgentypes.GroupVersion, p *types.Package) (string, error) { + goName := namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0]) + override, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupGoName"}, p.Comments) + + if err != nil { + return goName, err + } + if values, ok := override["groupGoName"]; ok { + goName = namer.IC(values[0]) + } + + return goName, nil +} + +func packageTypesForInputs(context *generator.Context, outPkgBase string, idOpts []apidefinitions.Option) map[string]*types.Package { + pkgTypes := map[string]*types.Package{} + for _, inputDir := range context.Inputs { + p := context.Universe.Package(inputDir) + info, err := apidefinitions.Identify(p, apidefinitions.ApplyConfiguration, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + continue + } + internal := isInternalPackage(p) + if internal { + klog.Warningf("Skipping internal package: %s", p.Path) + continue + } + // This is how the client generator finds the package we are creating. It uses the API package name, not the group name. + // This matches the approach of the client-gen, so the two generator can work together. + // For example, if openshift/api/cloudnetwork/v1 contains an apigroup cloud.network.openshift.io, the client-gen + // builds a package called cloudnetwork/v1 to contain it. This change makes the applyconfiguration-gen use the same. + _, gvPackageString := util.ParsePathGroupVersion(p.Path) + pkg := path.Join(outPkgBase, strings.ToLower(gvPackageString)) + pkgTypes[pkg] = p + } + return pkgTypes +} + +func groupVersion(p *types.Package) (gv clientgentypes.GroupVersion, err error) { + parts := strings.Split(p.Path, "/") + gv.Group = clientgentypes.Group(parts[len(parts)-2]) + gv.Version = clientgentypes.Version(parts[len(parts)-1]) + + // If there's a comment of the form "// +groupName=somegroup" or + // "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the + // group when generating. + override, ok, err := apidefinitions.GroupNameForPackage(p.Comments) + if err != nil { + return gv, err + } + if ok { + gv.Group = clientgentypes.Group(override) + } + + return gv, nil +} + +// isInternalPackage returns true if the package is an internal package +func isInternalPackage(p *types.Package) bool { + for _, t := range p.Types { + for _, member := range t.Members { + if member.Name == "ObjectMeta" { + return isInternal(member) + } + } + } + return false +} + +// isInternal returns true if the tags for a member do not contain a json tag +func isInternal(m types.Member) bool { + _, ok := lookupJSONTags(m) + return !ok +} + +func hasObjectMetaField(t *types.Type) bool { + for _, member := range t.Members { + if objectMeta.Name == member.Type.Name && member.Embedded { + return true + } + } + return false +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/types.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/types.go new file mode 100644 index 0000000000..2f9fc783c4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/types.go @@ -0,0 +1,37 @@ +/* +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 generators + +import "k8s.io/gengo/v2/types" + +var ( + fmtSprintf = types.Ref("fmt", "Sprintf") + syncOnce = types.Ref("sync", "Once") + applyConfiguration = types.Ref("k8s.io/apimachinery/pkg/runtime", "ApplyConfiguration") + groupVersionKind = types.Ref("k8s.io/apimachinery/pkg/runtime/schema", "GroupVersionKind") + typeMeta = types.Ref("k8s.io/apimachinery/pkg/apis/meta/v1", "TypeMeta") + objectMeta = types.Ref("k8s.io/apimachinery/pkg/apis/meta/v1", "ObjectMeta") + rawExtension = types.Ref("k8s.io/apimachinery/pkg/runtime", "RawExtension") + unknown = types.Ref("k8s.io/apimachinery/pkg/runtime", "Unknown") + extractInto = types.Ref("k8s.io/apimachinery/pkg/util/managedfields", "ExtractInto") + typeConverter = types.Ref("k8s.io/apimachinery/pkg/util/managedfields", "TypeConverter") + newSchemeTypeConverter = types.Ref("k8s.io/apimachinery/pkg/util/managedfields", "NewSchemeTypeConverter") + runtimeScheme = types.Ref("k8s.io/apimachinery/pkg/runtime", "Scheme") + smdNewParser = types.Ref("sigs.k8s.io/structured-merge-diff/v6/typed", "NewParser") + smdParser = types.Ref("sigs.k8s.io/structured-merge-diff/v6/typed", "Parser") + yamlObject = types.Ref("sigs.k8s.io/structured-merge-diff/v6/typed", "YAMLObject") +) diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/util.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/util.go new file mode 100644 index 0000000000..d8db28fa14 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/generators/util.go @@ -0,0 +1,177 @@ +/* +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 generators + +import ( + "io" + "path" + "sort" + "strings" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +// utilGenerator generates the ForKind() utility function. +type utilGenerator struct { + generator.GoGenerator + outputPackage string + imports namer.ImportTracker + groupVersions map[string]clientgentypes.GroupVersions + groupGoNames map[string]string + typesForGroupVersion map[clientgentypes.GroupVersion][]applyConfig + filtered bool + typeModels *typeModels +} + +var _ generator.Generator = &utilGenerator{} + +func (g *utilGenerator) Filter(*generator.Context, *types.Type) bool { + // generate file exactly once + if !g.filtered { + g.filtered = true + return true + } + return false +} + +func (g *utilGenerator) Namers(*generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + "singularKind": namer.NewPublicNamer(0), + } +} + +func (g *utilGenerator) Imports(*generator.Context) (imports []string) { + return g.imports.ImportLines() +} + +type group struct { + GroupGoName string + Name string + Versions []*version +} + +type groupSort []group + +func (g groupSort) Len() int { return len(g) } +func (g groupSort) Less(i, j int) bool { + return strings.ToLower(g[i].Name) < strings.ToLower(g[j].Name) +} +func (g groupSort) Swap(i, j int) { g[i], g[j] = g[j], g[i] } + +type version struct { + Name string + GoName string + Resources []applyConfig +} + +type versionSort []*version + +func (v versionSort) Len() int { return len(v) } +func (v versionSort) Less(i, j int) bool { + return strings.ToLower(v[i].Name) < strings.ToLower(v[j].Name) +} +func (v versionSort) Swap(i, j int) { v[i], v[j] = v[j], v[i] } + +type applyConfig struct { + Type *types.Type + ApplyConfiguration *types.Type + OpenAPIName string +} + +type applyConfigSort []applyConfig + +func (v applyConfigSort) Len() int { return len(v) } +func (v applyConfigSort) Less(i, j int) bool { + return strings.ToLower(v[i].Type.Name.Name) < strings.ToLower(v[j].Type.Name.Name) +} +func (v applyConfigSort) Swap(i, j int) { v[i], v[j] = v[j], v[i] } + +func (g *utilGenerator) GenerateType(c *generator.Context, _ *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "{{", "}}") + + var groups []group + schemeGVs := make(map[*version]*types.Type) + + for groupPackageName, groupVersions := range g.groupVersions { + group := group{ + GroupGoName: g.groupGoNames[groupPackageName], + Name: groupVersions.Group.NonEmpty(), + Versions: []*version{}, + } + for _, v := range groupVersions.Versions { + gv := clientgentypes.GroupVersion{Group: groupVersions.Group, Version: v.Version} + version := &version{ + Name: v.Version.NonEmpty(), + GoName: namer.IC(v.Version.NonEmpty()), + Resources: g.typesForGroupVersion[gv], + } + schemeGVs[version] = c.Universe.Variable(types.Name{ + Package: g.typesForGroupVersion[gv][0].Type.Name.Package, + Name: "SchemeGroupVersion", + }) + group.Versions = append(group.Versions, version) + } + sort.Sort(versionSort(group.Versions)) + groups = append(groups, group) + } + sort.Sort(groupSort(groups)) + + m := map[string]interface{}{ + "applyConfiguration": applyConfiguration, + "groups": groups, + "internalParser": types.Ref(path.Join(g.outputPackage, "internal"), "Parser"), + "runtimeScheme": runtimeScheme, + "schemeGVs": schemeGVs, + "schemaGroupVersionKind": groupVersionKind, + "typeConverter": typeConverter, + "newSchemeTypeConverter": newSchemeTypeConverter, + } + sw.Do(forKindFunc, m) + sw.Do(newTypeConverterFunc, m) + + return sw.Error() +} + +var newTypeConverterFunc = ` +func NewTypeConverter(scheme *{{.runtimeScheme|raw}}) {{.typeConverter|raw}} { + return {{.newSchemeTypeConverter|raw}}(scheme, {{.internalParser|raw}}()) +} +` + +var forKindFunc = ` +// ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no +// apply configuration type exists for the given GroupVersionKind. +func ForKind(kind {{.schemaGroupVersionKind|raw}}) interface{} { + switch kind { + {{range $group := .groups -}}{{$GroupGoName := .GroupGoName -}} + {{range $version := .Versions -}} + // Group={{$group.Name}}, Version={{.Name}} + {{range .Resources -}} + case {{index $.schemeGVs $version|raw}}.WithKind("{{.Type|singularKind}}"): + return &{{.ApplyConfiguration|raw}}{} + {{end}} + {{end}} + {{end -}} + } + return nil +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/main.go new file mode 100644 index 0000000000..f6b03f8877 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/applyconfiguration-gen/main.go @@ -0,0 +1,60 @@ +/* +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. +*/ + +// applyconfiguration-gen is a tool for auto-generating apply builder functions. +package main + +import ( + "flag" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/applyconfiguration-gen/args" + "k8s.io/code-generator/cmd/applyconfiguration-gen/generators" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + args := args.New() + args.AddFlags(pflag.CommandLine, "k8s.io/kubernetes/pkg/apis") // TODO: move this input path out of applyconfiguration-gen + if err := flag.Set("logtostderr", "true"); err != nil { + klog.Fatalf("Error: %v", err) + } + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return generators.GetTargets(context, args) + } + + // Run it. + if err := gengo.Execute( + generators.NameSystems(), + generators.DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/OWNERS b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/OWNERS new file mode 100644 index 0000000000..967eb2a7bb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/OWNERS @@ -0,0 +1,11 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - wojtek-t + - caesarxuchao +reviewers: + - wojtek-t + - caesarxuchao + - jpbetz +emeritus_approvers: + - lavalamp diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/README.md b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/README.md new file mode 100644 index 0000000000..b8206127ff --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/README.md @@ -0,0 +1,2 @@ +See [generating-clientset.md](https://git.k8s.io/community/contributors/devel/sig-api-machinery/generating-clientset.md) + diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/args.go new file mode 100644 index 0000000000..9ea1d0bf36 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/args.go @@ -0,0 +1,154 @@ +/* +Copyright 2015 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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/code-generator/pkg/apidefinitions" +) + +type Args struct { + // The directory for the generated results. + OutputDir string + + // The Go import-path of the generated results. + OutputPkg string + + // The boilerplate header for Go files. + GoHeaderFile string + + // A sorted list of group versions to generate. For each of them the package path is found + // in GroupVersionToInputPath. + Groups []types.GroupVersions + + // Overrides for which types should be included in the client. + IncludedTypesOverrides map[types.GroupVersion][]string + + // ClientsetName is the name of the clientset to be generated. It's + // populated from command-line arguments. + ClientsetName string + // ClientsetAPIPath is the default API HTTP path for generated clients. + ClientsetAPIPath string + // ClientsetOnly determines if we should generate the clients for groups and + // types along with the clientset. It's populated from command-line + // arguments. + ClientsetOnly bool + // FakeClient determines if client-gen generates the fake clients. + FakeClient bool + // PluralExceptions specify list of exceptions used when pluralizing certain types. + // For example 'Endpoints:Endpoints', otherwise the pluralizer will generate 'Endpointes'. + PluralExceptions []string + + // ApplyConfigurationPackage is the package of apply builders generated by + // applyconfiguration-gen. + // If non-empty, Apply functions are generated for each type and reference the apply builders. + // If empty (""), Apply functions are not generated. + ApplyConfigurationPackage string + + // PrefersProtobuf determines if the generated clientset uses protobuf for API requests. + PrefersProtobuf bool + + apidefinitions.LintArgs +} + +func New() *Args { + return &Args{ + ClientsetName: "internalclientset", + ClientsetAPIPath: "/apis", + ClientsetOnly: false, + FakeClient: true, + ApplyConfigurationPackage: "", + } +} + +func (args *Args) AddFlags(fs *pflag.FlagSet, inputBase string) { + gvsBuilder := NewGroupVersionsBuilder(&args.Groups) + fs.StringVar(&args.OutputDir, "output-dir", "", + "the base directory under which to generate results") + fs.StringVar(&args.OutputPkg, "output-pkg", args.OutputPkg, + "the Go import-path of the generated results") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + fs.Var(NewGVPackagesValue(gvsBuilder, nil), "input", + `group/versions that client-gen will generate clients for. At most one version per group is allowed. Specified in the format "group1/version1,group2/version2...".`) + fs.Var(NewGVTypesValue(&args.IncludedTypesOverrides, []string{}), "included-types-overrides", + "list of group/version/type for which client should be generated. By default, client is generated for all types which have genclient in types.go. This overrides that. For each groupVersion in this list, only the types mentioned here will be included. The default check of genclient will be used for other group versions.") + fs.Var(NewInputBasePathValue(gvsBuilder, inputBase), "input-base", + "base path to look for the api group.") + fs.StringVarP(&args.ClientsetName, "clientset-name", "n", args.ClientsetName, + "the name of the generated clientset package.") + fs.StringVarP(&args.ClientsetAPIPath, "clientset-api-path", "", args.ClientsetAPIPath, + "the value of default API HTTP path, starting with / and without trailing /.") + fs.BoolVar(&args.ClientsetOnly, "clientset-only", args.ClientsetOnly, + "when set, client-gen only generates the clientset shell, without generating the individual typed clients") + fs.BoolVar(&args.FakeClient, "fake-clientset", args.FakeClient, + "when set, client-gen will generate the fake clientset that can be used in tests") + fs.StringSliceVar(&args.PluralExceptions, "plural-exceptions", args.PluralExceptions, + "list of comma separated plural exception definitions in Type:PluralizedType form") + fs.StringVar(&args.ApplyConfigurationPackage, "apply-configuration-package", args.ApplyConfigurationPackage, + "optional package of apply configurations, generated by applyconfiguration-gen, that are required to generate Apply functions for each type in the clientset. By default Apply functions are not generated.") + fs.BoolVar(&args.PrefersProtobuf, "prefers-protobuf", args.PrefersProtobuf, + "when set, client-gen will generate a clientset that uses protobuf for API requests") + apidefinitions.AddFlags(&args.LintArgs, fs) + + // support old flags + fs.SetNormalizeFunc(mapFlagName("clientset-path", "output-pkg", fs.GetNormalizeFunc())) +} + +func (args *Args) Validate() error { + if len(args.OutputDir) == 0 { + return fmt.Errorf("--output-dir must be specified") + } + if len(args.OutputPkg) == 0 { + return fmt.Errorf("--output-pkg must be specified") + } + if len(args.ClientsetName) == 0 { + return fmt.Errorf("--clientset-name must be specified") + } + if len(args.ClientsetAPIPath) == 0 { + return fmt.Errorf("--clientset-api-path cannot be empty") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + + return nil +} + +// GroupVersionPackages returns a map from GroupVersion to the package with the types.go. +func (args *Args) GroupVersionPackages() map[types.GroupVersion]string { + res := map[types.GroupVersion]string{} + for _, pkg := range args.Groups { + for _, v := range pkg.Versions { + res[types.GroupVersion{Group: pkg.Group, Version: v.Version}] = v.Package + } + } + return res +} + +func mapFlagName(from, to string, old func(fs *pflag.FlagSet, name string) pflag.NormalizedName) func(fs *pflag.FlagSet, name string) pflag.NormalizedName { + return func(fs *pflag.FlagSet, name string) pflag.NormalizedName { + if name == from { + name = to + } + return old(fs, name) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvpackages.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvpackages.go new file mode 100644 index 0000000000..f5e7f4063a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvpackages.go @@ -0,0 +1,175 @@ +/* +Copyright 2017 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 args + +import ( + "bytes" + "encoding/csv" + "flag" + "path" + "sort" + "strings" + + "k8s.io/code-generator/cmd/client-gen/generators/util" + "k8s.io/code-generator/cmd/client-gen/types" +) + +type inputBasePathValue struct { + builder *groupVersionsBuilder +} + +var _ flag.Value = &inputBasePathValue{} + +func NewInputBasePathValue(builder *groupVersionsBuilder, def string) *inputBasePathValue { + v := &inputBasePathValue{ + builder: builder, + } + v.Set(def) + return v +} + +func (s *inputBasePathValue) Set(val string) error { + s.builder.importBasePath = val + return s.builder.update() +} + +func (s *inputBasePathValue) Type() string { + return "string" +} + +func (s *inputBasePathValue) String() string { + return s.builder.importBasePath +} + +type gvPackagesValue struct { + builder *groupVersionsBuilder + groups []string + changed bool +} + +func NewGVPackagesValue(builder *groupVersionsBuilder, def []string) *gvPackagesValue { + gvp := new(gvPackagesValue) + gvp.builder = builder + if def != nil { + if err := gvp.set(def); err != nil { + panic(err) + } + } + return gvp +} + +var _ flag.Value = &gvPackagesValue{} + +func (s *gvPackagesValue) set(vs []string) error { + if s.changed { + s.groups = append(s.groups, vs...) + } else { + s.groups = append([]string(nil), vs...) + } + + s.builder.groups = s.groups + return s.builder.update() +} + +func (s *gvPackagesValue) Set(val string) error { + vs, err := readAsCSV(val) + if err != nil { + return err + } + if err := s.set(vs); err != nil { + return err + } + s.changed = true + return nil +} + +func (s *gvPackagesValue) Type() string { + return "stringSlice" +} + +func (s *gvPackagesValue) String() string { + str, _ := writeAsCSV(s.groups) + return "[" + str + "]" +} + +type groupVersionsBuilder struct { + value *[]types.GroupVersions + groups []string + importBasePath string +} + +func NewGroupVersionsBuilder(groups *[]types.GroupVersions) *groupVersionsBuilder { + return &groupVersionsBuilder{ + value: groups, + } +} + +func (p *groupVersionsBuilder) update() error { + var seenGroups = make(map[types.Group]*types.GroupVersions) + for _, v := range p.groups { + pth, gvString := util.ParsePathGroupVersion(v) + gv, err := types.ToGroupVersion(gvString) + if err != nil { + return err + } + + versionPkg := types.PackageVersion{Package: path.Join(p.importBasePath, pth, gv.Group.NonEmpty(), gv.Version.String()), Version: gv.Version} + if group, ok := seenGroups[gv.Group]; ok { + vers := group.Versions + vers = append(vers, versionPkg) + seenGroups[gv.Group].Versions = vers + } else { + seenGroups[gv.Group] = &types.GroupVersions{ + PackageName: gv.Group.NonEmpty(), + Group: gv.Group, + Versions: []types.PackageVersion{versionPkg}, + } + } + } + + var groupNames []string + for groupName := range seenGroups { + groupNames = append(groupNames, groupName.String()) + } + sort.Strings(groupNames) + *p.value = []types.GroupVersions{} + for _, groupName := range groupNames { + *p.value = append(*p.value, *seenGroups[types.Group(groupName)]) + } + + return nil +} + +func readAsCSV(val string) ([]string, error) { + if val == "" { + return []string{}, nil + } + stringReader := strings.NewReader(val) + csvReader := csv.NewReader(stringReader) + return csvReader.Read() +} + +func writeAsCSV(vals []string) (string, error) { + b := &bytes.Buffer{} + w := csv.NewWriter(b) + err := w.Write(vals) + if err != nil { + return "", err + } + w.Flush() + return strings.TrimSuffix(b.String(), "\n"), nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvpackages_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvpackages_test.go new file mode 100644 index 0000000000..3f7eb8902e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvpackages_test.go @@ -0,0 +1,116 @@ +/* +Copyright 2017 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 args + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/cmd/client-gen/types" +) + +func TestGVPackageFlag(t *testing.T) { + tests := []struct { + args []string + def []string + importBasePath string + expected map[types.GroupVersion]string + expectedGroups []types.GroupVersions + parseError string + }{ + { + args: []string{}, + expected: map[types.GroupVersion]string{}, + expectedGroups: []types.GroupVersions{}, + }, + { + args: []string{"foo/bar/v1", "foo/bar/v2", "foo/bar/", "foo/v1"}, + expectedGroups: []types.GroupVersions{ + {PackageName: "bar", Group: types.Group("bar"), Versions: []types.PackageVersion{ + {Version: "v1", Package: "foo/bar/v1"}, + {Version: "v2", Package: "foo/bar/v2"}, + {Version: "", Package: "foo/bar"}, + }}, + {PackageName: "foo", Group: types.Group("foo"), Versions: []types.PackageVersion{ + {Version: "v1", Package: "foo/v1"}, + }}, + }, + }, + { + args: []string{"foo/bar/v1", "foo/bar/v2", "foo/bar/", "foo/v1"}, + def: []string{"foo/bar/v1alpha1", "foo/v1"}, + expectedGroups: []types.GroupVersions{ + {PackageName: "bar", Group: types.Group("bar"), Versions: []types.PackageVersion{ + {Version: "v1", Package: "foo/bar/v1"}, + {Version: "v2", Package: "foo/bar/v2"}, + {Version: "", Package: "foo/bar"}, + }}, + {PackageName: "foo", Group: types.Group("foo"), Versions: []types.PackageVersion{ + {Version: "v1", Package: "foo/v1"}, + }}, + }, + }, + { + args: []string{"api/v1", "api"}, + expectedGroups: []types.GroupVersions{ + {PackageName: "api", Group: types.Group("api"), Versions: []types.PackageVersion{ + {Version: "v1", Package: "api/v1"}, + {Version: "", Package: "api"}, + }}, + }, + }, + { + args: []string{"foo/v1"}, + importBasePath: "k8s.io/api", + expectedGroups: []types.GroupVersions{ + {PackageName: "foo", Group: types.Group("foo"), Versions: []types.PackageVersion{ + {Version: "v1", Package: "k8s.io/api/foo/v1"}, + }}, + }, + }, + } + for i, test := range tests { + fs := pflag.NewFlagSet("testGVPackage", pflag.ContinueOnError) + groups := []types.GroupVersions{} + builder := NewGroupVersionsBuilder(&groups) + fs.Var(NewGVPackagesValue(builder, test.def), "input", "usage") + fs.Var(NewInputBasePathValue(builder, test.importBasePath), "input-base-path", "usage") + + args := []string{} + for _, a := range test.args { + args = append(args, fmt.Sprintf("--input=%s", a)) + } + + err := fs.Parse(args) + if test.parseError != "" { + if err == nil { + t.Errorf("%d: expected error %q, got nil", i, test.parseError) + } else if !strings.Contains(err.Error(), test.parseError) { + t.Errorf("%d: expected error %q, got %q", i, test.parseError, err) + } + } else if err != nil { + t.Errorf("%d: expected nil error, got %v", i, err) + } + if !reflect.DeepEqual(groups, test.expectedGroups) { + t.Errorf("%d: expected groups %+v, got groups %+v", i, test.expectedGroups, groups) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvtype.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvtype.go new file mode 100644 index 0000000000..e4e3ccb536 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/args/gvtype.go @@ -0,0 +1,110 @@ +/* +Copyright 2017 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 args + +import ( + "flag" + "fmt" + "strings" + + "k8s.io/code-generator/cmd/client-gen/types" +) + +type gvTypeValue struct { + gvToTypes *map[types.GroupVersion][]string + changed bool +} + +func NewGVTypesValue(gvToTypes *map[types.GroupVersion][]string, def []string) *gvTypeValue { + gvt := new(gvTypeValue) + gvt.gvToTypes = gvToTypes + if def != nil { + if err := gvt.set(def); err != nil { + panic(err) + } + } + return gvt +} + +var _ flag.Value = &gvTypeValue{} + +func (s *gvTypeValue) set(vs []string) error { + if !s.changed { + *s.gvToTypes = map[types.GroupVersion][]string{} + } + + for _, input := range vs { + gvString, typeStr, err := parseGroupVersionType(input) + if err != nil { + return err + } + gv, err := types.ToGroupVersion(gvString) + if err != nil { + return err + } + types, ok := (*s.gvToTypes)[gv] + if !ok { + types = []string{} + } + types = append(types, typeStr) + (*s.gvToTypes)[gv] = types + } + + return nil +} + +func (s *gvTypeValue) Set(val string) error { + vs, err := readAsCSV(val) + if err != nil { + return err + } + if err := s.set(vs); err != nil { + return err + } + s.changed = true + return nil +} + +func (s *gvTypeValue) Type() string { + return "stringSlice" +} + +func (s *gvTypeValue) String() string { + strs := make([]string, 0, len(*s.gvToTypes)) + for gv, ts := range *s.gvToTypes { + for _, t := range ts { + strs = append(strs, gv.Group.String()+"/"+gv.Version.String()+"/"+t) + } + } + str, _ := writeAsCSV(strs) + return "[" + str + "]" +} + +func parseGroupVersionType(gvtString string) (gvString string, typeStr string, err error) { + invalidFormatErr := fmt.Errorf("invalid value: %s, should be of the form group/version/type", gvtString) + subs := strings.Split(gvtString, "/") + length := len(subs) + switch length { + case 2: + // gvtString of the form group/type, e.g. api/Service,extensions/ReplicaSet + return subs[0] + "/", subs[1], nil + case 3: + return strings.Join(subs[:length-1], "/"), subs[length-1], nil + default: + return "", "", invalidFormatErr + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/client_generator.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/client_generator.go new file mode 100644 index 0000000000..65dafc82ec --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/client_generator.go @@ -0,0 +1,461 @@ +/* +Copyright 2015 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 generators has the generators for the client-gen utility. +package generators + +import ( + "fmt" + "path" + "path/filepath" + "strings" + + "k8s.io/code-generator/cmd/client-gen/args" + "k8s.io/code-generator/cmd/client-gen/generators/fake" + "k8s.io/code-generator/cmd/client-gen/generators/scheme" + "k8s.io/code-generator/cmd/client-gen/generators/util" + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/code-generator/pkg/apidefinitions" + codegennamer "k8s.io/code-generator/pkg/namer" + genutil "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/klog/v2" +) + +// NameSystems returns the name system used by the generators in this package. +func NameSystems(pluralExceptions map[string]string) namer.NameSystems { + lowercaseNamer := namer.NewAllLowercasePluralNamer(pluralExceptions) + + publicNamer := &ExceptionNamer{ + Exceptions: map[string]string{ + // these exceptions are used to deconflict the generated code + // you can put your fully qualified package like + // to generate a name that doesn't conflict with your group. + // "k8s.io/apis/events/v1beta1.Event": "EventResource" + }, + KeyFunc: func(t *types.Type) string { + return t.Name.Package + "." + t.Name.Name + }, + Delegate: namer.NewPublicNamer(0), + } + privateNamer := &ExceptionNamer{ + Exceptions: map[string]string{ + // these exceptions are used to deconflict the generated code + // you can put your fully qualified package like + // to generate a name that doesn't conflict with your group. + // "k8s.io/apis/events/v1beta1.Event": "eventResource" + }, + KeyFunc: func(t *types.Type) string { + return t.Name.Package + "." + t.Name.Name + }, + Delegate: namer.NewPrivateNamer(0), + } + publicPluralNamer := &ExceptionNamer{ + Exceptions: map[string]string{ + // these exceptions are used to deconflict the generated code + // you can put your fully qualified package like + // to generate a name that doesn't conflict with your group. + // "k8s.io/apis/events/v1beta1.Event": "EventResource" + }, + KeyFunc: func(t *types.Type) string { + return t.Name.Package + "." + t.Name.Name + }, + Delegate: namer.NewPublicPluralNamer(pluralExceptions), + } + privatePluralNamer := &ExceptionNamer{ + Exceptions: map[string]string{ + // you can put your fully qualified package like + // to generate a name that doesn't conflict with your group. + // "k8s.io/apis/events/v1beta1.Event": "eventResource" + // these exceptions are used to deconflict the generated code + "k8s.io/apis/events/v1beta1.Event": "eventResources", + "k8s.io/kubernetes/pkg/apis/events.Event": "eventResources", + }, + KeyFunc: func(t *types.Type) string { + return t.Name.Package + "." + t.Name.Name + }, + Delegate: namer.NewPrivatePluralNamer(pluralExceptions), + } + + return namer.NameSystems{ + "singularKind": namer.NewPublicNamer(0), + "public": publicNamer, + "private": privateNamer, + "raw": namer.NewRawNamer("", nil), + "publicPlural": publicPluralNamer, + "privatePlural": privatePluralNamer, + "allLowercasePlural": lowercaseNamer, + "resource": codegennamer.NewTagOverrideNamer("resourceName", lowercaseNamer), + } +} + +// ExceptionNamer allows you specify exceptional cases with exact names. This allows you to have control +// for handling various conflicts, like group and resource names for instance. +type ExceptionNamer struct { + Exceptions map[string]string + KeyFunc func(*types.Type) string + + Delegate namer.Namer +} + +// Name provides the requested name for a type. +func (n *ExceptionNamer) Name(t *types.Type) string { + key := n.KeyFunc(t) + if exception, ok := n.Exceptions[key]; ok { + return exception + } + return n.Delegate.Name(t) +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +func targetForGroup(gv clientgentypes.GroupVersion, typeList []*types.Type, clientsetDir, clientsetPkg string, groupPkgName string, groupGoName string, apiPath string, inputPkg string, applyBuilderPkg string, boilerplate []byte, prefersProtobuf bool) generator.Target { + subdir := []string{"typed", strings.ToLower(groupPkgName), strings.ToLower(gv.Version.NonEmpty())} + gvDir := filepath.Join(clientsetDir, filepath.Join(subdir...)) + gvPkg := path.Join(clientsetPkg, path.Join(subdir...)) + + return &generator.SimpleTarget{ + PkgName: strings.ToLower(gv.Version.NonEmpty()), + PkgPath: gvPkg, + PkgDir: gvDir, + HeaderComment: boilerplate, + PkgDocComment: []byte("// This package has the automatically generated typed clients.\n"), + // GeneratorsFunc returns a list of generators. Each generator makes a + // single file. + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = []generator.Generator{ + // Always generate a "doc.go" file. + generator.GoGenerator{OutputFilename: "doc.go"}, + } + // Since we want a file per type that we generate a client for, we + // have to provide a function for this. + for _, t := range typeList { + generators = append(generators, &genClientForType{ + GoGenerator: generator.GoGenerator{ + OutputFilename: strings.ToLower(c.Namers["private"].Name(t)) + ".go", + }, + outputPackage: gvPkg, + inputPackage: inputPkg, + clientsetPackage: clientsetPkg, + applyConfigurationPackage: applyBuilderPkg, + group: gv.Group.NonEmpty(), + version: gv.Version.String(), + groupGoName: groupGoName, + prefersProtobuf: prefersProtobuf, + typeToMatch: t, + imports: generator.NewImportTrackerForPackage(gvPkg), + }) + } + + generators = append(generators, &genGroup{ + GoGenerator: generator.GoGenerator{ + OutputFilename: groupPkgName + "_client.go", + }, + outputPackage: gvPkg, + inputPackage: inputPkg, + clientsetPackage: clientsetPkg, + group: gv.Group.NonEmpty(), + version: gv.Version.String(), + groupGoName: groupGoName, + apiPath: apiPath, + types: typeList, + imports: generator.NewImportTrackerForPackage(gvPkg), + }) + + expansionFileName := "generated_expansion.go" + generators = append(generators, &genExpansion{ + groupPackagePath: gvDir, + GoGenerator: generator.GoGenerator{ + OutputFilename: expansionFileName, + }, + types: typeList, + }) + + return generators + }, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + return util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient + }, + } +} + +func targetForClientset(args *args.Args, clientsetDir, clientsetPkg string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Target { + return &generator.SimpleTarget{ + PkgName: args.ClientsetName, + PkgPath: clientsetPkg, + PkgDir: clientsetDir, + HeaderComment: boilerplate, + // GeneratorsFunc returns a list of generators. Each generator generates a + // single file. + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = []generator.Generator{ + &genClientset{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "clientset.go", + }, + groups: args.Groups, + groupGoNames: groupGoNames, + clientsetPackage: clientsetPkg, + imports: generator.NewImportTrackerForPackage(clientsetPkg), + }, + } + return generators + }, + } +} + +func targetForScheme(args *args.Args, clientsetDir, clientsetPkg string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Target { + schemeDir := filepath.Join(clientsetDir, "scheme") + schemePkg := path.Join(clientsetPkg, "scheme") + + // create runtime.Registry for internal client because it has to know about group versions + internalClient := false +NextGroup: + for _, group := range args.Groups { + for _, v := range group.Versions { + if v.String() == "" { + internalClient = true + break NextGroup + } + } + } + + return &generator.SimpleTarget{ + PkgName: "scheme", + PkgPath: schemePkg, + PkgDir: schemeDir, + HeaderComment: boilerplate, + PkgDocComment: []byte("// This package contains the scheme of the automatically generated clientset.\n"), + // GeneratorsFunc returns a list of generators. Each generator generates a + // single file. + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = []generator.Generator{ + // Always generate a "doc.go" file. + generator.GoGenerator{OutputFilename: "doc.go"}, + + &scheme.GenScheme{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "register.go", + }, + InputPackages: args.GroupVersionPackages(), + OutputPkg: schemePkg, + OutputPath: schemeDir, + Groups: args.Groups, + GroupGoNames: groupGoNames, + ImportTracker: generator.NewImportTrackerForPackage(schemePkg), + CreateRegistry: internalClient, + }, + } + return generators + }, + } +} + +// applyGroupOverrides applies group name overrides to each package, if applicable. If there is a +// comment of the form "// +groupName=somegroup" or "// +groupName=somegroup.foo.bar.io", use the +// first field (somegroup) as the name of the group in Go code, e.g. as the func name in a clientset. +func applyGroupOverrides(universe types.Universe, args *args.Args) error { + // Create a map from "old GV" to "new GV" so we know what changes we need to make. + changes := make(map[clientgentypes.GroupVersion]clientgentypes.GroupVersion) + for gv, inputDir := range args.GroupVersionPackages() { + p := universe.Package(inputDir) + override, ok, err := apidefinitions.GroupNameForPackage(p.Comments) + if err != nil { + return err + } + if ok { + newGV := clientgentypes.GroupVersion{ + Group: clientgentypes.Group(override), + Version: gv.Version, + } + changes[gv] = newGV + } + } + + // Modify args.Groups based on the groupName overrides. + newGroups := make([]clientgentypes.GroupVersions, 0, len(args.Groups)) + for _, gvs := range args.Groups { + gv := clientgentypes.GroupVersion{ + Group: gvs.Group, + Version: gvs.Versions[0].Version, // we only need a version, and the first will do + } + if newGV, ok := changes[gv]; ok { + // There's an override, so use it. + newGVS := clientgentypes.GroupVersions{ + PackageName: gvs.PackageName, + Group: newGV.Group, + Versions: gvs.Versions, + } + newGroups = append(newGroups, newGVS) + } else { + // No override. + newGroups = append(newGroups, gvs) + } + } + args.Groups = newGroups + return nil +} + +// Because we try to assemble inputs from an input-base and a set of +// group-version arguments, sometimes that comes in as a filesystem path. This +// function rewrites them all as their canonical Go import-paths. +// +// TODO: Change this tool to just take inputs as Go "patterns" like every other +// gengo tool, then extract GVs from those. +func sanitizePackagePaths(context *generator.Context, args *args.Args) error { + for i := range args.Groups { + pkg := &args.Groups[i] + for j := range pkg.Versions { + ver := &pkg.Versions[j] + input := ver.Package + p := context.Universe[input] + if p == nil || p.Name == "" { + pkgs, err := context.FindPackages(input) + if err != nil { + return fmt.Errorf("can't find input package %q: %w", input, err) + } + p = context.Universe[pkgs[0]] + if p == nil { + return fmt.Errorf("can't find input package %q in universe", input) + } + ver.Package = p.Path + } + } + } + return nil +} + +// GetTargets makes the client target definition. +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, "", gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + includedTypesOverrides := args.IncludedTypesOverrides + + if err := sanitizePackagePaths(context, args); err != nil { + klog.Fatalf("cannot sanitize inputs: %v", err) + } + if err := applyGroupOverrides(context.Universe, args); err != nil { + klog.Fatalf("cannot apply group overrides: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + gvToTypes := map[clientgentypes.GroupVersion][]*types.Type{} + groupGoNames := make(map[clientgentypes.GroupVersion]string) + for gv, inputDir := range args.GroupVersionPackages() { + p := context.Universe.Package(inputDir) + + info, err := apidefinitions.Identify(p, apidefinitions.Client, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + continue + } + + // If there's a comment of the form "// +groupGoName=SomeUniqueShortName", use that as + // the Go group identifier in CamelCase. It defaults + groupGoNames[gv] = namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0]) + override, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupGoName"}, p.Comments) + if err != nil { + klog.Fatalf("cannot extract groupGoName tags: %v", err) + } + if override["groupGoName"] != nil { + groupGoNames[gv] = namer.IC(override["groupGoName"][0]) + } + + for n, t := range p.Types { + // filter out types which are not included in user specified overrides. + typesOverride, ok := includedTypesOverrides[gv] + if ok { + found := false + for _, typeStr := range typesOverride { + if typeStr == n { + found = true + break + } + } + if !found { + continue + } + } else { + // User has not specified any override for this group version. + // filter out types which don't have genclient. + if tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)); !tags.GenerateClient { + continue + } + } + if _, found := gvToTypes[gv]; !found { + gvToTypes[gv] = []*types.Type{} + } + gvToTypes[gv] = append(gvToTypes[gv], t) + } + } + + clientsetDir := filepath.Join(args.OutputDir, args.ClientsetName) + clientsetPkg := path.Join(args.OutputPkg, args.ClientsetName) + + var targetList []generator.Target + + targetList = append(targetList, + targetForClientset(args, clientsetDir, clientsetPkg, groupGoNames, boilerplate)) + targetList = append(targetList, + targetForScheme(args, clientsetDir, clientsetPkg, groupGoNames, boilerplate)) + if args.FakeClient { + targetList = append(targetList, + fake.TargetForClientset(args, clientsetDir, clientsetPkg, args.ApplyConfigurationPackage, groupGoNames, boilerplate)) + } + + // If --clientset-only=true, we don't regenerate the individual typed clients. + if args.ClientsetOnly { + return []generator.Target(targetList) + } + + orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)} + gvPackages := args.GroupVersionPackages() + for _, group := range args.Groups { + for _, version := range group.Versions { + gv := clientgentypes.GroupVersion{Group: group.Group, Version: version.Version} + types := gvToTypes[gv] + inputPath := gvPackages[gv] + targetList = append(targetList, + targetForGroup( + gv, orderer.OrderTypes(types), clientsetDir, clientsetPkg, + group.PackageName, groupGoNames[gv], args.ClientsetAPIPath, + inputPath, args.ApplyConfigurationPackage, boilerplate, args.PrefersProtobuf)) + if args.FakeClient { + targetList = append(targetList, + fake.TargetForGroup(gv, orderer.OrderTypes(types), clientsetDir, clientsetPkg, group.PackageName, groupGoNames[gv], inputPath, args.ApplyConfigurationPackage, boilerplate)) + } + } + } + + return targetList +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/fake_client_generator.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/fake_client_generator.go new file mode 100644 index 0000000000..935efec21a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/fake_client_generator.go @@ -0,0 +1,132 @@ +/* +Copyright 2015 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 fake + +import ( + "path" + "path/filepath" + "strings" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" + + "k8s.io/code-generator/cmd/client-gen/args" + scheme "k8s.io/code-generator/cmd/client-gen/generators/scheme" + "k8s.io/code-generator/cmd/client-gen/generators/util" + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" +) + +func TargetForGroup(gv clientgentypes.GroupVersion, typeList []*types.Type, clientsetDir, clientsetPkg string, groupPkgName string, groupGoName string, inputPkg string, applyBuilderPackage string, boilerplate []byte) generator.Target { + // TODO: should make this a function, called by here and in client-generator.go + subdir := []string{"typed", strings.ToLower(groupPkgName), strings.ToLower(gv.Version.NonEmpty())} + outputDir := filepath.Join(clientsetDir, filepath.Join(subdir...), "fake") + outputPkg := path.Join(clientsetPkg, path.Join(subdir...), "fake") + realClientPkg := path.Join(clientsetPkg, path.Join(subdir...)) + + return &generator.SimpleTarget{ + PkgName: "fake", + PkgPath: outputPkg, + PkgDir: outputDir, + HeaderComment: boilerplate, + PkgDocComment: []byte("// Package fake has the automatically generated clients.\n"), + // GeneratorsFunc returns a list of generators. Each generator makes a + // single file. + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = []generator.Generator{ + // Always generate a "doc.go" file. + generator.GoGenerator{OutputFilename: "doc.go"}, + } + // Since we want a file per type that we generate a client for, we + // have to provide a function for this. + for _, t := range typeList { + generators = append(generators, &genFakeForType{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "fake_" + strings.ToLower(c.Namers["private"].Name(t)) + ".go", + }, + outputPackage: outputPkg, + realClientPackage: realClientPkg, + inputPackage: inputPkg, + version: gv.Version.String(), + groupGoName: groupGoName, + typeToMatch: t, + imports: generator.NewImportTrackerForPackage(outputPkg), + applyConfigurationPackage: applyBuilderPackage, + }) + } + + generators = append(generators, &genFakeForGroup{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "fake_" + groupPkgName + "_client.go", + }, + outputPackage: outputPkg, + realClientPackage: realClientPkg, + version: gv.Version.String(), + groupGoName: groupGoName, + types: typeList, + imports: generator.NewImportTrackerForPackage(outputPkg), + }) + return generators + }, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + return util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient + }, + } +} + +func TargetForClientset(args *args.Args, clientsetDir, clientsetPkg string, applyConfigurationPkg string, groupGoNames map[clientgentypes.GroupVersion]string, boilerplate []byte) generator.Target { + return &generator.SimpleTarget{ + // TODO: we'll generate fake clientset for different release in the future. + // Package name and path are hard coded for now. + PkgName: "fake", + PkgPath: path.Join(clientsetPkg, "fake"), + PkgDir: filepath.Join(clientsetDir, "fake"), + HeaderComment: boilerplate, + PkgDocComment: []byte("// This package has the automatically generated fake clientset.\n"), + // GeneratorsFunc returns a list of generators. Each generator generates a + // single file. + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = []generator.Generator{ + // Always generate a "doc.go" file. + generator.GoGenerator{OutputFilename: "doc.go"}, + + &genClientset{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "clientset_generated.go", + }, + groups: args.Groups, + groupGoNames: groupGoNames, + fakeClientsetPackage: clientsetPkg, + imports: generator.NewImportTrackerForPackage(clientsetPkg), + realClientsetPackage: clientsetPkg, + applyConfigurationPackage: applyConfigurationPkg, + }, + &scheme.GenScheme{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "register.go", + }, + InputPackages: args.GroupVersionPackages(), + OutputPkg: clientsetPkg, + Groups: args.Groups, + GroupGoNames: groupGoNames, + ImportTracker: generator.NewImportTrackerForPackage(clientsetPkg), + PrivateScheme: true, + }, + } + return generators + }, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_clientset.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_clientset.go new file mode 100644 index 0000000000..3343942cf6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_clientset.go @@ -0,0 +1,238 @@ +/* +Copyright 2016 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 fake + +import ( + "fmt" + "io" + "path" + "strings" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +// genClientset generates a package for a clientset. +type genClientset struct { + generator.GoGenerator + groups []clientgentypes.GroupVersions + groupGoNames map[clientgentypes.GroupVersion]string + fakeClientsetPackage string // must be a Go import-path + imports namer.ImportTracker + clientsetGenerated bool + // the import path of the generated real clientset. + realClientsetPackage string // must be a Go import-path + applyConfigurationPackage string +} + +var _ generator.Generator = &genClientset{} + +func (g *genClientset) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.fakeClientsetPackage, g.imports), + } +} + +// We only want to call GenerateType() once. +func (g *genClientset) Filter(c *generator.Context, t *types.Type) bool { + ret := !g.clientsetGenerated + g.clientsetGenerated = true + return ret +} + +func (g *genClientset) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + for _, group := range g.groups { + for _, version := range group.Versions { + groupClientPackage := path.Join(g.fakeClientsetPackage, "typed", strings.ToLower(group.PackageName), strings.ToLower(version.NonEmpty())) + fakeGroupClientPackage := path.Join(groupClientPackage, "fake") + + groupAlias := strings.ToLower(g.groupGoNames[clientgentypes.GroupVersion{Group: group.Group, Version: version.Version}]) + imports = append(imports, fmt.Sprintf("%s%s \"%s\"", groupAlias, strings.ToLower(version.NonEmpty()), groupClientPackage)) + imports = append(imports, fmt.Sprintf("fake%s%s \"%s\"", groupAlias, strings.ToLower(version.NonEmpty()), fakeGroupClientPackage)) + } + } + // the package that has the clientset Interface + imports = append(imports, fmt.Sprintf("clientset \"%s\"", g.realClientsetPackage)) + // imports for the code in commonTemplate + imports = append(imports, + "k8s.io/client-go/testing", + "k8s.io/client-go/discovery", + "fakediscovery \"k8s.io/client-go/discovery/fake\"", + "k8s.io/apimachinery/pkg/runtime", + "k8s.io/apimachinery/pkg/watch", + "metav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"", + ) + + return +} + +func (g *genClientset) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + generateApply := len(g.applyConfigurationPackage) > 0 + + // TODO: We actually don't need any type information to generate the clientset, + // perhaps we can adapt the go2ild framework to this kind of usage. + sw := generator.NewSnippetWriter(w, c, "$", "$") + + allGroups := clientgentypes.ToGroupVersionInfo(g.groups, g.groupGoNames) + + sw.Do(common, nil) + + if generateApply { + sw.Do(managedFieldsClientset, map[string]any{ + "newTypeConverter": types.Ref(g.applyConfigurationPackage, "NewTypeConverter"), + }) + } + + sw.Do(checkImpl, nil) + + for _, group := range allGroups { + m := map[string]interface{}{ + "group": group.Group, + "version": group.Version, + "PackageAlias": group.PackageAlias, + "GroupGoName": group.GroupGoName, + "Version": namer.IC(group.Version.String()), + } + + sw.Do(clientsetInterfaceImplTemplate, m) + } + + return sw.Error() +} + +// This part of code is version-independent, unchanging. + +var managedFieldsClientset = ` +// NewClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +// +// Compared to NewSimpleClientset, the Clientset returned here supports field tracking and thus +// server-side apply. Beware though that support in that for CRDs is missing +// (https://github.com/kubernetes/kubernetes/issues/126850). +func NewClientset(objects ...runtime.Object) *Clientset { + o := testing.NewFieldManagedObjectTracker( + scheme, + codecs.UniversalDecoder(), + $.newTypeConverter|raw$(scheme), + ) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchAction, ok := action.(testing.WatchActionImpl); ok { + opts = watchAction.ListOptions + } + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns, opts) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} +` + +var common = ` +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchAction, ok := action.(testing.WatchActionImpl); ok { + opts = watchAction.ListOptions + } + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns, opts) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterfaces { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +// IsWatchListSemanticsUnSupported informs the reflector that this client +// doesn't support WatchList semantics. +// +// This is a synthetic method whose sole purpose is to satisfy the optional +// interface check performed by the reflector. +// Returning true signals that WatchList can NOT be used. +// No additional logic is implemented here. +func (c *Clientset) IsWatchListSemanticsUnSupported() bool { + return true +} +` + +var checkImpl = ` +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) +` + +var clientsetInterfaceImplTemplate = ` +// $.GroupGoName$$.Version$ retrieves the $.GroupGoName$$.Version$Client +func (c *Clientset) $.GroupGoName$$.Version$() $.PackageAlias$.$.GroupGoName$$.Version$Interface { + return &fake$.PackageAlias$.Fake$.GroupGoName$$.Version${Fake: &c.Fake} +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_group.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_group.go new file mode 100644 index 0000000000..04c586a0ed --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_group.go @@ -0,0 +1,131 @@ +/* +Copyright 2015 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 fake + +import ( + "fmt" + "io" + "path" + "strings" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/code-generator/cmd/client-gen/generators/util" +) + +// genFakeForGroup produces a file for a group client, e.g. ExtensionsClient for the extension group. +type genFakeForGroup struct { + generator.GoGenerator + outputPackage string // must be a Go import-path + realClientPackage string // must be a Go import-path + version string + groupGoName string + // types in this group + types []*types.Type + imports namer.ImportTracker + // If the genGroup has been called. This generator should only execute once. + called bool +} + +var _ generator.Generator = &genFakeForGroup{} + +// We only want to call GenerateType() once per group. +func (g *genFakeForGroup) Filter(c *generator.Context, t *types.Type) bool { + if !g.called { + g.called = true + return true + } + return false +} + +func (g *genFakeForGroup) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *genFakeForGroup) Imports(c *generator.Context) (imports []string) { + imports = g.imports.ImportLines() + if len(g.types) != 0 { + imports = append(imports, fmt.Sprintf("%s \"%s\"", strings.ToLower(path.Base(g.realClientPackage)), g.realClientPackage)) + } + return imports +} + +func (g *genFakeForGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + m := map[string]interface{}{ + "GroupGoName": g.groupGoName, + "Version": namer.IC(g.version), + "Fake": c.Universe.Type(types.Name{Package: "k8s.io/client-go/testing", Name: "Fake"}), + "RESTClientInterface": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "Interface"}), + "RESTClient": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "RESTClient"}), + "FakeClient": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "FakeClient"}), + "NewFakeClient": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewFakeClient"}), + } + + sw.Do(groupClientTemplate, m) + for _, t := range g.types { + tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + if err != nil { + return err + } + wrapper := map[string]interface{}{ + "type": t, + "GroupGoName": g.groupGoName, + "Version": namer.IC(g.version), + "realClientPackage": strings.ToLower(path.Base(g.realClientPackage)), + } + if tags.NonNamespaced { + sw.Do(getterImplNonNamespaced, wrapper) + continue + } + sw.Do(getterImplNamespaced, wrapper) + } + sw.Do(getRESTClient, m) + return sw.Error() +} + +var groupClientTemplate = ` +type Fake$.GroupGoName$$.Version$ struct { + *$.Fake|raw$ +} +` + +var getterImplNamespaced = ` +func (c *Fake$.GroupGoName$$.Version$) $.type|publicPlural$(namespace string) $.realClientPackage$.$.type|public$Interface { + return newFake$.type|publicPlural$(c, namespace) +} +` + +var getterImplNonNamespaced = ` +func (c *Fake$.GroupGoName$$.Version$) $.type|publicPlural$() $.realClientPackage$.$.type|public$Interface { + return newFake$.type|publicPlural$(c) +} +` + +var getRESTClient = ` +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *Fake$.GroupGoName$$.Version$) RESTClient() $.RESTClientInterface|raw$ { + var ret *$.RESTClient|raw$ + return ret +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_type.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_type.go new file mode 100644 index 0000000000..6c1410039e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/fake/generator_fake_for_type.go @@ -0,0 +1,543 @@ +/* +Copyright 2015 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 fake + +import ( + "io" + "path" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/code-generator/cmd/client-gen/generators/util" +) + +// genFakeForType produces a file for each top-level type. +type genFakeForType struct { + generator.GoGenerator + outputPackage string // Must be a Go import-path + realClientPackage string // Must be a Go import-path + version string + groupGoName string + inputPackage string + typeToMatch *types.Type + imports namer.ImportTracker + applyConfigurationPackage string +} + +var _ generator.Generator = &genFakeForType{} + +var titler = cases.Title(language.Und) + +// Filter ignores all but one type because we're making a single file per type. +func (g *genFakeForType) Filter(c *generator.Context, t *types.Type) bool { return t == g.typeToMatch } + +func (g *genFakeForType) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *genFakeForType) Imports(c *generator.Context) (imports []string) { + return g.imports.ImportLines() +} + +// GenerateType makes the body of a file implementing the individual typed client for type t. +func (g *genFakeForType) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + if err != nil { + return err + } + + const pkgClientGoTesting = "k8s.io/client-go/testing" + m := map[string]interface{}{ + "type": t, + "inputType": t, + "resultType": t, + "subresourcePath": "", + "namespaced": !tags.NonNamespaced, + "GroupGoName": g.groupGoName, + "Version": namer.IC(g.version), + "realClientInterface": c.Universe.Type(types.Name{Package: g.realClientPackage, Name: t.Name.Name + "Interface"}), + "SchemeGroupVersion": c.Universe.Type(types.Name{Package: t.Name.Package, Name: "SchemeGroupVersion"}), + "CreateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "CreateOptions"}), + "DeleteOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "DeleteOptions"}), + "GetOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "GetOptions"}), + "ListOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"}), + "PatchOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "PatchOptions"}), + "ApplyOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ApplyOptions"}), + "UpdateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "UpdateOptions"}), + "PatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}), + "ApplyPatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "ApplyPatchType"}), + "watchInterface": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"}), + "jsonMarshal": c.Universe.Type(types.Name{Package: "encoding/json", Name: "Marshal"}), + "fmtErrorf": c.Universe.Type(types.Name{Package: "fmt", Name: "Errorf"}), + "contextContext": c.Universe.Type(types.Name{Package: "context", Name: "Context"}), + + "NewRootListActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootListActionWithOptions"}), + "NewListActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewListActionWithOptions"}), + "NewRootGetActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootGetActionWithOptions"}), + "NewGetActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewGetActionWithOptions"}), + "NewRootDeleteActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootDeleteActionWithOptions"}), + "NewDeleteActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewDeleteActionWithOptions"}), + "NewRootUpdateActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootUpdateActionWithOptions"}), + "NewUpdateActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewUpdateActionWithOptions"}), + "NewRootCreateActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootCreateActionWithOptions"}), + "NewCreateActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewCreateActionWithOptions"}), + "NewRootWatchActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootWatchActionWithOptions"}), + "NewWatchActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewWatchActionWithOptions"}), + "NewCreateSubresourceActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewCreateSubresourceActionWithOptions"}), + "NewRootCreateSubresourceActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootCreateSubresourceActionWithOptions"}), + "NewUpdateSubresourceActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewUpdateSubresourceActionWithOptions"}), + "NewGetSubresourceActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewGetSubresourceActionWithOptions"}), + "NewRootGetSubresourceActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootGetSubresourceActionWithOptions"}), + "NewRootUpdateSubresourceActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootUpdateSubresourceActionWithOptions"}), + "NewRootPatchSubresourceActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewRootPatchSubresourceActionWithOptions"}), + "NewPatchSubresourceActionWithOptions": c.Universe.Function(types.Name{Package: pkgClientGoTesting, Name: "NewPatchSubresourceActionWithOptions"}), + "FakeClient": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "FakeClient"}), + "NewFakeClient": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewFakeClient"}), + "FakeClientWithApply": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "FakeClientWithApply"}), + "NewFakeClientWithApply": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewFakeClientWithApply"}), + "FakeClientWithList": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "FakeClientWithList"}), + "NewFakeClientWithList": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewFakeClientWithList"}), + "FakeClientWithListAndApply": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "FakeClientWithListAndApply"}), + "NewFakeClientWithListAndApply": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewFakeClientWithListAndApply"}), + } + + generateApply := len(g.applyConfigurationPackage) > 0 + if generateApply { + // Generated apply builder type references required for generated Apply function + _, gvString := util.ParsePathGroupVersion(g.inputPackage) + m["inputApplyConfig"] = types.Ref(path.Join(g.applyConfigurationPackage, gvString), t.Name.Name+"ApplyConfiguration") + } + + listableOrAppliable := noList | noApply + + if !tags.NoVerbs && tags.HasVerb("list") { + listableOrAppliable |= withList + } + + if !tags.NoVerbs && tags.HasVerb("apply") && generateApply { + listableOrAppliable |= withApply + } + + sw.Do(structType[listableOrAppliable], m) + sw.Do(newStruct[listableOrAppliable], m) + + if tags.NoVerbs { + return sw.Error() + } + + _, typeGVString := util.ParsePathGroupVersion(g.inputPackage) + + // generate extended client methods + for _, e := range tags.Extensions { + if e.HasVerb("apply") && !generateApply { + continue + } + inputType := *t + resultType := *t + inputGVString := typeGVString + if len(e.InputTypeOverride) > 0 { + if name, pkg := e.Input(); len(pkg) > 0 { + _, inputGVString = util.ParsePathGroupVersion(pkg) + newType := c.Universe.Type(types.Name{Package: pkg, Name: name}) + inputType = *newType + } else { + inputType.Name.Name = e.InputTypeOverride + } + } + if len(e.ResultTypeOverride) > 0 { + if name, pkg := e.Result(); len(pkg) > 0 { + newType := c.Universe.Type(types.Name{Package: pkg, Name: name}) + resultType = *newType + } else { + resultType.Name.Name = e.ResultTypeOverride + } + } + m["inputType"] = &inputType + m["resultType"] = &resultType + m["subresourcePath"] = e.SubResourcePath + if e.HasVerb("apply") { + m["inputApplyConfig"] = types.Ref(path.Join(g.applyConfigurationPackage, inputGVString), inputType.Name.Name+"ApplyConfiguration") + } + + if e.HasVerb("get") { + if e.IsSubresource() { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, getSubresourceTemplate), m) + } else { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, getTemplate), m) + } + } + + if e.HasVerb("list") { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, listTemplate), m) + } + + // TODO: Figure out schemantic for watching a sub-resource. + if e.HasVerb("watch") { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, watchTemplate), m) + } + + if e.HasVerb("create") { + if e.IsSubresource() { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, createSubresourceTemplate), m) + } else { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, createTemplate), m) + } + } + + if e.HasVerb("update") { + if e.IsSubresource() { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, updateSubresourceTemplate), m) + } else { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, updateTemplate), m) + } + } + + // TODO: Figure out schemantic for deleting a sub-resource (what arguments + // are passed, does it need two names? etc. + if e.HasVerb("delete") { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, deleteTemplate), m) + } + + if e.HasVerb("patch") { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, patchTemplate), m) + } + + if e.HasVerb("apply") && generateApply { + if e.IsSubresource() { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, applySubresourceTemplate), m) + } else { + sw.Do(adjustTemplate(e.VerbName, e.VerbType, applyTemplate), m) + } + } + } + + return sw.Error() +} + +// adjustTemplate adjust the origin verb template using the expansion name. +// TODO: Make the verbs in templates parametrized so the strings.Replace() is +// not needed. +func adjustTemplate(name, verbType, template string) string { + return strings.ReplaceAll(template, " "+titler.String(verbType), " "+name) +} + +// struct and constructor variants +const ( + // The following values are bits in a bitmask. + // The values which can be set indicate list support and apply support; + // to make the declarations easier to read (like a truth table), corresponding zero-values + // are also declared. + noList = 0 + noApply = 0 + withList = 1 << iota + withApply +) + +// The following string slices are similar to maps, but with combinable keys used as indices. +// Each entry defines whether it supports lists and/or apply; each bit is then toggled: +// * noList, noApply: index 0; +// * withList, noApply: index 1; +// * noList, withApply: index 2; +// * withList, withApply: index 3. +// Go enforces index unicity in these kinds of declarations. + +// struct declarations +var structType = []string{ + noList | noApply: ` + // fake$.type|publicPlural$ implements $.type|public$Interface + type fake$.type|publicPlural$ struct { + *$.FakeClient|raw$[*$.type|raw$] + Fake *Fake$.GroupGoName$$.Version$ + } + `, + withList | noApply: ` + // fake$.type|publicPlural$ implements $.type|public$Interface + type fake$.type|publicPlural$ struct { + *$.FakeClientWithList|raw$[*$.type|raw$, *$.type|raw$List] + Fake *Fake$.GroupGoName$$.Version$ + } + `, + noList | withApply: ` + // fake$.type|publicPlural$ implements $.type|public$Interface + type fake$.type|publicPlural$ struct { + *$.FakeClientWithApply|raw$[*$.type|raw$, *$.inputApplyConfig|raw$] + Fake *Fake$.GroupGoName$$.Version$ + } + `, + withList | withApply: ` + // fake$.type|publicPlural$ implements $.type|public$Interface + type fake$.type|publicPlural$ struct { + *$.FakeClientWithListAndApply|raw$[*$.type|raw$, *$.type|raw$List, *$.inputApplyConfig|raw$] + Fake *Fake$.GroupGoName$$.Version$ + } + `, +} + +// Constructors for the struct, in all variants +var newStruct = []string{ + noList | noApply: ` + func newFake$.type|publicPlural$(fake *Fake$.GroupGoName$$.Version$$if .namespaced$, namespace string$end$) $.realClientInterface|raw$ { + return &fake$.type|publicPlural${ + $.NewFakeClient|raw$[*$.type|raw$]( + fake.Fake, + $if .namespaced$namespace$else$""$end$, + $.SchemeGroupVersion|raw$.WithResource("$.type|resource$"), + $.SchemeGroupVersion|raw$.WithKind("$.type|singularKind$"), + func() *$.type|raw$ {return &$.type|raw${}}, + ), + fake, + } + } + `, + noList | withApply: ` + func newFake$.type|publicPlural$(fake *Fake$.GroupGoName$$.Version$$if .namespaced$, namespace string$end$) $.realClientInterface|raw$ { + return &fake$.type|publicPlural${ + $.NewFakeClientWithApply|raw$[*$.type|raw$, *$.inputApplyConfig|raw$]( + fake.Fake, + $if .namespaced$namespace$else$""$end$, + $.SchemeGroupVersion|raw$.WithResource("$.type|resource$"), + $.SchemeGroupVersion|raw$.WithKind("$.type|singularKind$"), + func() *$.type|raw$ {return &$.type|raw${}}, + ), + fake, + } + } + `, + withList | noApply: ` + func newFake$.type|publicPlural$(fake *Fake$.GroupGoName$$.Version$$if .namespaced$, namespace string$end$) $.realClientInterface|raw$ { + return &fake$.type|publicPlural${ + $.NewFakeClientWithList|raw$[*$.type|raw$, *$.type|raw$List]( + fake.Fake, + $if .namespaced$namespace$else$""$end$, + $.SchemeGroupVersion|raw$.WithResource("$.type|resource$"), + $.SchemeGroupVersion|raw$.WithKind("$.type|singularKind$"), + func() *$.type|raw$ {return &$.type|raw${}}, + func() *$.type|raw$List {return &$.type|raw$List{}}, + func(dst, src *$.type|raw$List) {dst.ListMeta = src.ListMeta}, + func(list *$.type|raw$List) []*$.type|raw$ {return gentype.ToPointerSlice(list.Items)}, + func(list *$.type|raw$List, items []*$.type|raw$) {list.Items = gentype.FromPointerSlice(items)}, + ), + fake, + } + } + `, + withList | withApply: ` + func newFake$.type|publicPlural$(fake *Fake$.GroupGoName$$.Version$$if .namespaced$, namespace string$end$) $.realClientInterface|raw$ { + return &fake$.type|publicPlural${ + $.NewFakeClientWithListAndApply|raw$[*$.type|raw$, *$.type|raw$List, *$.inputApplyConfig|raw$]( + fake.Fake, + $if .namespaced$namespace$else$""$end$, + $.SchemeGroupVersion|raw$.WithResource("$.type|resource$"), + $.SchemeGroupVersion|raw$.WithKind("$.type|singularKind$"), + func() *$.type|raw$ {return &$.type|raw${}}, + func() *$.type|raw$List {return &$.type|raw$List{}}, + func(dst, src *$.type|raw$List) {dst.ListMeta = src.ListMeta}, + func(list *$.type|raw$List) []*$.type|raw$ {return gentype.ToPointerSlice(list.Items)}, + func(list *$.type|raw$List, items []*$.type|raw$) {list.Items = gentype.FromPointerSlice(items)}, + ), + fake, + } + } + `, +} + +var listTemplate = ` +// List takes label and field selectors, and returns the list of $.type|publicPlural$ that match those selectors. +func (c *fake$.type|publicPlural$) List(ctx $.contextContext|raw$, opts $.ListOptions|raw$) (result *$.type|raw$List, err error) { + emptyResult := &$.type|raw$List{} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewListActionWithOptions|raw$(c.Resource(), c.Kind(), c.Namespace(), opts), emptyResult) + $else$Invokes($.NewRootListActionWithOptions|raw$(c.Resource(), c.Kind(), opts), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.type|raw$List), err +} +` + +var getTemplate = ` +// Get takes name of the $.type|private$, and returns the corresponding $.resultType|private$ object, and an error if there is any. +func (c *fake$.type|publicPlural$) Get(ctx $.contextContext|raw$, name string, options $.GetOptions|raw$) (result *$.resultType|raw$, err error) { + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewGetActionWithOptions|raw$(c.Resource(), c.Namespace(), name, options), emptyResult) + $else$Invokes($.NewRootGetActionWithOptions|raw$(c.Resource(), name, options), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` + +var getSubresourceTemplate = ` +// Get takes name of the $.type|private$, and returns the corresponding $.resultType|private$ object, and an error if there is any. +func (c *fake$.type|publicPlural$) Get(ctx $.contextContext|raw$, $.type|private$Name string, options $.GetOptions|raw$) (result *$.resultType|raw$, err error) { + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewGetSubresourceActionWithOptions|raw$(c.Resource(), c.Namespace(), "$.subresourcePath$", $.type|private$Name, options), emptyResult) + $else$Invokes($.NewRootGetSubresourceActionWithOptions|raw$(c.Resource(), "$.subresourcePath$", $.type|private$Name, options), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` + +var deleteTemplate = ` +// Delete takes name of the $.type|private$ and deletes it. Returns an error if one occurs. +func (c *fake$.type|publicPlural$) Delete(ctx $.contextContext|raw$, name string, opts $.DeleteOptions|raw$) error { + _, err := c.Fake. + $if .namespaced$Invokes($.NewDeleteActionWithOptions|raw$(c.Resource(), c.Namespace(), name, opts), &$.type|raw${}) + $else$Invokes($.NewRootDeleteActionWithOptions|raw$(c.Resource(), name, opts), &$.type|raw${})$end$ + return err +} +` + +var createTemplate = ` +// Create takes the representation of a $.inputType|private$ and creates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any. +func (c *fake$.type|publicPlural$) Create(ctx $.contextContext|raw$, $.inputType|private$ *$.inputType|raw$, opts $.CreateOptions|raw$) (result *$.resultType|raw$, err error) { + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewCreateActionWithOptions|raw$(c.Resource(), c.Namespace(), $.inputType|private$, opts), emptyResult) + $else$Invokes($.NewRootCreateActionWithOptions|raw$(c.Resource(), $.inputType|private$, opts), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` + +var createSubresourceTemplate = ` +// Create takes the representation of a $.inputType|private$ and creates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any. +func (c *fake$.type|publicPlural$) Create(ctx $.contextContext|raw$, $.type|private$Name string, $.inputType|private$ *$.inputType|raw$, opts $.CreateOptions|raw$) (result *$.resultType|raw$, err error) { + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewCreateSubresourceActionWithOptions|raw$(c.Resource(), $.type|private$Name, "$.subresourcePath$", c.Namespace(), $.inputType|private$, opts), emptyResult) + $else$Invokes($.NewRootCreateSubresourceActionWithOptions|raw$(c.Resource(), $.type|private$Name, "$.subresourcePath$", $.inputType|private$, opts), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` + +var updateTemplate = ` +// Update takes the representation of a $.inputType|private$ and updates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any. +func (c *fake$.type|publicPlural$) Update(ctx $.contextContext|raw$, $.inputType|private$ *$.inputType|raw$, opts $.UpdateOptions|raw$) (result *$.resultType|raw$, err error) { + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewUpdateActionWithOptions|raw$(c.Resource(), c.Namespace(), $.inputType|private$, opts), emptyResult) + $else$Invokes($.NewRootUpdateActionWithOptions|raw$(c.Resource(), $.inputType|private$, opts), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` + +var updateSubresourceTemplate = ` +// Update takes the representation of a $.inputType|private$ and updates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any. +func (c *fake$.type|publicPlural$) Update(ctx $.contextContext|raw$, $.type|private$Name string, $.inputType|private$ *$.inputType|raw$, opts $.UpdateOptions|raw$) (result *$.resultType|raw$, err error) { + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewUpdateSubresourceActionWithOptions|raw$(c.Resource(), "$.subresourcePath$", c.Namespace(), $.inputType|private$, opts), &$.inputType|raw${}) + $else$Invokes($.NewRootUpdateSubresourceActionWithOptions|raw$(c.Resource(), "$.subresourcePath$", $.inputType|private$, opts), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` + +var watchTemplate = ` +// Watch returns a $.watchInterface|raw$ that watches the requested $.type|privatePlural$. +func (c *fake$.type|publicPlural$) Watch(ctx $.contextContext|raw$, opts $.ListOptions|raw$) ($.watchInterface|raw$, error) { + return c.Fake. + $if .namespaced$InvokesWatch($.NewWatchActionWithOptions|raw$(c.Resource(), c.Namespace(), opts)) + $else$InvokesWatch($.NewRootWatchActionWithOptions|raw$(c.Resource(), opts))$end$ +} +` + +var patchTemplate = ` +// Patch applies the patch and returns the patched $.resultType|private$. +func (c *fake$.type|publicPlural$) Patch(ctx $.contextContext|raw$, name string, pt $.PatchType|raw$, data []byte, opts $.PatchOptions|raw$, subresources ...string) (result *$.resultType|raw$, err error) { + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewPatchSubresourceActionWithOptions|raw$(c.Resource(), c.Namespace(), name, pt, data, opts, subresources... ), emptyResult) + $else$Invokes($.NewRootPatchSubresourceActionWithOptions|raw$(c.Resource(), name, pt, data, opts, subresources...), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` + +var applyTemplate = ` +// Apply takes the given apply declarative configuration, applies it and returns the applied $.resultType|private$. +func (c *fake$.type|publicPlural$) Apply(ctx $.contextContext|raw$, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (result *$.resultType|raw$, err error) { + if $.inputType|private$ == nil { + return nil, $.fmtErrorf|raw$("$.inputType|private$ provided to Apply must not be nil") + } + data, err := $.jsonMarshal|raw$($.inputType|private$) + if err != nil { + return nil, err + } + name := $.inputType|private$.Name + if name == nil { + return nil, $.fmtErrorf|raw$("$.inputType|private$.Name must be provided to Apply") + } + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewPatchSubresourceActionWithOptions|raw$(c.Resource(), c.Namespace(), *name, $.ApplyPatchType|raw$, data, opts.ToPatchOptions()), emptyResult) + $else$Invokes($.NewRootPatchSubresourceActionWithOptions|raw$(c.Resource(), *name, $.ApplyPatchType|raw$, data, opts.ToPatchOptions()), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` + +var applySubresourceTemplate = ` +// Apply takes top resource name and the apply declarative configuration for $.subresourcePath$, +// applies it and returns the applied $.resultType|private$, and an error, if there is any. +func (c *fake$.type|publicPlural$) Apply(ctx $.contextContext|raw$, $.type|private$Name string, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (result *$.resultType|raw$, err error) { + if $.inputType|private$ == nil { + return nil, $.fmtErrorf|raw$("$.inputType|private$ provided to Apply must not be nil") + } + data, err := $.jsonMarshal|raw$($.inputType|private$) + if err != nil { + return nil, err + } + emptyResult := &$.resultType|raw${} + obj, err := c.Fake. + $if .namespaced$Invokes($.NewPatchSubresourceActionWithOptions|raw$(c.Resource(), c.Namespace(), $.type|private$Name, $.ApplyPatchType|raw$, data, opts.ToPatchOptions(), "$.inputType|private$"), emptyResult) + $else$Invokes($.NewRootPatchSubresourceActionWithOptions|raw$(c.Resource(), $.type|private$Name, $.ApplyPatchType|raw$, data, opts.ToPatchOptions(), "$.inputType|private$"), emptyResult)$end$ + if obj == nil { + return emptyResult, err + } + return obj.(*$.resultType|raw$), err +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_clientset.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_clientset.go new file mode 100644 index 0000000000..7609c907b2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_clientset.go @@ -0,0 +1,210 @@ +/* +Copyright 2016 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 generators + +import ( + "fmt" + "io" + "path" + "strings" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +// genClientset generates a package for a clientset. +type genClientset struct { + generator.GoGenerator + groups []clientgentypes.GroupVersions + groupGoNames map[clientgentypes.GroupVersion]string + clientsetPackage string // must be a Go import-path + imports namer.ImportTracker + clientsetGenerated bool +} + +var _ generator.Generator = &genClientset{} + +func (g *genClientset) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.clientsetPackage, g.imports), + } +} + +// We only want to call GenerateType() once. +func (g *genClientset) Filter(c *generator.Context, t *types.Type) bool { + ret := !g.clientsetGenerated + g.clientsetGenerated = true + return ret +} + +func (g *genClientset) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + for _, group := range g.groups { + for _, version := range group.Versions { + typedClientPath := path.Join(g.clientsetPackage, "typed", strings.ToLower(group.PackageName), strings.ToLower(version.NonEmpty())) + groupAlias := strings.ToLower(g.groupGoNames[clientgentypes.GroupVersion{Group: group.Group, Version: version.Version}]) + imports = append(imports, fmt.Sprintf("%s%s \"%s\"", groupAlias, strings.ToLower(version.NonEmpty()), typedClientPath)) + } + } + return +} + +func (g *genClientset) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + // TODO: We actually don't need any type information to generate the clientset, + // perhaps we can adapt the go2ild framework to this kind of usage. + sw := generator.NewSnippetWriter(w, c, "$", "$") + + allGroups := clientgentypes.ToGroupVersionInfo(g.groups, g.groupGoNames) + m := map[string]interface{}{ + "allGroups": allGroups, + "fmtErrorf": c.Universe.Type(types.Name{Package: "fmt", Name: "Errorf"}), + "Config": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "Config"}), + "DefaultKubernetesUserAgent": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "DefaultKubernetesUserAgent"}), + "RESTClientInterface": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "Interface"}), + "RESTHTTPClientFor": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "HTTPClientFor"}), + "DiscoveryInterfaces": c.Universe.Type(types.Name{Package: "k8s.io/client-go/discovery", Name: "DiscoveryInterfaces"}), + "DiscoveryClient": c.Universe.Type(types.Name{Package: "k8s.io/client-go/discovery", Name: "DiscoveryClient"}), + "httpClient": c.Universe.Type(types.Name{Package: "net/http", Name: "Client"}), + "NewDiscoveryClientForConfigAndClient": c.Universe.Function(types.Name{Package: "k8s.io/client-go/discovery", Name: "NewDiscoveryClientForConfigAndClient"}), + "NewDiscoveryClientForConfigOrDie": c.Universe.Function(types.Name{Package: "k8s.io/client-go/discovery", Name: "NewDiscoveryClientForConfigOrDie"}), + "NewDiscoveryClient": c.Universe.Function(types.Name{Package: "k8s.io/client-go/discovery", Name: "NewDiscoveryClient"}), + "flowcontrolNewTokenBucketRateLimiter": c.Universe.Function(types.Name{Package: "k8s.io/client-go/util/flowcontrol", Name: "NewTokenBucketRateLimiter"}), + } + sw.Do(clientsetInterface, m) + sw.Do(clientsetTemplate, m) + for _, g := range allGroups { + sw.Do(clientsetInterfaceImplTemplate, g) + } + sw.Do(getDiscoveryTemplate, m) + sw.Do(newClientsetForConfigTemplate, m) + sw.Do(newClientsetForConfigAndClientTemplate, m) + sw.Do(newClientsetForConfigOrDieTemplate, m) + sw.Do(newClientsetForRESTClientTemplate, m) + + return sw.Error() +} + +var clientsetInterface = ` +type Interface interface { + Discovery() $.DiscoveryInterfaces|raw$ + $range .allGroups$$.GroupGoName$$.Version$() $.PackageAlias$.$.GroupGoName$$.Version$Interface + $end$ +} +` + +var clientsetTemplate = ` +// Clientset contains the clients for groups. +type Clientset struct { + *$.DiscoveryClient|raw$ + $range .allGroups$$.LowerCaseGroupGoName$$.Version$ *$.PackageAlias$.$.GroupGoName$$.Version$Client + $end$ +} +` + +var clientsetInterfaceImplTemplate = ` +// $.GroupGoName$$.Version$ retrieves the $.GroupGoName$$.Version$Client +func (c *Clientset) $.GroupGoName$$.Version$() $.PackageAlias$.$.GroupGoName$$.Version$Interface { + return c.$.LowerCaseGroupGoName$$.Version$ +} +` + +var getDiscoveryTemplate = ` +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() $.DiscoveryInterfaces|raw$ { + if c == nil { + return nil + } + return c.DiscoveryClient +} +` + +var newClientsetForConfigTemplate = ` +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *$.Config|raw$) (*Clientset, error) { + configShallowCopy := *c + + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = $.DefaultKubernetesUserAgent|raw$() + } + + // share the transport between all clients + httpClient, err := $.RESTHTTPClientFor|raw$(&configShallowCopy) + if err != nil { + return nil, err + } + + return NewForConfigAndClient(&configShallowCopy, httpClient) +} +` + +var newClientsetForConfigAndClientTemplate = ` +// NewForConfigAndClient creates a new Clientset for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. +func NewForConfigAndClient(c *$.Config|raw$, httpClient *$.httpClient|raw$) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, $.fmtErrorf|raw$("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = $.flowcontrolNewTokenBucketRateLimiter|raw$(configShallowCopy.QPS, configShallowCopy.Burst) + } + + var cs Clientset + var err error +$range .allGroups$ cs.$.LowerCaseGroupGoName$$.Version$, err =$.PackageAlias$.NewForConfigAndClient(&configShallowCopy, httpClient) + if err!=nil { + return nil, err + } +$end$ + cs.DiscoveryClient, err = $.NewDiscoveryClientForConfigAndClient|raw$(&configShallowCopy, httpClient) + if err!=nil { + return nil, err + } + return &cs, nil +} +` + +var newClientsetForConfigOrDieTemplate = ` +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *$.Config|raw$) *Clientset { + cs, err := NewForConfig(c) + if err!=nil { + panic(err) + } + return cs +} +` + +var newClientsetForRESTClientTemplate = ` +// New creates a new Clientset for the given RESTClient. +func New(c $.RESTClientInterface|raw$) *Clientset { + var cs Clientset +$range .allGroups$ cs.$.LowerCaseGroupGoName$$.Version$ =$.PackageAlias$.New(c) +$end$ + cs.DiscoveryClient = $.NewDiscoveryClient|raw$(c) + return &cs +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_expansion.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_expansion.go new file mode 100644 index 0000000000..5971cc5bcd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_expansion.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 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 generators + +import ( + "io" + "os" + "path/filepath" + "strings" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" +) + +// genExpansion produces a file for a group client, e.g. ExtensionsClient for the extension group. +type genExpansion struct { + generator.GoGenerator + groupPackagePath string + // types in a group + types []*types.Type +} + +// We only want to call GenerateType() once per group. +func (g *genExpansion) Filter(c *generator.Context, t *types.Type) bool { + return len(g.types) == 0 || t == g.types[0] +} + +func (g *genExpansion) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + for _, t := range g.types { + if _, err := os.Stat(filepath.Join(g.groupPackagePath, strings.ToLower(t.Name.Name+"_expansion.go"))); os.IsNotExist(err) { + sw.Do(expansionInterfaceTemplate, t) + } + } + return sw.Error() +} + +var expansionInterfaceTemplate = ` +type $.|public$Expansion interface {} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_group.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_group.go new file mode 100644 index 0000000000..ae86da8380 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_group.go @@ -0,0 +1,260 @@ +/* +Copyright 2015 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 generators + +import ( + "io" + "path" + + "k8s.io/code-generator/cmd/client-gen/generators/util" + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +// genGroup produces a file for a group client, e.g. ExtensionsClient for the extension group. +type genGroup struct { + generator.GoGenerator + outputPackage string + group string + version string + groupGoName string + apiPath string + // types in this group + types []*types.Type + imports namer.ImportTracker + inputPackage string + clientsetPackage string // must be a Go import-path + // If the genGroup has been called. This generator should only execute once. + called bool +} + +var _ generator.Generator = &genGroup{} + +// We only want to call GenerateType() once per group. +func (g *genGroup) Filter(c *generator.Context, t *types.Type) bool { + if !g.called { + g.called = true + return true + } + return false +} + +func (g *genGroup) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *genGroup) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + return +} + +func (g *genGroup) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + // allow user to define a group name that's different from the one parsed from the directory. + p := c.Universe.Package(g.inputPackage) + groupName := g.group + override, ok, err := apidefinitions.GroupNameForPackage(p.Comments) + if err != nil { + return err + } + if ok { + groupName = override + } + + apiPath := `"` + g.apiPath + `"` + if groupName == "" { + apiPath = `"/api"` + } + schemePackage := path.Join(g.clientsetPackage, "scheme") + m := map[string]interface{}{ + "version": g.version, + "groupName": groupName, + "GroupGoName": g.groupGoName, + "Version": namer.IC(g.version), + "types": g.types, + "apiPath": apiPath, + "httpClient": c.Universe.Type(types.Name{Package: "net/http", Name: "Client"}), + "schemaGroupVersion": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersion"}), + "runtimeAPIVersionInternal": c.Universe.Variable(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "APIVersionInternal"}), + "restConfig": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "Config"}), + "restDefaultKubernetesUserAgent": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "DefaultKubernetesUserAgent"}), + "restRESTClientInterface": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "Interface"}), + "RESTHTTPClientFor": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "HTTPClientFor"}), + "restRESTClientFor": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "RESTClientFor"}), + "restRESTClientForConfigAndClient": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "RESTClientForConfigAndClient"}), + "restCodecFactoryForGeneratedClient": c.Universe.Function(types.Name{Package: "k8s.io/client-go/rest", Name: "CodecFactoryForGeneratedClient"}), + "SchemeGroupVersion": c.Universe.Variable(types.Name{Package: g.inputPackage, Name: "SchemeGroupVersion"}), + "SchemePrioritizedVersionsForGroup": c.Universe.Variable(types.Name{Package: schemePackage, Name: "Scheme.PrioritizedVersionsForGroup"}), + "Codecs": c.Universe.Variable(types.Name{Package: schemePackage, Name: "Codecs"}), + "Scheme": c.Universe.Variable(types.Name{Package: schemePackage, Name: "Scheme"}), + } + sw.Do(groupInterfaceTemplate, m) + sw.Do(groupClientTemplate, m) + for _, t := range g.types { + tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + if err != nil { + return err + } + wrapper := map[string]interface{}{ + "type": t, + "GroupGoName": g.groupGoName, + "Version": namer.IC(g.version), + } + if tags.NonNamespaced { + sw.Do(getterImplNonNamespaced, wrapper) + } else { + sw.Do(getterImplNamespaced, wrapper) + } + } + sw.Do(newClientForConfigTemplate, m) + sw.Do(newClientForConfigAndClientTemplate, m) + sw.Do(newClientForConfigOrDieTemplate, m) + sw.Do(newClientForRESTClientTemplate, m) + if g.version == "" { + sw.Do(setInternalVersionClientDefaultsTemplate, m) + } else { + sw.Do(setClientDefaultsTemplate, m) + } + sw.Do(getRESTClient, m) + + return sw.Error() +} + +var groupInterfaceTemplate = ` +type $.GroupGoName$$.Version$Interface interface { + RESTClient() $.restRESTClientInterface|raw$ + $range .types$ $.|publicPlural$Getter + $end$ +} +` + +var groupClientTemplate = ` +// $.GroupGoName$$.Version$Client is used to interact with features provided by the $.groupName$ group. +type $.GroupGoName$$.Version$Client struct { + restClient $.restRESTClientInterface|raw$ +} +` + +var getterImplNamespaced = ` +func (c *$.GroupGoName$$.Version$Client) $.type|publicPlural$(namespace string) $.type|public$Interface { + return new$.type|publicPlural$(c, namespace) +} +` + +var getterImplNonNamespaced = ` +func (c *$.GroupGoName$$.Version$Client) $.type|publicPlural$() $.type|public$Interface { + return new$.type|publicPlural$(c) +} +` + +var newClientForConfigTemplate = ` +// NewForConfig creates a new $.GroupGoName$$.Version$Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *$.restConfig|raw$) (*$.GroupGoName$$.Version$Client, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := $.RESTHTTPClientFor|raw$(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} +` + +var newClientForConfigAndClientTemplate = ` +// NewForConfigAndClient creates a new $.GroupGoName$$.Version$Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *$.restConfig|raw$, h *$.httpClient|raw$) (*$.GroupGoName$$.Version$Client, error) { + config := *c + setConfigDefaults(&config) + client, err := $.restRESTClientForConfigAndClient|raw$(&config, h) + if err != nil { + return nil, err + } + return &$.GroupGoName$$.Version$Client{client}, nil +} +` + +var newClientForConfigOrDieTemplate = ` +// NewForConfigOrDie creates a new $.GroupGoName$$.Version$Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *$.restConfig|raw$) *$.GroupGoName$$.Version$Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} +` + +var getRESTClient = ` +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *$.GroupGoName$$.Version$Client) RESTClient() $.restRESTClientInterface|raw$ { + if c == nil { + return nil + } + return c.restClient +} +` + +var newClientForRESTClientTemplate = ` +// New creates a new $.GroupGoName$$.Version$Client for the given RESTClient. +func New(c $.restRESTClientInterface|raw$) *$.GroupGoName$$.Version$Client { + return &$.GroupGoName$$.Version$Client{c} +} +` + +var setInternalVersionClientDefaultsTemplate = ` +func setConfigDefaults(config *$.restConfig|raw$) { + config.APIPath = $.apiPath$ + if config.UserAgent == "" { + config.UserAgent = $.restDefaultKubernetesUserAgent|raw$() + } + if config.GroupVersion == nil || config.GroupVersion.Group != $.SchemePrioritizedVersionsForGroup|raw$("$.groupName$")[0].Group { + gv := $.SchemePrioritizedVersionsForGroup|raw$("$.groupName$")[0] + config.GroupVersion = &gv + } + config.NegotiatedSerializer = $.restCodecFactoryForGeneratedClient|raw$($.Scheme|raw$, $.Codecs|raw$) + + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } +} +` + +var setClientDefaultsTemplate = ` +func setConfigDefaults(config *$.restConfig|raw$) { + gv := $.SchemeGroupVersion|raw$ + config.GroupVersion = &gv + config.APIPath = $.apiPath$ + config.NegotiatedSerializer = $.restCodecFactoryForGeneratedClient|raw$($.Scheme|raw$, $.Codecs|raw$).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = $.restDefaultKubernetesUserAgent|raw$() + } +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_type.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_type.go new file mode 100644 index 0000000000..5853cff033 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/generator_for_type.go @@ -0,0 +1,842 @@ +/* +Copyright 2015 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 generators + +import ( + "io" + "path" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/code-generator/cmd/client-gen/generators/util" +) + +// genClientForType produces a file for each top-level type. +type genClientForType struct { + generator.GoGenerator + outputPackage string // must be a Go import-path + inputPackage string + clientsetPackage string // must be a Go import-path + applyConfigurationPackage string // must be a Go import-path + group string + version string + groupGoName string + prefersProtobuf bool + typeToMatch *types.Type + imports namer.ImportTracker +} + +var _ generator.Generator = &genClientForType{} + +var titler = cases.Title(language.Und) + +// Filter ignores all but one type because we're making a single file per type. +func (g *genClientForType) Filter(c *generator.Context, t *types.Type) bool { + return t == g.typeToMatch +} + +func (g *genClientForType) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *genClientForType) Imports(c *generator.Context) (imports []string) { + return g.imports.ImportLines() +} + +// Ideally, we'd like genStatus to return true if there is a subresource path +// registered for "status" in the API server, but we do not have that +// information, so genStatus returns true if the type has a status field. +func genStatus(t *types.Type) bool { + // Default to true if we have a Status member + hasStatus := false + for _, m := range t.Members { + if m.Name == "Status" { + hasStatus = true + break + } + } + return hasStatus && !util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).NoStatus +} + +// GenerateType makes the body of a file implementing the individual typed client for type t. +func (g *genClientForType) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + generateApply := len(g.applyConfigurationPackage) > 0 + defaultVerbTemplates := buildDefaultVerbTemplates(generateApply) + subresourceDefaultVerbTemplates := buildSubresourceDefaultVerbTemplates(generateApply) + sw := generator.NewSnippetWriter(w, c, "$", "$") + pkg := path.Base(t.Name.Package) + tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + if err != nil { + return err + } + type extendedInterfaceMethod struct { + template string + args map[string]interface{} + } + _, typeGVString := util.ParsePathGroupVersion(g.inputPackage) + extendedMethods := []extendedInterfaceMethod{} + for _, e := range tags.Extensions { + if e.HasVerb("apply") && !generateApply { + continue + } + inputType := *t + resultType := *t + inputGVString := typeGVString + // TODO: Extract this to some helper method as this code is copied into + // 2 other places. + if len(e.InputTypeOverride) > 0 { + if name, pkg := e.Input(); len(pkg) > 0 { + _, inputGVString = util.ParsePathGroupVersion(pkg) + newType := c.Universe.Type(types.Name{Package: pkg, Name: name}) + inputType = *newType + } else { + inputType.Name.Name = e.InputTypeOverride + } + } + if len(e.ResultTypeOverride) > 0 { + if name, pkg := e.Result(); len(pkg) > 0 { + newType := c.Universe.Type(types.Name{Package: pkg, Name: name}) + resultType = *newType + } else { + resultType.Name.Name = e.ResultTypeOverride + } + } + var updatedVerbtemplate string + if _, exists := subresourceDefaultVerbTemplates[e.VerbType]; e.IsSubresource() && exists { + updatedVerbtemplate = e.VerbName + "(" + strings.TrimPrefix(subresourceDefaultVerbTemplates[e.VerbType], titler.String(e.VerbType)+"(") + } else { + updatedVerbtemplate = e.VerbName + "(" + strings.TrimPrefix(defaultVerbTemplates[e.VerbType], titler.String(e.VerbType)+"(") + } + extendedMethod := extendedInterfaceMethod{ + template: updatedVerbtemplate, + args: map[string]interface{}{ + "type": t, + "inputType": &inputType, + "resultType": &resultType, + "CreateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "CreateOptions"}), + "GetOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "GetOptions"}), + "ListOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"}), + "UpdateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "UpdateOptions"}), + "ApplyOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ApplyOptions"}), + "PatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}), + "PatchOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "PatchOptions"}), + "context": c.Universe.Type(types.Name{Package: "context", Name: "Context"}), + }, + } + if e.HasVerb("apply") { + extendedMethod.args["inputApplyConfig"] = types.Ref(path.Join(g.applyConfigurationPackage, inputGVString), inputType.Name.Name+"ApplyConfiguration") + } + extendedMethods = append(extendedMethods, extendedMethod) + } + m := map[string]interface{}{ + "type": t, + "inputType": t, + "resultType": t, + "package": pkg, + "Package": namer.IC(pkg), + "namespaced": !tags.NonNamespaced, + "Group": namer.IC(g.group), + "subresource": false, + "subresourcePath": "", + "GroupGoName": g.groupGoName, + "prefersProtobuf": g.prefersProtobuf, + "Version": namer.IC(g.version), + "CreateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "CreateOptions"}), + "DeleteOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "DeleteOptions"}), + "GetOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "GetOptions"}), + "ListOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"}), + "PatchOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "PatchOptions"}), + "ApplyOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ApplyOptions"}), + "UpdateOptions": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "UpdateOptions"}), + "PatchType": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/types", Name: "PatchType"}), + "watchInterface": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"}), + "RESTClientInterface": c.Universe.Type(types.Name{Package: "k8s.io/client-go/rest", Name: "Interface"}), + "schemeParameterCodec": c.Universe.Variable(types.Name{Package: path.Join(g.clientsetPackage, "scheme"), Name: "ParameterCodec"}), + "fmtErrorf": c.Universe.Function(types.Name{Package: "fmt", Name: "Errorf"}), + "klogWarningf": c.Universe.Function(types.Name{Package: "k8s.io/klog/v2", Name: "Warningf"}), + "context": c.Universe.Type(types.Name{Package: "context", Name: "Context"}), + "timeDuration": c.Universe.Type(types.Name{Package: "time", Name: "Duration"}), + "timeSecond": c.Universe.Type(types.Name{Package: "time", Name: "Second"}), + "applyNewRequest": c.Universe.Function(types.Name{Package: "k8s.io/client-go/util/apply", Name: "NewRequest"}), + "Client": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "Client"}), + "ClientWithList": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "ClientWithList"}), + "ClientWithApply": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "ClientWithApply"}), + "ClientWithListAndApply": c.Universe.Type(types.Name{Package: "k8s.io/client-go/gentype", Name: "ClientWithListAndApply"}), + "NewClient": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewClient"}), + "NewClientWithApply": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewClientWithApply"}), + "NewClientWithList": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewClientWithList"}), + "NewClientWithListAndApply": c.Universe.Function(types.Name{Package: "k8s.io/client-go/gentype", Name: "NewClientWithListAndApply"}), + } + + if generateApply { + // Generated apply configuration type references required for generated Apply function + _, gvString := util.ParsePathGroupVersion(g.inputPackage) + m["inputApplyConfig"] = types.Ref(path.Join(g.applyConfigurationPackage, gvString), t.Name.Name+"ApplyConfiguration") + } + + sw.Do(getterComment, m) + if tags.NonNamespaced { + sw.Do(getterNonNamespaced, m) + } else { + sw.Do(getterNamespaced, m) + } + + sw.Do(interfaceTemplate1, m) + if !tags.NoVerbs { + if !genStatus(t) { + tags.SkipVerbs = append(tags.SkipVerbs, "updateStatus") + tags.SkipVerbs = append(tags.SkipVerbs, "applyStatus") + } + interfaceSuffix := "" + if len(extendedMethods) > 0 { + interfaceSuffix = "\n" + } + sw.Do("\n"+generateInterface(defaultVerbTemplates, tags)+interfaceSuffix, m) + // add extended verbs into interface + for _, v := range extendedMethods { + sw.Do(v.template+interfaceSuffix, v.args) + } + + } + sw.Do(interfaceTemplate4, m) + + structNamespaced := namespaced + if tags.NonNamespaced { + structNamespaced = nonNamespaced + } + + if tags.NoVerbs { + sw.Do(structType[noList|noApply], m) + sw.Do(newStruct[structNamespaced|noList|noApply], m) + + return sw.Error() + } + + listableOrAppliable := noList | noApply + + if tags.HasVerb("list") { + listableOrAppliable |= withList + } + + if tags.HasVerb("apply") && generateApply { + listableOrAppliable |= withApply + } + + sw.Do(structType[listableOrAppliable], m) + sw.Do(newStruct[structNamespaced|listableOrAppliable], m) + + // generate expansion methods + for _, e := range tags.Extensions { + if e.HasVerb("apply") && !generateApply { + continue + } + inputType := *t + resultType := *t + inputGVString := typeGVString + if len(e.InputTypeOverride) > 0 { + if name, pkg := e.Input(); len(pkg) > 0 { + _, inputGVString = util.ParsePathGroupVersion(pkg) + newType := c.Universe.Type(types.Name{Package: pkg, Name: name}) + inputType = *newType + } else { + inputType.Name.Name = e.InputTypeOverride + } + } + if len(e.ResultTypeOverride) > 0 { + if name, pkg := e.Result(); len(pkg) > 0 { + newType := c.Universe.Type(types.Name{Package: pkg, Name: name}) + resultType = *newType + } else { + resultType.Name.Name = e.ResultTypeOverride + } + } + m["inputType"] = &inputType + m["resultType"] = &resultType + m["subresourcePath"] = e.SubResourcePath + m["verb"] = e.VerbName + if e.HasVerb("apply") { + m["inputApplyConfig"] = types.Ref(path.Join(g.applyConfigurationPackage, inputGVString), inputType.Name.Name+"ApplyConfiguration") + } + + if e.HasVerb("get") { + if e.IsSubresource() { + sw.Do(getSubresourceTemplate, m) + } else { + sw.Do(getTemplate, m) + } + } + + if e.HasVerb("list") { + if e.IsSubresource() { + sw.Do(listSubresourceTemplate, m) + } else { + sw.Do(listTemplate, m) + } + } + + // TODO: Figure out schemantic for watching a sub-resource. + if e.HasVerb("watch") { + sw.Do(watchTemplate, m) + } + + if e.HasVerb("create") { + if e.IsSubresource() { + sw.Do(createSubresourceTemplate, m) + } else { + sw.Do(createTemplate, m) + } + } + + if e.HasVerb("update") { + if e.IsSubresource() { + sw.Do(updateSubresourceTemplate, m) + } else { + sw.Do(updateTemplate, m) + } + } + + // TODO: Figure out schemantic for deleting a sub-resource (what arguments + // are passed, does it need two names? etc. + if e.HasVerb("delete") { + sw.Do(deleteTemplate, m) + } + + if e.HasVerb("patch") { + sw.Do(patchTemplate, m) + } + + if e.HasVerb("apply") { + if e.IsSubresource() { + sw.Do(applySubresourceTemplate, m) + } else { + sw.Do(applyTemplate, m) + } + } + } + + return sw.Error() +} + +func generateInterface(defaultVerbTemplates map[string]string, tags util.Tags) string { + // need an ordered list here to guarantee order of generated methods. + out := []string{} + for _, m := range util.SupportedVerbs { + if tags.HasVerb(m) && len(defaultVerbTemplates[m]) > 0 { + out = append(out, defaultVerbTemplates[m]) + } + } + return strings.Join(out, "\n") +} + +func buildSubresourceDefaultVerbTemplates(generateApply bool) map[string]string { + m := map[string]string{ + "create": `Create(ctx $.context|raw$, $.type|private$Name string, $.inputType|private$ *$.inputType|raw$, opts $.CreateOptions|raw$) (*$.resultType|raw$, error)`, + "list": `List(ctx $.context|raw$, $.type|private$Name string, opts $.ListOptions|raw$) (*$.resultType|raw$List, error)`, + "update": `Update(ctx $.context|raw$, $.type|private$Name string, $.inputType|private$ *$.inputType|raw$, opts $.UpdateOptions|raw$) (*$.resultType|raw$, error)`, + "get": `Get(ctx $.context|raw$, $.type|private$Name string, options $.GetOptions|raw$) (*$.resultType|raw$, error)`, + } + if generateApply { + m["apply"] = `Apply(ctx $.context|raw$, $.type|private$Name string, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (*$.resultType|raw$, error)` + } + return m +} + +func buildDefaultVerbTemplates(generateApply bool) map[string]string { + m := map[string]string{ + "create": `Create(ctx $.context|raw$, $.inputType|private$ *$.inputType|raw$, opts $.CreateOptions|raw$) (*$.resultType|raw$, error)`, + "update": `Update(ctx $.context|raw$, $.inputType|private$ *$.inputType|raw$, opts $.UpdateOptions|raw$) (*$.resultType|raw$, error)`, + "updateStatus": `// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +UpdateStatus(ctx $.context|raw$, $.inputType|private$ *$.type|raw$, opts $.UpdateOptions|raw$) (*$.type|raw$, error)`, + "delete": `Delete(ctx $.context|raw$, name string, opts $.DeleteOptions|raw$) error`, + "deleteCollection": `DeleteCollection(ctx $.context|raw$, opts $.DeleteOptions|raw$, listOpts $.ListOptions|raw$) error`, + "get": `Get(ctx $.context|raw$, name string, opts $.GetOptions|raw$) (*$.resultType|raw$, error)`, + "list": `List(ctx $.context|raw$, opts $.ListOptions|raw$) (*$.resultType|raw$List, error)`, + "watch": `Watch(ctx $.context|raw$, opts $.ListOptions|raw$) ($.watchInterface|raw$, error)`, + "patch": `Patch(ctx $.context|raw$, name string, pt $.PatchType|raw$, data []byte, opts $.PatchOptions|raw$, subresources ...string) (result *$.resultType|raw$, err error)`, + } + if generateApply { + m["apply"] = `Apply(ctx $.context|raw$, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (result *$.resultType|raw$, err error)` + m["applyStatus"] = `// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). +ApplyStatus(ctx $.context|raw$, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (result *$.resultType|raw$, err error)` + } + return m +} + +// group client will implement this interface. +var getterComment = ` +// $.type|publicPlural$Getter has a method to return a $.type|public$Interface. +// A group's client should implement this interface.` + +var getterNamespaced = ` +type $.type|publicPlural$Getter interface { + $.type|publicPlural$(namespace string) $.type|public$Interface +} +` + +var getterNonNamespaced = ` +type $.type|publicPlural$Getter interface { + $.type|publicPlural$() $.type|public$Interface +} +` + +// this type's interface, typed client will implement this interface. +var interfaceTemplate1 = ` +// $.type|public$Interface has methods to work with $.type|public$ resources. +type $.type|public$Interface interface {` + +var interfaceTemplate4 = ` + $.type|public$Expansion +} +` + +// struct and constructor variants +const ( + // The following values are bits in a bitmask. + // The values which can be set indicate namespace support, list support, and apply support; + // to make the declarations easier to read (like a truth table), corresponding zero-values + // are also declared. + namespaced = 0 + noList = 0 + noApply = 0 + nonNamespaced = 1 << iota + withList + withApply +) + +// The following string slices are similar to maps, but with combinable keys used as indices. +// Each entry defines whether it supports lists and/or apply, and if namespacedness matters, +// namespaces; each bit is then toggled: +// * noList, noApply: index 0; +// * withList, noApply: index 2; +// * noList, withApply: index 4; +// * withList, withApply: index 6. +// When namespacedness matters, the namespaced variants are the same as the above, and +// the non-namespaced variants are offset by 1. +// Go enforces index unicity in these kinds of declarations. + +// struct declarations +// Namespacedness does not matter +var structType = []string{ + noList | noApply: ` + // $.type|privatePlural$ implements $.type|public$Interface + type $.type|privatePlural$ struct { + *$.Client|raw$[*$.resultType|raw$] + } + `, + withList | noApply: ` + // $.type|privatePlural$ implements $.type|public$Interface + type $.type|privatePlural$ struct { + *$.ClientWithList|raw$[*$.resultType|raw$, *$.resultType|raw$List] + } + `, + noList | withApply: ` + // $.type|privatePlural$ implements $.type|public$Interface + type $.type|privatePlural$ struct { + *$.ClientWithApply|raw$[*$.resultType|raw$, *$.inputApplyConfig|raw$] + } + `, + withList | withApply: ` + // $.type|privatePlural$ implements $.type|public$Interface + type $.type|privatePlural$ struct { + *$.ClientWithListAndApply|raw$[*$.resultType|raw$, *$.resultType|raw$List, *$.inputApplyConfig|raw$] + } + `, +} + +// Constructors for the struct, in all variants +// Namespacedness matters +var newStruct = []string{ + namespaced | noList | noApply: ` + // new$.type|publicPlural$ returns a $.type|publicPlural$ + func new$.type|publicPlural$(c *$.GroupGoName$$.Version$Client, namespace string) *$.type|privatePlural$ { + return &$.type|privatePlural${ + $.NewClient|raw$[*$.resultType|raw$]( + "$.type|resource$", + c.RESTClient(), + $.schemeParameterCodec|raw$, + namespace, + func() *$.resultType|raw$ { return &$.resultType|raw${} }, + $if .prefersProtobuf$gentype.PrefersProtobuf[*$.resultType|raw$](),$end$ + ), + } + } + `, + namespaced | noList | withApply: ` + // new$.type|publicPlural$ returns a $.type|publicPlural$ + func new$.type|publicPlural$(c *$.GroupGoName$$.Version$Client, namespace string) *$.type|privatePlural$ { + return &$.type|privatePlural${ + $.NewClientWithApply|raw$[*$.resultType|raw$, *$.inputApplyConfig|raw$]( + "$.type|resource$", + c.RESTClient(), + $.schemeParameterCodec|raw$, + namespace, + func() *$.resultType|raw$ { return &$.resultType|raw${} }, + $if .prefersProtobuf$gentype.PrefersProtobuf[*$.resultType|raw$](),$end$ + ), + } + } + `, + namespaced | withList | noApply: ` + // new$.type|publicPlural$ returns a $.type|publicPlural$ + func new$.type|publicPlural$(c *$.GroupGoName$$.Version$Client, namespace string) *$.type|privatePlural$ { + return &$.type|privatePlural${ + $.NewClientWithList|raw$[*$.resultType|raw$, *$.resultType|raw$List]( + "$.type|resource$", + c.RESTClient(), + $.schemeParameterCodec|raw$, + namespace, + func() *$.resultType|raw$ { return &$.resultType|raw${} }, + func() *$.resultType|raw$List { return &$.resultType|raw$List{} }, + $if .prefersProtobuf$gentype.PrefersProtobuf[*$.resultType|raw$](),$end$ + ), + } + } + `, + namespaced | withList | withApply: ` + // new$.type|publicPlural$ returns a $.type|publicPlural$ + func new$.type|publicPlural$(c *$.GroupGoName$$.Version$Client, namespace string) *$.type|privatePlural$ { + return &$.type|privatePlural${ + $.NewClientWithListAndApply|raw$[*$.resultType|raw$, *$.resultType|raw$List, *$.inputApplyConfig|raw$]( + "$.type|resource$", + c.RESTClient(), + $.schemeParameterCodec|raw$, + namespace, + func() *$.resultType|raw$ { return &$.resultType|raw${} }, + func() *$.resultType|raw$List { return &$.resultType|raw$List{} }, + $if .prefersProtobuf$gentype.PrefersProtobuf[*$.resultType|raw$](),$end$ + ), + } + } + `, + nonNamespaced | noList | noApply: ` + // new$.type|publicPlural$ returns a $.type|publicPlural$ + func new$.type|publicPlural$(c *$.GroupGoName$$.Version$Client) *$.type|privatePlural$ { + return &$.type|privatePlural${ + $.NewClient|raw$[*$.resultType|raw$]( + "$.type|resource$", + c.RESTClient(), + $.schemeParameterCodec|raw$, + "", + func() *$.resultType|raw$ { return &$.resultType|raw${} }, + $if .prefersProtobuf$gentype.PrefersProtobuf[*$.resultType|raw$](),$end$ + ), + } + } + `, + nonNamespaced | noList | withApply: ` + // new$.type|publicPlural$ returns a $.type|publicPlural$ + func new$.type|publicPlural$(c *$.GroupGoName$$.Version$Client) *$.type|privatePlural$ { + return &$.type|privatePlural${ + $.NewClientWithApply|raw$[*$.resultType|raw$, *$.inputApplyConfig|raw$]( + "$.type|resource$", + c.RESTClient(), + $.schemeParameterCodec|raw$, + "", + func() *$.resultType|raw$ { return &$.resultType|raw${} }, + $if .prefersProtobuf$gentype.PrefersProtobuf[*$.resultType|raw$](),$end$ + ), + } + } + `, + nonNamespaced | withList | noApply: ` + // new$.type|publicPlural$ returns a $.type|publicPlural$ + func new$.type|publicPlural$(c *$.GroupGoName$$.Version$Client) *$.type|privatePlural$ { + return &$.type|privatePlural${ + $.NewClientWithList|raw$[*$.resultType|raw$, *$.resultType|raw$List]( + "$.type|resource$", + c.RESTClient(), + $.schemeParameterCodec|raw$, + "", + func() *$.resultType|raw$ { return &$.resultType|raw${} }, + func() *$.resultType|raw$List { return &$.resultType|raw$List{} }, + $if .prefersProtobuf$gentype.PrefersProtobuf[*$.resultType|raw$](),$end$ + ), + } + } + `, + nonNamespaced | withList | withApply: ` + // new$.type|publicPlural$ returns a $.type|publicPlural$ + func new$.type|publicPlural$(c *$.GroupGoName$$.Version$Client) *$.type|privatePlural$ { + return &$.type|privatePlural${ + $.NewClientWithListAndApply|raw$[*$.resultType|raw$, *$.resultType|raw$List, *$.inputApplyConfig|raw$]( + "$.type|resource$", + c.RESTClient(), + $.schemeParameterCodec|raw$, + "", + func() *$.resultType|raw$ { return &$.resultType|raw${} }, + func() *$.resultType|raw$List { return &$.resultType|raw$List{} }, + $if .prefersProtobuf$gentype.PrefersProtobuf[*$.resultType|raw$](),$end$ + ), + } + } + `, +} + +var listTemplate = ` +// $.verb$ takes label and field selectors, and returns the list of $.resultType|publicPlural$ that match those selectors. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, opts $.ListOptions|raw$) (result *$.resultType|raw$List, err error) { + var timeout $.timeDuration|raw$ + if opts.TimeoutSeconds != nil{ + timeout = $.timeDuration|raw$(*opts.TimeoutSeconds) * $.timeSecond|raw$ + } + result = &$.resultType|raw$List{} + err = c.GetClient().Get(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + VersionedParams(&opts, $.schemeParameterCodec|raw$). + Timeout(timeout). + Do(ctx). + Into(result) + return +} +` + +var listSubresourceTemplate = ` +// $.verb$ takes $.type|raw$ name, label and field selectors, and returns the list of $.resultType|publicPlural$ that match those selectors. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, $.type|private$Name string, opts $.ListOptions|raw$) (result *$.resultType|raw$List, err error) { + var timeout $.timeDuration|raw$ + if opts.TimeoutSeconds != nil{ + timeout = $.timeDuration|raw$(*opts.TimeoutSeconds) * $.timeSecond|raw$ + } + result = &$.resultType|raw$List{} + err = c.GetClient().Get(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name($.type|private$Name). + SubResource("$.subresourcePath$"). + VersionedParams(&opts, $.schemeParameterCodec|raw$). + Timeout(timeout). + Do(ctx). + Into(result) + return +} +` + +var getTemplate = ` +// $.verb$ takes name of the $.type|private$, and returns the corresponding $.resultType|private$ object, and an error if there is any. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, name string, options $.GetOptions|raw$) (result *$.resultType|raw$, err error) { + result = &$.resultType|raw${} + err = c.GetClient().Get(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name(name). + VersionedParams(&options, $.schemeParameterCodec|raw$). + Do(ctx). + Into(result) + return +} +` + +var getSubresourceTemplate = ` +// $.verb$ takes name of the $.type|private$, and returns the corresponding $.resultType|raw$ object, and an error if there is any. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, $.type|private$Name string, options $.GetOptions|raw$) (result *$.resultType|raw$, err error) { + result = &$.resultType|raw${} + err = c.GetClient().Get(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name($.type|private$Name). + SubResource("$.subresourcePath$"). + VersionedParams(&options, $.schemeParameterCodec|raw$). + Do(ctx). + Into(result) + return +} +` + +var deleteTemplate = ` +// $.verb$ takes name of the $.type|private$ and deletes it. Returns an error if one occurs. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, name string, opts $.DeleteOptions|raw$) error { + return c.GetClient().Delete(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} +` + +var createSubresourceTemplate = ` +// $.verb$ takes the representation of a $.inputType|private$ and creates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, $.type|private$Name string, $.inputType|private$ *$.inputType|raw$, opts $.CreateOptions|raw$) (result *$.resultType|raw$, err error) { + result = &$.resultType|raw${} + err = c.GetClient().Post(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name($.type|private$Name). + SubResource("$.subresourcePath$"). + VersionedParams(&opts, $.schemeParameterCodec|raw$). + Body($.inputType|private$). + Do(ctx). + Into(result) + return +} +` + +var createTemplate = ` +// $.verb$ takes the representation of a $.inputType|private$ and creates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, $.inputType|private$ *$.inputType|raw$, opts $.CreateOptions|raw$) (result *$.resultType|raw$, err error) { + result = &$.resultType|raw${} + err = c.GetClient().Post(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + VersionedParams(&opts, $.schemeParameterCodec|raw$). + Body($.inputType|private$). + Do(ctx). + Into(result) + return +} +` + +var updateSubresourceTemplate = ` +// $.verb$ takes the top resource name and the representation of a $.inputType|private$ and updates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, $.type|private$Name string, $.inputType|private$ *$.inputType|raw$, opts $.UpdateOptions|raw$) (result *$.resultType|raw$, err error) { + result = &$.resultType|raw${} + err = c.GetClient().Put(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name($.type|private$Name). + SubResource("$.subresourcePath$"). + VersionedParams(&opts, $.schemeParameterCodec|raw$). + Body($.inputType|private$). + Do(ctx). + Into(result) + return +} +` + +var updateTemplate = ` +// $.verb$ takes the representation of a $.inputType|private$ and updates it. Returns the server's representation of the $.resultType|private$, and an error, if there is any. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, $.inputType|private$ *$.inputType|raw$, opts $.UpdateOptions|raw$) (result *$.resultType|raw$, err error) { + result = &$.resultType|raw${} + err = c.GetClient().Put(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name($.inputType|private$.Name). + VersionedParams(&opts, $.schemeParameterCodec|raw$). + Body($.inputType|private$). + Do(ctx). + Into(result) + return +} +` + +var watchTemplate = ` +// $.verb$ returns a $.watchInterface|raw$ that watches the requested $.type|privatePlural$. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, opts $.ListOptions|raw$) ($.watchInterface|raw$, error) { + var timeout $.timeDuration|raw$ + if opts.TimeoutSeconds != nil{ + timeout = $.timeDuration|raw$(*opts.TimeoutSeconds) * $.timeSecond|raw$ + } + opts.Watch = true + return c.GetClient().Get(). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + VersionedParams(&opts, $.schemeParameterCodec|raw$). + Timeout(timeout). + Watch(ctx) +} +` +var patchTemplate = ` +// $.verb$ applies the patch and returns the patched $.resultType|private$. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, name string, pt $.PatchType|raw$, data []byte, opts $.PatchOptions|raw$, subresources ...string) (result *$.resultType|raw$, err error) { + result = &$.resultType|raw${} + err = c.GetClient().Patch(pt). + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, $.schemeParameterCodec|raw$). + Body(data). + Do(ctx). + Into(result) + return +} +` + +var applyTemplate = ` +// $.verb$ takes the given apply declarative configuration, applies it and returns the applied $.resultType|private$. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (result *$.resultType|raw$, err error) { + if $.inputType|private$ == nil { + return nil, $.fmtErrorf|raw$("$.inputType|private$ provided to $.verb$ must not be nil") + } + patchOpts := opts.ToPatchOptions() + name := $.inputType|private$.Name + if name == nil { + return nil, $.fmtErrorf|raw$("$.inputType|private$.Name must be provided to $.verb$") + } + request, err := $.applyNewRequest|raw$(c.GetClient(), $.inputType|private$) + if err != nil { + return nil, err + } + result = &$.resultType|raw${} + err = request. + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name(*name). + VersionedParams(&patchOpts, $.schemeParameterCodec|raw$). + Do(ctx). + Into(result) + return +} +` + +var applySubresourceTemplate = ` +// $.verb$ takes top resource name and the apply declarative configuration for $.subresourcePath$, +// applies it and returns the applied $.resultType|private$, and an error, if there is any. +func (c *$.type|privatePlural$) $.verb$(ctx $.context|raw$, $.type|private$Name string, $.inputType|private$ *$.inputApplyConfig|raw$, opts $.ApplyOptions|raw$) (result *$.resultType|raw$, err error) { + if $.inputType|private$ == nil { + return nil, $.fmtErrorf|raw$("$.inputType|private$ provided to $.verb$ must not be nil") + } + patchOpts := opts.ToPatchOptions() + request, err := $.applyNewRequest|raw$(c.GetClient(), $.inputType|private$) + if err != nil { + return nil, err + } + + result = &$.resultType|raw${} + err = request. + $if .prefersProtobuf$UseProtobufAsDefault().$end$ + $if .namespaced$Namespace(c.GetNamespace()).$end$ + Resource("$.type|resource$"). + Name($.type|private$Name). + SubResource("$.subresourcePath$"). + VersionedParams(&patchOpts, $.schemeParameterCodec|raw$). + Do(ctx). + Into(result) + return +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/scheme/generator_for_scheme.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/scheme/generator_for_scheme.go new file mode 100644 index 0000000000..7229055799 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/scheme/generator_for_scheme.go @@ -0,0 +1,187 @@ +/* +Copyright 2017 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 scheme + +import ( + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +// GenScheme produces a package for a clientset with the scheme, codecs and parameter codecs. +type GenScheme struct { + generator.GoGenerator + OutputPkg string // Must be a Go import-path + OutputPath string // optional + Groups []clientgentypes.GroupVersions + GroupGoNames map[clientgentypes.GroupVersion]string + InputPackages map[clientgentypes.GroupVersion]string + ImportTracker namer.ImportTracker + PrivateScheme bool + CreateRegistry bool + schemeGenerated bool +} + +func (g *GenScheme) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.OutputPkg, g.ImportTracker), + } +} + +// We only want to call GenerateType() once. +func (g *GenScheme) Filter(c *generator.Context, t *types.Type) bool { + ret := !g.schemeGenerated + g.schemeGenerated = true + return ret +} + +func (g *GenScheme) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.ImportTracker.ImportLines()...) + for _, group := range g.Groups { + for _, version := range group.Versions { + packagePath := g.InputPackages[clientgentypes.GroupVersion{Group: group.Group, Version: version.Version}] + groupAlias := strings.ToLower(g.GroupGoNames[clientgentypes.GroupVersion{Group: group.Group, Version: version.Version}]) + if g.CreateRegistry { + // import the install package for internal clientsets instead of the type package with register.go + if version.Version != "" { + packagePath = path.Dir(packagePath) + } + packagePath = path.Join(packagePath, "install") + + imports = append(imports, fmt.Sprintf("%s \"%s\"", groupAlias, packagePath)) + break + } else { + imports = append(imports, fmt.Sprintf("%s%s \"%s\"", groupAlias, strings.ToLower(version.Version.NonEmpty()), packagePath)) + } + } + } + return +} + +func (g *GenScheme) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + allGroupVersions := clientgentypes.ToGroupVersionInfo(g.Groups, g.GroupGoNames) + allInstallGroups := clientgentypes.ToGroupInstallPackages(g.Groups, g.GroupGoNames) + + m := map[string]interface{}{ + "publicScheme": !g.PrivateScheme, + "allGroupVersions": allGroupVersions, + "allInstallGroups": allInstallGroups, + "customRegister": false, + "runtimeNewParameterCodec": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "NewParameterCodec"}), + "runtimeNewScheme": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "NewScheme"}), + "serializerNewCodecFactory": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/serializer", Name: "NewCodecFactory"}), + "runtimeScheme": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "Scheme"}), + "runtimeSchemeBuilder": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "SchemeBuilder"}), + "runtimeUtilMust": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/util/runtime", Name: "Must"}), + "schemaGroupVersion": c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersion"}), + "metav1AddToGroupVersion": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "AddToGroupVersion"}), + } + globals := map[string]string{ + "Scheme": "Scheme", + "Codecs": "Codecs", + "ParameterCodec": "ParameterCodec", + "Registry": "Registry", + } + for k, v := range globals { + if g.PrivateScheme { + m[k] = strings.ToLower(v[0:1]) + v[1:] + } else { + m[k] = v + } + } + + sw.Do(globalsTemplate, m) + + if g.OutputPath != "" { + if _, err := os.Stat(filepath.Join(g.OutputPath, strings.ToLower("register_custom.go"))); err == nil { + m["customRegister"] = true + } + } + + if g.CreateRegistry { + sw.Do(registryRegistration, m) + } else { + sw.Do(simpleRegistration, m) + } + + return sw.Error() +} + +var globalsTemplate = ` +var $.Scheme$ = $.runtimeNewScheme|raw$() +var $.Codecs$ = $.serializerNewCodecFactory|raw$($.Scheme$) +$if .publicScheme$var $.ParameterCodec$ = $.runtimeNewParameterCodec|raw$($.Scheme$)$end -$` + +var registryRegistration = ` + +func init() { + $.metav1AddToGroupVersion|raw$($.Scheme$, $.schemaGroupVersion|raw${Version: "v1"}) + Install($.Scheme$) +} + +// Install registers the API group and adds types to a scheme +func Install(scheme *$.runtimeScheme|raw$) { + $- range .allInstallGroups$ + $.InstallPackageAlias$.Install(scheme) + $- end$ + $if .customRegister$ + ExtraInstall(scheme) + $end -$ +} +` + +var simpleRegistration = ` +var localSchemeBuilder = $.runtimeSchemeBuilder|raw${ + $- range .allGroupVersions$ + $.PackageAlias$.AddToScheme, + $- end$ + $if .customRegister$ + ExtraAddToScheme, + $end -$ +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + $.metav1AddToGroupVersion|raw$($.Scheme$, $.schemaGroupVersion|raw${Version: "v1"}) + $.runtimeUtilMust|raw$(AddToScheme($.Scheme$)) +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/gvpackages.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/gvpackages.go new file mode 100644 index 0000000000..fcc1950909 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/gvpackages.go @@ -0,0 +1,30 @@ +/* +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 util + +import "strings" + +func ParsePathGroupVersion(pgvString string) (gvPath string, gvString string) { + subs := strings.Split(pgvString, "/") + length := len(subs) + switch length { + case 0, 1, 2: + return "", pgvString + default: + return strings.Join(subs[:length-2], "/"), strings.Join(subs[length-2:], "/") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/tags.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/tags.go new file mode 100644 index 0000000000..5218dfad3b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/tags.go @@ -0,0 +1,344 @@ +/* +Copyright 2016 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 util + +import ( + "errors" + "fmt" + "strings" + + "k8s.io/gengo/v2" +) + +var supportedTags = []string{ + "genclient", + "genclient:nonNamespaced", + "genclient:noVerbs", + "genclient:onlyVerbs", + "genclient:skipVerbs", + "genclient:noStatus", + "genclient:readonly", + "genclient:method", +} + +// SupportedVerbs is a list of supported verbs for +onlyVerbs and +skipVerbs. +var SupportedVerbs = []string{ + "create", + "update", + "updateStatus", + "delete", + "deleteCollection", + "get", + "list", + "watch", + "patch", + "apply", + "applyStatus", +} + +// ReadonlyVerbs represents a list of read-only verbs. +var ReadonlyVerbs = []string{ + "get", + "list", + "watch", +} + +// genClientPrefix is the default prefix for all genclient tags. +const genClientPrefix = "genclient:" + +// unsupportedExtensionVerbs is a list of verbs we don't support generating +// extension client functions for. +var unsupportedExtensionVerbs = []string{ + "updateStatus", + "deleteCollection", + "watch", + "delete", +} + +// inputTypeSupportedVerbs is a list of verb types that supports overriding the +// input argument type. +var inputTypeSupportedVerbs = []string{ + "create", + "update", + "apply", +} + +// resultTypeSupportedVerbs is a list of verb types that supports overriding the +// resulting type. +var resultTypeSupportedVerbs = []string{ + "create", + "update", + "get", + "list", + "patch", + "apply", +} + +// Extensions allows to extend the default set of client verbs +// (CRUD+watch+patch+list+deleteCollection) for a given type with custom defined +// verbs. Custom verbs can have custom input and result types and also allow to +// use a sub-resource in a request instead of top-level resource type. +// +// Example: +// +// +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale +// +// type ReplicaSet struct { ... } +// +// The 'method=UpdateScale' is the name of the client function. +// The 'verb=update' here means the client function will use 'PUT' action. +// The 'subresource=scale' means we will use SubResource template to generate this client function. +// The 'input' is the input type used for creation (function argument). +// The 'result' (not needed in this case) is the result type returned from the +// client function. +type extension struct { + // VerbName is the name of the custom verb (Scale, Instantiate, etc..) + VerbName string + // VerbType is the type of the verb (only verbs from SupportedVerbs are + // supported) + VerbType string + // SubResourcePath defines a path to a sub-resource to use in the request. + // (optional) + SubResourcePath string + // InputTypeOverride overrides the input parameter type for the verb. By + // default the original type is used. Overriding the input type only works for + // "create" and "update" verb types. The given type must exists in the same + // package as the original type. + // (optional) + InputTypeOverride string + // ResultTypeOverride overrides the resulting object type for the verb. By + // default the original type is used. Overriding the result type works. + // (optional) + ResultTypeOverride string +} + +// IsSubresource indicates if this extension should generate the sub-resource. +func (e *extension) IsSubresource() bool { + return len(e.SubResourcePath) > 0 +} + +// HasVerb checks if the extension matches the given verb. +func (e *extension) HasVerb(verb string) bool { + return e.VerbType == verb +} + +// Input returns the input override package path and the type. +func (e *extension) Input() (string, string) { + parts := strings.Split(e.InputTypeOverride, ".") + return parts[len(parts)-1], strings.Join(parts[0:len(parts)-1], ".") +} + +// Result returns the result override package path and the type. +func (e *extension) Result() (string, string) { + parts := strings.Split(e.ResultTypeOverride, ".") + return parts[len(parts)-1], strings.Join(parts[0:len(parts)-1], ".") +} + +// Tags represents a genclient configuration for a single type. +type Tags struct { + // +genclient + GenerateClient bool + // +genclient:nonNamespaced + NonNamespaced bool + // +genclient:noStatus + NoStatus bool + // +genclient:noVerbs + NoVerbs bool + // +genclient:skipVerbs=get,update + // +genclient:onlyVerbs=create,delete + SkipVerbs []string + // +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale + Extensions []extension +} + +// HasVerb returns true if we should include the given verb in final client interface and +// generate the function for it. +func (t Tags) HasVerb(verb string) bool { + if len(t.SkipVerbs) == 0 { + return true + } + for _, s := range t.SkipVerbs { + if verb == s { + return false + } + } + return true +} + +// MustParseClientGenTags calls ParseClientGenTags but instead of returning error it panics. +func MustParseClientGenTags(lines []string) Tags { + tags, err := ParseClientGenTags(lines) + if err != nil { + panic(err.Error()) + } + return tags +} + +// ParseClientGenTags parse the provided genclient tags and validates that no unknown +// tags are provided. +func ParseClientGenTags(lines []string) (Tags, error) { + ret := Tags{} + values := gengo.ExtractCommentTags("+", lines) + var value []string + value, ret.GenerateClient = values["genclient"] + // Check the old format and error when used to avoid generating client when //+genclient=false + if len(value) > 0 && len(value[0]) > 0 { + return ret, fmt.Errorf("+genclient=%s is invalid, use //+genclient if you want to generate client or omit it when you want to disable generation", value) + } + _, ret.NonNamespaced = values[genClientPrefix+"nonNamespaced"] + // Check the old format and error when used + if value := values["nonNamespaced"]; len(value) > 0 && len(value[0]) > 0 { + return ret, fmt.Errorf("+nonNamespaced=%s is invalid, use //+genclient:nonNamespaced instead", value[0]) + } + _, ret.NoVerbs = values[genClientPrefix+"noVerbs"] + _, ret.NoStatus = values[genClientPrefix+"noStatus"] + onlyVerbs := []string{} + if _, isReadonly := values[genClientPrefix+"readonly"]; isReadonly { + onlyVerbs = ReadonlyVerbs + } + // Check the old format and error when used + if value := values["readonly"]; len(value) > 0 && len(value[0]) > 0 { + return ret, fmt.Errorf("+readonly=%s is invalid, use //+genclient:readonly instead", value[0]) + } + if v, exists := values[genClientPrefix+"skipVerbs"]; exists { + ret.SkipVerbs = strings.Split(v[0], ",") + } + if v, exists := values[genClientPrefix+"onlyVerbs"]; exists || len(onlyVerbs) > 0 { + if len(v) > 0 { + onlyVerbs = append(onlyVerbs, strings.Split(v[0], ",")...) + } + skipVerbs := []string{} + for _, m := range SupportedVerbs { + skip := true + for _, o := range onlyVerbs { + if o == m { + skip = false + break + } + } + // Check for conflicts + for _, v := range skipVerbs { + if v == m { + return ret, fmt.Errorf("verb %q used both in genclient:skipVerbs and genclient:onlyVerbs", v) + } + } + if skip { + skipVerbs = append(skipVerbs, m) + } + } + ret.SkipVerbs = skipVerbs + } + var err error + if ret.Extensions, err = parseClientExtensions(values); err != nil { + return ret, err + } + return ret, validateClientGenTags(values) +} + +func parseClientExtensions(tags map[string][]string) ([]extension, error) { + var ret []extension + for name, values := range tags { + if !strings.HasPrefix(name, genClientPrefix+"method") { + continue + } + for _, value := range values { + // the value comes in this form: "Foo,verb=create" + ext := extension{} + parts := strings.Split(value, ",") + if len(parts) == 0 { + return nil, fmt.Errorf("invalid of empty extension verb name: %q", value) + } + // The first part represents the name of the extension + ext.VerbName = parts[0] + if len(ext.VerbName) == 0 { + return nil, fmt.Errorf("must specify a verb name (// +genclient:method=Foo,verb=create)") + } + // Parse rest of the arguments + params := parts[1:] + for _, p := range params { + parts := strings.Split(p, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid extension tag specification %q", p) + } + key, val := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + if len(val) == 0 { + return nil, fmt.Errorf("empty value of %q for %q extension", key, ext.VerbName) + } + switch key { + case "verb": + ext.VerbType = val + case "subresource": + ext.SubResourcePath = val + case "input": + ext.InputTypeOverride = val + case "result": + ext.ResultTypeOverride = val + default: + return nil, fmt.Errorf("unknown extension configuration key %q", key) + } + } + // Validate resulting extension configuration + if len(ext.VerbType) == 0 { + return nil, fmt.Errorf("verb type must be specified (use '// +genclient:method=%s,verb=create')", ext.VerbName) + } + if len(ext.ResultTypeOverride) > 0 { + supported := false + for _, v := range resultTypeSupportedVerbs { + if ext.VerbType == v { + supported = true + break + } + } + if !supported { + return nil, fmt.Errorf("%s: result type is not supported for %q verbs (supported verbs: %#v)", ext.VerbName, ext.VerbType, resultTypeSupportedVerbs) + } + } + if len(ext.InputTypeOverride) > 0 { + supported := false + for _, v := range inputTypeSupportedVerbs { + if ext.VerbType == v { + supported = true + break + } + } + if !supported { + return nil, fmt.Errorf("%s: input type is not supported for %q verbs (supported verbs: %#v)", ext.VerbName, ext.VerbType, inputTypeSupportedVerbs) + } + } + for _, t := range unsupportedExtensionVerbs { + if ext.VerbType == t { + return nil, fmt.Errorf("verb %q is not supported by extension generator", ext.VerbType) + } + } + ret = append(ret, ext) + } + } + return ret, nil +} + +// validateTags validates that only supported genclient tags were provided. +func validateClientGenTags(values map[string][]string) error { + for _, k := range supportedTags { + delete(values, k) + } + for key := range values { + if strings.HasPrefix(key, strings.TrimSuffix(genClientPrefix, ":")) { + return errors.New("unknown tag detected: " + key) + } + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/tags_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/tags_test.go new file mode 100644 index 0000000000..b9ffcfbbba --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/generators/util/tags_test.go @@ -0,0 +1,148 @@ +/* +Copyright 2016 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 util + +import ( + "reflect" + "testing" +) + +func TestParseTags(t *testing.T) { + testCases := map[string]struct { + lines []string + expectTags Tags + expectError bool + }{ + "genclient": { + lines: []string{`+genclient`}, + expectTags: Tags{GenerateClient: true}, + }, + "genclient=true": { + lines: []string{`+genclient=true`}, + expectError: true, + }, + "nonNamespaced=true": { + lines: []string{`+genclient=true`, `+nonNamespaced=true`}, + expectError: true, + }, + "readonly=true": { + lines: []string{`+genclient=true`, `+readonly=true`}, + expectError: true, + }, + "genclient:nonNamespaced": { + lines: []string{`+genclient`, `+genclient:nonNamespaced`}, + expectTags: Tags{GenerateClient: true, NonNamespaced: true}, + }, + "genclient:noVerbs": { + lines: []string{`+genclient`, `+genclient:noVerbs`}, + expectTags: Tags{GenerateClient: true, NoVerbs: true}, + }, + "genclient:noStatus": { + lines: []string{`+genclient`, `+genclient:noStatus`}, + expectTags: Tags{GenerateClient: true, NoStatus: true}, + }, + "genclient:onlyVerbs": { + lines: []string{`+genclient`, `+genclient:onlyVerbs=create,delete`}, + expectTags: Tags{GenerateClient: true, SkipVerbs: []string{"update", "updateStatus", "deleteCollection", "get", "list", "watch", "patch", "apply", "applyStatus"}}, + }, + "genclient:readonly": { + lines: []string{`+genclient`, `+genclient:readonly`}, + expectTags: Tags{GenerateClient: true, SkipVerbs: []string{"create", "update", "updateStatus", "delete", "deleteCollection", "patch", "apply", "applyStatus"}}, + }, + "genclient:conflict": { + lines: []string{`+genclient`, `+genclient:onlyVerbs=create`, `+genclient:skipVerbs=create`}, + expectError: true, + }, + "genclient:invalid": { + lines: []string{`+genclient`, `+genclient:invalid`}, + expectError: true, + }, + } + for key, c := range testCases { + result, err := ParseClientGenTags(c.lines) + if err != nil && !c.expectError { + t.Fatalf("unexpected error: %v", err) + } + if !c.expectError && !reflect.DeepEqual(result, c.expectTags) { + t.Errorf("[%s] expected %#v to be %#v", key, result, c.expectTags) + } + } +} + +func TestParseTagsExtension(t *testing.T) { + testCases := map[string]struct { + lines []string + expectedExtensions []extension + expectError bool + }{ + "simplest extension": { + lines: []string{`+genclient:method=Foo,verb=create`}, + expectedExtensions: []extension{{VerbName: "Foo", VerbType: "create"}}, + }, + "multiple extensions": { + lines: []string{`+genclient:method=Foo,verb=create`, `+genclient:method=Bar,verb=get`}, + expectedExtensions: []extension{{VerbName: "Foo", VerbType: "create"}, {VerbName: "Bar", VerbType: "get"}}, + }, + "extension without verb": { + lines: []string{`+genclient:method`}, + expectError: true, + }, + "extension without verb type": { + lines: []string{`+genclient:method=Foo`}, + expectError: true, + }, + "sub-resource extension": { + lines: []string{`+genclient:method=Foo,verb=create,subresource=bar`}, + expectedExtensions: []extension{{VerbName: "Foo", VerbType: "create", SubResourcePath: "bar"}}, + }, + "output type extension": { + lines: []string{`+genclient:method=Foos,verb=list,result=Bars`}, + expectedExtensions: []extension{{VerbName: "Foos", VerbType: "list", ResultTypeOverride: "Bars"}}, + }, + "input type extension": { + lines: []string{`+genclient:method=Foo,verb=update,input=Bar`}, + expectedExtensions: []extension{{VerbName: "Foo", VerbType: "update", InputTypeOverride: "Bar"}}, + }, + "unknown verb type extension": { + lines: []string{`+genclient:method=Foo,verb=explode`}, + expectedExtensions: nil, + expectError: true, + }, + "invalid verb extension": { + lines: []string{`+genclient:method=Foo,unknown=bar`}, + expectedExtensions: nil, + expectError: true, + }, + "empty verb extension subresource": { + lines: []string{`+genclient:method=Foo,verb=get,subresource=`}, + expectedExtensions: nil, + expectError: true, + }, + } + for key, c := range testCases { + result, err := ParseClientGenTags(c.lines) + if err != nil && !c.expectError { + t.Fatalf("[%s] unexpected error: %v", key, err) + } + if err != nil && c.expectError { + t.Logf("[%s] got expected error: %+v", key, err) + } + if !c.expectError && !reflect.DeepEqual(result.Extensions, c.expectedExtensions) { + t.Errorf("[%s] expected %#+v to be %#+v", key, result.Extensions, c.expectedExtensions) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/main.go new file mode 100644 index 0000000000..0afda40f5e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/main.go @@ -0,0 +1,70 @@ +/* +Copyright 2015 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. +*/ + +// client-gen makes the individual typed clients using gengo. +package main + +import ( + "flag" + "slices" + + "github.com/spf13/pflag" + "k8s.io/klog/v2" + + "k8s.io/code-generator/cmd/client-gen/args" + "k8s.io/code-generator/cmd/client-gen/generators" + "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" +) + +func main() { + klog.InitFlags(nil) + args := args.New() + + args.AddFlags(pflag.CommandLine, "k8s.io/kubernetes/pkg/apis") // TODO: move this input path out of client-gen + flag.Set("logtostderr", "true") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + // add group version package as input dirs for gengo + inputPkgs := []string{} + for _, pkg := range args.Groups { + for _, v := range pkg.Versions { + inputPkgs = append(inputPkgs, v.Package) + } + } + // ensure stable code generation output + slices.Sort(inputPkgs) + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return generators.GetTargets(context, args) + } + + if err := gengo.Execute( + generators.NameSystems(util.PluralExceptionListToMapOrDie(args.PluralExceptions)), + generators.DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + inputPkgs, + ); err != nil { + klog.Fatalf("Error: %v", err) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/helpers.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/helpers.go new file mode 100644 index 0000000000..c84a775317 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/helpers.go @@ -0,0 +1,121 @@ +/* +Copyright 2016 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 types + +import ( + "fmt" + "regexp" + "sort" + "strings" + + "k8s.io/gengo/v2/namer" +) + +// ToGroupVersion turns "group/version" string into a GroupVersion struct. It reports error +// if it cannot parse the string. +func ToGroupVersion(gv string) (GroupVersion, error) { + // this can be the internal version for the legacy kube types + // TODO once we've cleared the last uses as strings, this special case should be removed. + if (len(gv) == 0) || (gv == "/") { + return GroupVersion{}, nil + } + + switch strings.Count(gv, "/") { + case 0: + return GroupVersion{Group(gv), ""}, nil + case 1: + i := strings.Index(gv, "/") + return GroupVersion{Group(gv[:i]), Version(gv[i+1:])}, nil + default: + return GroupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv) + } +} + +type sortableSliceOfVersions []string + +func (a sortableSliceOfVersions) Len() int { return len(a) } +func (a sortableSliceOfVersions) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a sortableSliceOfVersions) Less(i, j int) bool { + vi, vj := strings.TrimLeft(a[i], "v"), strings.TrimLeft(a[j], "v") + major := regexp.MustCompile("^[0-9]+") + viMajor, vjMajor := major.FindString(vi), major.FindString(vj) + viRemaining, vjRemaining := strings.TrimLeft(vi, viMajor), strings.TrimLeft(vj, vjMajor) + switch { + case len(viRemaining) == 0 && len(vjRemaining) == 0: + return viMajor < vjMajor + case len(viRemaining) == 0 && len(vjRemaining) != 0: + // stable version is greater than unstable version + return false + case len(viRemaining) != 0 && len(vjRemaining) == 0: + // stable version is greater than unstable version + return true + } + // neither are stable versions + if viMajor != vjMajor { + return viMajor < vjMajor + } + // assuming at most we have one alpha or one beta version, so if vi contains "alpha", it's the lesser one. + return strings.Contains(viRemaining, "alpha") +} + +// Determine the default version among versions. If a user calls a group client +// without specifying the version (e.g., c.CoreV1(), instead of c.CoreV1()), the +// default version will be returned. +func defaultVersion(versions []PackageVersion) Version { + var versionStrings []string + for _, version := range versions { + versionStrings = append(versionStrings, version.Version.String()) + } + sort.Sort(sortableSliceOfVersions(versionStrings)) + return Version(versionStrings[len(versionStrings)-1]) +} + +// ToGroupVersionInfo is a helper function used by generators for groups. +func ToGroupVersionInfo(groups []GroupVersions, groupGoNames map[GroupVersion]string) []GroupVersionInfo { + var groupVersionPackages []GroupVersionInfo + for _, group := range groups { + for _, version := range group.Versions { + groupGoName := groupGoNames[GroupVersion{Group: group.Group, Version: version.Version}] + groupVersionPackages = append(groupVersionPackages, GroupVersionInfo{ + Group: Group(namer.IC(group.Group.NonEmpty())), + Version: Version(namer.IC(version.Version.String())), + PackageAlias: strings.ToLower(groupGoName + version.Version.NonEmpty()), + GroupGoName: groupGoName, + LowerCaseGroupGoName: namer.IL(groupGoName), + }) + } + } + return groupVersionPackages +} + +func ToGroupInstallPackages(groups []GroupVersions, groupGoNames map[GroupVersion]string) []GroupInstallPackage { + var groupInstallPackages []GroupInstallPackage + for _, group := range groups { + defaultVersion := defaultVersion(group.Versions) + groupGoName := groupGoNames[GroupVersion{Group: group.Group, Version: defaultVersion}] + groupInstallPackages = append(groupInstallPackages, GroupInstallPackage{ + Group: Group(namer.IC(group.Group.NonEmpty())), + InstallPackageAlias: strings.ToLower(groupGoName), + }) + } + return groupInstallPackages +} + +// NormalizeGroupVersion calls normalizes the GroupVersion. +// func NormalizeGroupVersion(gv GroupVersion) GroupVersion { +// return GroupVersion{Group: gv.Group.NonEmpty(), Version: gv.Version, NonEmptyVersion: normalization.Version(gv.Version)} +// } diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/helpers_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/helpers_test.go new file mode 100644 index 0000000000..dec62dff21 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/helpers_test.go @@ -0,0 +1,32 @@ +/* +Copyright 2016 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 types + +import ( + "reflect" + "sort" + "testing" +) + +func TestVersionSort(t *testing.T) { + unsortedVersions := []string{"v4beta1", "v2beta1", "v2alpha1", "v3", "v1"} + expected := []string{"v2alpha1", "v2beta1", "v4beta1", "v1", "v3"} + sort.Sort(sortableSliceOfVersions(unsortedVersions)) + if !reflect.DeepEqual(unsortedVersions, expected) { + t.Errorf("expected %#v\ngot %#v", expected, unsortedVersions) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/types.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/types.go new file mode 100644 index 0000000000..df030e8459 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/client-gen/types/types.go @@ -0,0 +1,109 @@ +/* +Copyright 2016 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 types + +import "strings" + +type Version string + +func (v Version) String() string { + return string(v) +} + +func (v Version) NonEmpty() string { + if v == "" { + return "internalVersion" + } + return v.String() +} + +func (v Version) PackageName() string { + return strings.ToLower(v.NonEmpty()) +} + +type Group string + +func (g Group) String() string { + return string(g) +} + +func (g Group) NonEmpty() string { + if g == "" { + return "core" + } + return string(g) +} + +func (g Group) PackageName() string { + parts := strings.Split(g.NonEmpty(), ".") + if parts[0] == "internal" && len(parts) > 1 { + return strings.ToLower(parts[1] + parts[0]) + } + return strings.ToLower(parts[0]) +} + +type Kind string + +type PackageVersion struct { + Version + // The fully qualified package, e.g. k8s.io/kubernetes/pkg/apis/apps, where the types.go is found. + Package string +} + +type GroupVersion struct { + Group Group + Version Version +} + +type GroupVersionKind struct { + Group Group + Version Version + Kind Kind +} + +func (gv GroupVersion) ToAPIVersion() string { + if len(gv.Group) > 0 && gv.Group != "" { + return gv.Group.String() + "/" + gv.Version.String() + } else { + return gv.Version.String() + } +} + +func (gv GroupVersion) WithKind(kind Kind) GroupVersionKind { + return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind} +} + +type GroupVersions struct { + // The name of the package for this group, e.g. apps. + PackageName string + Group Group + Versions []PackageVersion +} + +// GroupVersionInfo contains all the info around a group version. +type GroupVersionInfo struct { + Group Group + Version Version + PackageAlias string + GroupGoName string + LowerCaseGroupGoName string +} + +type GroupInstallPackage struct { + Group Group + InstallPackageAlias string +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/args/args.go new file mode 100644 index 0000000000..5727858326 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/args/args.go @@ -0,0 +1,102 @@ +/* +Copyright 2017 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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2" +) + +// DefaultBasePeerDirs are the peer-dirs nearly everybody will use, i.e. those coming from +// apimachinery. +var DefaultBasePeerDirs = []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1", + "k8s.io/apimachinery/pkg/conversion", + "k8s.io/apimachinery/pkg/runtime", +} + +type Args struct { + // The filename of the generated results. + OutputFile string + + // Base peer dirs which nearly everybody will use, i.e. outside of Kubernetes core. Peer dirs + // are declared to make the generator pick up manually written conversion funcs from external + // packages. + BasePeerDirs []string + + // Custom peer dirs which are application specific. Peer dirs are declared to make the + // generator pick up manually written conversion funcs from external packages. + ExtraPeerDirs []string + + // SkipUnsafe indicates whether to generate unsafe conversions to improve the efficiency + // of these operations. The unsafe operation is a direct pointer assignment via unsafe + // (within the allowed uses of unsafe) and is equivalent to a proposed Golang change to + // allow structs that are identical to be assigned to each other. + SkipUnsafe bool + + // GoHeaderFile is the path to a boilerplate header file for generated + // code. + GoHeaderFile string + + // GeneratedBuildTag is the tag used to identify code generated by execution + // of this type. Each generator should use a different tag, and different + // groups of generators (external API that depends on Kube generations) should + // keep tags distinct as well. + GeneratedBuildTag string + + apidefinitions.LintArgs +} + +// New returns default arguments for the generator. +func New() *Args { + return &Args{ + BasePeerDirs: DefaultBasePeerDirs, + SkipUnsafe: false, + GeneratedBuildTag: gengo.StdBuildTag, + } +} + +// AddFlags add the generator flags to the flag set. +func (args *Args) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&args.OutputFile, "output-file", "generated.conversion.go", + "the name of the file to be generated") + fs.StringSliceVar(&args.BasePeerDirs, "base-peer-dirs", args.BasePeerDirs, + "Comma-separated list of apimachinery import paths which are considered, after tag-specified peers, for conversions. Only change these if you have very good reasons.") + fs.StringSliceVar(&args.ExtraPeerDirs, "extra-peer-dirs", args.ExtraPeerDirs, + "Application specific comma-separated list of import paths which are considered, after tag-specified peers and base-peer-dirs, for conversions.") + fs.BoolVar(&args.SkipUnsafe, "skip-unsafe", args.SkipUnsafe, + "If true, will not generate code using unsafe pointer conversions; resulting code may be slower.") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + fs.StringVar(&args.GeneratedBuildTag, "build-tag", args.GeneratedBuildTag, "A Go build tag to use to identify files generated by this command. Should be unique.") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputFile) == 0 { + return fmt.Errorf("--output-file must be specified") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go new file mode 100644 index 0000000000..f6e5c0d6e4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/generators/conversion.go @@ -0,0 +1,1352 @@ +/* +Copyright 2016 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 generators + +import ( + "bytes" + "fmt" + "io" + "path" + "reflect" + "sort" + "strings" + + "k8s.io/code-generator/cmd/conversion-gen/args" + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +// These are the comment tags that carry parameters for conversion generation. +const ( + // e.g., "+k8s:conversion-gen=" in doc.go, where is the + // import path of the package the peer types are defined in. + // e.g., "+k8s:conversion-gen=false" in a type's comment will let + // conversion-gen skip that type. + tagName = "k8s:conversion-gen" + // e.g. "+k8s:conversion-gen:explicit-from=net/url.Values" in the type comment + // will result in generating conversion from net/url.Values. + explicitFromTagName = "k8s:conversion-gen:explicit-from" +) + +func extractTagValues(tagName string, comments []string) ([]string, error) { + tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{tagName}, comments) + if err != nil { + return nil, err + } + tagList, exists := tags[tagName] + if !exists { + return nil, nil + } + values := make([]string, len(tagList)) + for i, v := range tagList { + values[i] = v.Value + } + return values, nil +} + +func extractTag(comments []string) ([]string, error) { + return extractTagValues(tagName, comments) +} + +func extractExplicitFromTag(comments []string) ([]string, error) { + return extractTagValues(explicitFromTagName, comments) +} + +func isCopyOnly(comments []string) (bool, error) { + values, err := extractTagValues("k8s:conversion-fn", comments) + if err != nil { + return false, err + } + return len(values) == 1 && values[0] == "copy-only", nil +} + +func isDrop(comments []string) (bool, error) { + values, err := extractTagValues("k8s:conversion-fn", comments) + if err != nil { + return false, err + } + return len(values) == 1 && values[0] == "drop", nil +} + +// TODO: This is created only to reduce number of changes in a single PR. +// Remove it and use PublicNamer instead. +func conversionNamer() *namer.NameStrategy { + return &namer.NameStrategy{ + Join: func(pre string, in []string, post string) string { + return strings.Join(in, "_") + }, + PrependPackageNames: 1, + } +} + +func defaultFnNamer() *namer.NameStrategy { + return &namer.NameStrategy{ + Prefix: "SetDefaults_", + Join: func(pre string, in []string, post string) string { + return pre + strings.Join(in, "_") + post + }, + } +} + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{ + "public": conversionNamer(), + "raw": namer.NewRawNamer("", nil), + "defaultfn": defaultFnNamer(), + } +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +func getPeerTypeFor(context *generator.Context, t *types.Type, potenialPeerPkgs []string) *types.Type { + for _, ppp := range potenialPeerPkgs { + p := context.Universe.Package(ppp) + if p == nil { + continue + } + if p.Has(t.Name.Name) { + return p.Type(t.Name.Name) + } + } + return nil +} + +type conversionPair struct { + inType *types.Type + outType *types.Type +} + +// All of the types in conversions map are of type "DeclarationOf" with +// the underlying type being "Func". +type conversionFuncMap map[conversionPair]*types.Type + +// Returns all manually-defined conversion functions in the package. +func getManualConversionFunctions(context *generator.Context, pkg *types.Package, manualMap conversionFuncMap) { + if pkg == nil { + klog.Warning("Skipping nil package passed to getManualConversionFunctions") + return + } + klog.V(3).Infof("Scanning for conversion functions in %v", pkg.Path) + + scopeName := types.Ref(conversionPackagePath, "Scope").Name + errorName := types.Ref("", "error").Name + buffer := &bytes.Buffer{} + sw := generator.NewSnippetWriter(buffer, context, "$", "$") + + for _, f := range pkg.Functions { + if f.Underlying == nil || f.Underlying.Kind != types.Func { + klog.Errorf("Malformed function: %#v", f) + continue + } + if f.Underlying.Signature == nil { + klog.Errorf("Function without signature: %#v", f) + continue + } + klog.V(6).Infof("Considering function %s", f.Name) + signature := f.Underlying.Signature + // Check whether the function is conversion function. + // Note that all of them have signature: + // func Convert_inType_To_outType(inType, outType, conversion.Scope) error + if signature.Receiver != nil { + klog.V(6).Infof("%s has a receiver", f.Name) + continue + } + if len(signature.Parameters) != 3 || signature.Parameters[2].Type.Name != scopeName { + klog.V(6).Infof("%s has wrong parameters", f.Name) + continue + } + if len(signature.Results) != 1 || signature.Results[0].Type.Name != errorName { + klog.V(6).Infof("%s has wrong results", f.Name) + continue + } + inType := signature.Parameters[0].Type + outType := signature.Parameters[1].Type + if inType.Kind != types.Pointer || outType.Kind != types.Pointer { + klog.V(6).Infof("%s has wrong parameter types", f.Name) + continue + } + // Now check if the name satisfies the convention. + // TODO: This should call the Namer directly. + args := argsFromType(inType.Elem, outType.Elem) + sw.Do("Convert_$.inType|public$_To_$.outType|public$", args) + if f.Name.Name == buffer.String() { + klog.V(2).Infof("Found conversion function %s", f.Name) + key := conversionPair{inType.Elem, outType.Elem} + // We might scan the same package twice, and that's OK. + if v, ok := manualMap[key]; ok && v != nil && v.Name.Package != pkg.Path { + panic(fmt.Sprintf("duplicate static conversion defined: %s -> %s from:\n%s.%s\n%s.%s", key.inType, key.outType, v.Name.Package, v.Name.Name, f.Name.Package, f.Name.Name)) + } + manualMap[key] = f + } else { + // prevent user error when they don't get the correct conversion signature + if strings.HasPrefix(f.Name.Name, "Convert_") { + klog.Errorf("Rename function %s %s -> %s to match expected conversion signature", f.Name.Package, f.Name.Name, buffer.String()) + } + klog.V(3).Infof("%s has wrong name", f.Name) + } + buffer.Reset() + } +} + +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, args.GeneratedBuildTag, gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + targetList := []generator.Target{} + + // Accumulate pre-existing conversion functions. + // TODO: This is too ad-hoc. We need a better way. + manualConversions := conversionFuncMap{} + + // Record types that are memory equivalent. A type is memory equivalent + // if it has the same memory layout and no nested manual conversion is + // defined. + // TODO: in the future, relax the nested manual conversion requirement + // if we can show that a large enough types are memory identical but + // have non-trivial conversion + memoryEquivalentTypes := equalMemoryTypes{} + + // First load other "input" packages. We do this as a single call because + // it is MUCH faster. + filteredInputs := make([]string, 0, len(context.Inputs)) + otherPkgs := make([]string, 0, len(context.Inputs)) + pkgToPeers := map[string][]string{} + pkgToExternal := map[string]string{} + + for _, i := range context.Inputs { + klog.V(3).Infof("considering pkg %q", i) + pkg := context.Universe[i] + + info, err := apidefinitions.Identify(pkg, apidefinitions.Conversion, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + klog.V(3).Infof(" no tag") + continue + } + filteredInputs = append(filteredInputs, i) + + // Sole +k8s:conversion-gen=false: emit only the package's + // hand-written conversions, no peer-driven standard conversions. + if !info.IsExplicitOnly() { + peerPkgs := info.PeerPackages() + klog.V(3).Infof(" peers: %q", peerPkgs) + pkgToPeers[i] = peerPkgs + otherPkgs = append(otherPkgs, peerPkgs...) + } + + externalTypes := info.ExternalTypes() + if externalTypes != i { + klog.V(3).Infof(" external types: %q", externalTypes) + otherPkgs = append(otherPkgs, externalTypes) + } + pkgToExternal[i] = externalTypes + } + + // Make sure explicit peer-packages are added. + peers := args.BasePeerDirs + peers = append(peers, args.ExtraPeerDirs...) + if expanded, err := context.FindPackages(peers...); err != nil { + klog.Fatalf("cannot find peer packages: %v", err) + } else { + otherPkgs = append(otherPkgs, expanded...) + // for each pkg, add these extras, too + for k := range pkgToPeers { + pkgToPeers[k] = append(pkgToPeers[k], expanded...) + } + } + + if len(otherPkgs) > 0 { + if _, err := context.LoadPackages(otherPkgs...); err != nil { + klog.Fatalf("cannot load packages: %v", err) + } + } + // update context.Order to the latest context.Universe + orderer := namer.Orderer{Namer: namer.NewPublicNamer(1)} + context.Order = orderer.OrderUniverse(context.Universe) + + // Look for conversion functions in the peer-packages. + for _, pp := range otherPkgs { + p := context.Universe[pp] + if p == nil { + klog.Fatalf("failed to find pkg: %s", pp) + } + getManualConversionFunctions(context, p, manualConversions) + } + + // We are generating conversions only for packages that are explicitly + // passed as InputDir. + for _, i := range filteredInputs { + klog.V(3).Infof("considering pkg %q", i) + pkg := context.Universe[i] + + // Add conversion and defaulting functions. + getManualConversionFunctions(context, pkg, manualConversions) + + // Find the right input pkg, which might not be this one. + externalTypes := pkgToExternal[i] + + // typesPkg is where the versioned types are defined. Sometimes it is + // different from pkg. For example, kubernetes core/v1 types are defined + // in k8s.io/api/core/v1, while pkg is at pkg/api/v1. + typesPkg := context.Universe[externalTypes] + + unsafeEquality := TypesEqual(memoryEquivalentTypes) + if args.SkipUnsafe { + unsafeEquality = noEquality{} + } + + targetList = append(targetList, + &generator.SimpleTarget{ + PkgName: path.Base(pkg.Path), + PkgPath: pkg.Path, + PkgDir: pkg.Dir, // output pkg is the same as the input + HeaderComment: boilerplate, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + return t.Name.Package == typesPkg.Path + }, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + return []generator.Generator{ + NewGenConversion(args.OutputFile, typesPkg.Path, pkg.Path, manualConversions, pkgToPeers[pkg.Path], unsafeEquality), + } + }, + }) + } + + // If there is a manual conversion defined between two types, exclude it + // from being a candidate for unsafe conversion + for k, v := range manualConversions { + copyOnly, err := isCopyOnly(v.CommentLines) + if err != nil { + klog.Errorf("error extracting tags: %v", err) + } else if copyOnly { + klog.V(4).Infof("Conversion function %s will not block memory copy because it is copy-only", v.Name) + continue + } + // this type should be excluded from all equivalence, because the converter must be called. + memoryEquivalentTypes.Skip(k.inType, k.outType) + } + + return targetList +} + +type equalMemoryTypes map[conversionPair]bool + +func (e equalMemoryTypes) Skip(a, b *types.Type) { + e[conversionPair{a, b}] = false + e[conversionPair{b, a}] = false +} + +func (e equalMemoryTypes) Equal(a, b *types.Type) bool { + equal, _ := e.cachingEqual(a, b, nil) + return equal +} + +// cachingEqual recursively compares a and b for memory equality, +// using a cache of previously computed results, and caching the result before returning when possible. +// alreadyVisitedStack is used to check for cycles during recursion. +// The returned cacheable boolean tells the caller whether the equal result is a definitive answer that can be safely cached, +// or if it's a temporary assumption made to break a cycle in a recursively defined type. +func (e equalMemoryTypes) cachingEqual(a, b *types.Type, alreadyVisitedStack []*types.Type) (equal, cacheable bool) { + if a == b { + return true, true + } + if equal, ok := e[conversionPair{a, b}]; ok { + return equal, true + } + if equal, ok := e[conversionPair{b, a}]; ok { + return equal, true + } + result, cacheable := e.equal(a, b, alreadyVisitedStack) + if cacheable { + e[conversionPair{a, b}] = result + e[conversionPair{b, a}] = result + } + return result, cacheable +} + +// equal recursively compares a and b for memory equality. +// alreadyVisitedStack is used to check for cycles during recursion. +// The returned cacheable boolean tells the caller whether the equal result is a definitive answer that can be safely cached, +// or if it's a temporary assumption made to break a cycle in a recursively defined type. +func (e equalMemoryTypes) equal(a, b *types.Type, alreadyVisitedStack []*types.Type) (equal, cacheable bool) { + in, out := unwrapAlias(a), unwrapAlias(b) + switch { + case in == out: + return true, true + case in.Kind == out.Kind: + for _, v := range alreadyVisitedStack { + if v == in { + // if the type was visited in this stack already, return early to avoid infinite recursion, but do not cache the results + return true, false + } + } + alreadyVisitedStack = append(alreadyVisitedStack, in) + + switch in.Kind { + case types.Struct: + if len(in.Members) != len(out.Members) { + return false, true + } + cacheable = true + for i, inMember := range in.Members { + outMember := out.Members[i] + memberEqual, memberCacheable := e.cachingEqual(inMember.Type, outMember.Type, alreadyVisitedStack) + if !memberEqual { + return false, true + } + if !memberCacheable { + cacheable = false + } + } + return true, cacheable + case types.Pointer: + return e.cachingEqual(in.Elem, out.Elem, alreadyVisitedStack) + case types.Map: + keyEqual, keyCacheable := e.cachingEqual(in.Key, out.Key, alreadyVisitedStack) + valueEqual, valueCacheable := e.cachingEqual(in.Elem, out.Elem, alreadyVisitedStack) + return keyEqual && valueEqual, keyCacheable && valueCacheable + case types.Slice: + return e.cachingEqual(in.Elem, out.Elem, alreadyVisitedStack) + case types.Interface: + // TODO: determine whether the interfaces are actually equivalent - for now, they must have the + // same type. + return false, true + case types.Builtin: + return in.Name.Name == out.Name.Name, true + } + } + return false, true +} + +func findMember(t *types.Type, name string) (types.Member, bool) { + if t.Kind != types.Struct { + return types.Member{}, false + } + for _, member := range t.Members { + if member.Name == name { + return member, true + } + } + return types.Member{}, false +} + +// unwrapAlias recurses down aliased types to find the bedrock type. +func unwrapAlias(in *types.Type) *types.Type { + for in.Kind == types.Alias { + in = in.Underlying + } + return in +} + +const ( + runtimePackagePath = "k8s.io/apimachinery/pkg/runtime" + conversionPackagePath = "k8s.io/apimachinery/pkg/conversion" +) + +type noEquality struct{} + +func (noEquality) Equal(_, _ *types.Type) bool { return false } + +type TypesEqual interface { + Equal(a, b *types.Type) bool +} + +// genConversion produces a file with a autogenerated conversions. +type genConversion struct { + generator.GoGenerator + // the package that contains the types that conversion func are going to be + // generated for + typesPackage string + // the package that the conversion funcs are going to be output to + outputPackage string + // packages that contain the peer of types in typesPacakge + peerPackages []string + manualConversions conversionFuncMap + imports namer.ImportTracker + types []*types.Type + explicitConversions []conversionPair + skippedFields map[*types.Type][]string + useUnsafe TypesEqual +} + +func NewGenConversion(outputFilename, typesPackage, outputPackage string, manualConversions conversionFuncMap, peerPkgs []string, useUnsafe TypesEqual) generator.Generator { + return &genConversion{ + GoGenerator: generator.GoGenerator{ + OutputFilename: outputFilename, + }, + typesPackage: typesPackage, + outputPackage: outputPackage, + peerPackages: peerPkgs, + manualConversions: manualConversions, + imports: generator.NewImportTrackerForPackage(outputPackage), + types: []*types.Type{}, + explicitConversions: []conversionPair{}, + skippedFields: map[*types.Type][]string{}, + useUnsafe: useUnsafe, + } +} + +func (g *genConversion) Namers(c *generator.Context) namer.NameSystems { + // Have the raw namer for this file track what it imports. + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + "publicIT": &namerPlusImportTracking{ + delegate: conversionNamer(), + tracker: g.imports, + }, + } +} + +type namerPlusImportTracking struct { + delegate namer.Namer + tracker namer.ImportTracker +} + +func (n *namerPlusImportTracking) Name(t *types.Type) string { + n.tracker.AddType(t) + return n.delegate.Name(t) +} + +func (g *genConversion) convertibleOnlyWithinPackage(inType, outType *types.Type) bool { + var t *types.Type + var other *types.Type + if inType.Name.Package == g.typesPackage { + t, other = inType, outType + } else { + t, other = outType, inType + } + + if t.Name.Package != g.typesPackage { + return false + } + // If the type has opted out, skip it. + tagvals, err := extractTag(t.CommentLines) + if err != nil { + klog.Errorf("Type %v: error extracting tags: %v", t, err) + return false + } + + if tagvals != nil { + if tagvals[0] != "false" { + klog.Fatalf("Type %v: unsupported %s value: %q", t, tagName, tagvals[0]) + } + klog.V(2).Infof("type %v requests no conversion generation, skipping", t) + return false + } + // TODO: Consider generating functions for other kinds too. + if t.Kind != types.Struct { + return false + } + // Also, filter out private types. + if namer.IsPrivateGoName(other.Name.Name) { + return false + } + return true +} + +func getExplicitFromTypes(t *types.Type) []types.Name { + comments := t.SecondClosestCommentLines + comments = append(comments, t.CommentLines...) + result := []types.Name{} + paths, err := extractExplicitFromTag(comments) + if err != nil { + klog.Errorf("Error extracting explicit-from tag for %v: %v", t.Name, err) + return result + } + for _, path := range paths { + items := strings.Split(path, ".") + if len(items) != 2 { + klog.Errorf("Unexpected k8s:conversion-gen:explicit-from tag: %s", path) + continue + } + switch { + case items[0] == "net/url" && items[1] == "Values": + default: + klog.Fatalf("Not supported k8s:conversion-gen:explicit-from tag: %s", path) + } + result = append(result, types.Name{Package: items[0], Name: items[1]}) + } + return result +} + +func (g *genConversion) Filter(c *generator.Context, t *types.Type) bool { + convertibleWithPeer := func() bool { + peerType := getPeerTypeFor(c, t, g.peerPackages) + if peerType == nil { + return false + } + if !g.convertibleOnlyWithinPackage(t, peerType) { + return false + } + g.types = append(g.types, t) + return true + }() + + explicitlyConvertible := func() bool { + inTypes := getExplicitFromTypes(t) + if len(inTypes) == 0 { + return false + } + for i := range inTypes { + pair := conversionPair{ + inType: &types.Type{Name: inTypes[i]}, + outType: t, + } + g.explicitConversions = append(g.explicitConversions, pair) + } + return true + }() + + return convertibleWithPeer || explicitlyConvertible +} + +func (g *genConversion) isOtherPackage(pkg string) bool { + if pkg == g.outputPackage { + return false + } + if strings.HasSuffix(pkg, `"`+g.outputPackage+`"`) { + return false + } + return true +} + +func (g *genConversion) Imports(c *generator.Context) (imports []string) { + var importLines []string + for _, singleImport := range g.imports.ImportLines() { + if g.isOtherPackage(singleImport) { + importLines = append(importLines, singleImport) + } + } + return importLines +} + +func argsFromType(inType, outType *types.Type) generator.Args { + return generator.Args{ + "inType": inType, + "outType": outType, + } +} + +const nameTmpl = "Convert_$.inType|publicIT$_To_$.outType|publicIT$" + +func (g *genConversion) preexists(inType, outType *types.Type) (*types.Type, bool) { + function, ok := g.manualConversions[conversionPair{inType, outType}] + return function, ok +} + +func (g *genConversion) preexistsPointers(inType, outType *types.Type) (*types.Type, bool) { + if inType.Kind != types.Pointer { + return nil, false + } + if outType.Kind != types.Pointer { + return nil, false + } + return g.preexists(inType.Elem, outType.Elem) +} + +func (g *genConversion) Init(c *generator.Context, w io.Writer) error { + klogV := klog.V(6) + if klogV.Enabled() { + if m, ok := g.useUnsafe.(equalMemoryTypes); ok { + var result []string + klogV.Info("All objects without identical memory layout:") + for k, v := range m { + if v { + continue + } + result = append(result, fmt.Sprintf(" %s -> %s = %t", k.inType, k.outType, v)) + } + sort.Strings(result) + for _, s := range result { + klogV.Info(s) + } + } + } + sw := generator.NewSnippetWriter(w, c, "$", "$") + sw.Do("func init() {\n", nil) + sw.Do("localSchemeBuilder.Register(RegisterConversions)\n", nil) + sw.Do("}\n", nil) + + scheme := c.Universe.Type(types.Name{Package: runtimePackagePath, Name: "Scheme"}) + schemePtr := &types.Type{ + Kind: types.Pointer, + Elem: scheme, + } + sw.Do("// RegisterConversions adds conversion functions to the given scheme.\n", nil) + sw.Do("// Public to allow building arbitrary schemes.\n", nil) + sw.Do("func RegisterConversions(s $.|raw$) error {\n", schemePtr) + for _, t := range g.types { + peerType := getPeerTypeFor(c, t, g.peerPackages) + if _, found := g.preexists(t, peerType); !found { + args := argsFromType(t, peerType).With("Scope", types.Ref(conversionPackagePath, "Scope")) + sw.Do("if err := s.AddGeneratedConversionFunc((*$.inType|raw$)(nil), (*$.outType|raw$)(nil), func(a, b interface{}, scope $.Scope|raw$) error { return "+nameTmpl+"(a.(*$.inType|raw$), b.(*$.outType|raw$), scope) }); err != nil { return err }\n", args) + } + if _, found := g.preexists(peerType, t); !found { + args := argsFromType(peerType, t).With("Scope", types.Ref(conversionPackagePath, "Scope")) + sw.Do("if err := s.AddGeneratedConversionFunc((*$.inType|raw$)(nil), (*$.outType|raw$)(nil), func(a, b interface{}, scope $.Scope|raw$) error { return "+nameTmpl+"(a.(*$.inType|raw$), b.(*$.outType|raw$), scope) }); err != nil { return err }\n", args) + } + } + + for i := range g.explicitConversions { + args := argsFromType(g.explicitConversions[i].inType, g.explicitConversions[i].outType).With("Scope", types.Ref(conversionPackagePath, "Scope")) + sw.Do("if err := s.AddGeneratedConversionFunc((*$.inType|raw$)(nil), (*$.outType|raw$)(nil), func(a, b interface{}, scope $.Scope|raw$) error { return "+nameTmpl+"(a.(*$.inType|raw$), b.(*$.outType|raw$), scope) }); err != nil { return err }\n", args) + } + + var pairs []conversionPair + for pair, t := range g.manualConversions { + if t.Name.Package != g.outputPackage { + continue + } + pairs = append(pairs, pair) + } + // sort by name of the conversion function + sort.Slice(pairs, func(i, j int) bool { + return g.manualConversions[pairs[i]].Name.Name < g.manualConversions[pairs[j]].Name.Name + }) + for _, pair := range pairs { + args := argsFromType(pair.inType, pair.outType).With("Scope", types.Ref(conversionPackagePath, "Scope")).With("fn", g.manualConversions[pair]) + sw.Do("if err := s.AddConversionFunc((*$.inType|raw$)(nil), (*$.outType|raw$)(nil), func(a, b interface{}, scope $.Scope|raw$) error { return $.fn|raw$(a.(*$.inType|raw$), b.(*$.outType|raw$), scope) }); err != nil { return err }\n", args) + } + + sw.Do("return nil\n", nil) + sw.Do("}\n\n", nil) + return sw.Error() +} + +func (g *genConversion) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + klog.V(5).Infof("generating for type %v", t) + sw := generator.NewSnippetWriter(w, c, "$", "$") + + if peerType := getPeerTypeFor(c, t, g.peerPackages); peerType != nil { + g.generateConversion(t, peerType, sw) + g.generateConversion(peerType, t, sw) + } + + for _, inTypeName := range getExplicitFromTypes(t) { + inPkg, ok := c.Universe[inTypeName.Package] + if !ok { + klog.Errorf("Unrecognized package: %s", inTypeName.Package) + continue + } + inType, ok := inPkg.Types[inTypeName.Name] + if !ok { + klog.Errorf("Unrecognized type in package %s: %s", inTypeName.Package, inTypeName.Name) + continue + } + switch { + case inType.Name.Package == "net/url" && inType.Name.Name == "Values": + g.generateFromURLValues(inType, t, sw) + default: + klog.Errorf("Not supported input type: %#v", inType.Name) + } + } + + return sw.Error() +} + +func (g *genConversion) generateConversion(inType, outType *types.Type, sw *generator.SnippetWriter) { + args := argsFromType(inType, outType). + With("Scope", types.Ref(conversionPackagePath, "Scope")) + + sw.Do("func auto"+nameTmpl+"(in *$.inType|raw$, out *$.outType|raw$, s $.Scope|raw$) error {\n", args) + g.generateFor(inType, outType, sw) + sw.Do("return nil\n", nil) + sw.Do("}\n\n", nil) + + if _, found := g.preexists(inType, outType); found { + // There is a public manual Conversion method: use it. + } else if skipped := g.skippedFields[inType]; len(skipped) != 0 { + // The inType had some fields we could not generate. + klog.Errorf("Warning: could not find nor generate a final Conversion function for %v -> %v", inType, outType) + klog.Errorf(" the following fields need manual conversion:") + for _, f := range skipped { + klog.Errorf(" - %v", f) + } + } else { + // Emit a public conversion function. + sw.Do("// "+nameTmpl+" is an autogenerated conversion function.\n", args) + sw.Do("func "+nameTmpl+"(in *$.inType|raw$, out *$.outType|raw$, s $.Scope|raw$) error {\n", args) + sw.Do("return auto"+nameTmpl+"(in, out, s)\n", args) + sw.Do("}\n\n", nil) + } +} + +// we use the system of shadowing 'in' and 'out' so that the same code is valid +// at any nesting level. This makes the autogenerator easy to understand, and +// the compiler shouldn't care. +func (g *genConversion) generateFor(inType, outType *types.Type, sw *generator.SnippetWriter) { + klog.V(4).Infof("generating %v -> %v", inType, outType) + var f func(*types.Type, *types.Type, *generator.SnippetWriter) + + switch inType.Kind { + case types.Builtin: + f = g.doBuiltin + case types.Map: + f = g.doMap + case types.Slice: + f = g.doSlice + case types.Struct: + f = g.doStruct + case types.Pointer: + f = g.doPointer + case types.Alias: + f = g.doAlias + default: + f = g.doUnknown + } + + f(inType, outType, sw) +} + +func (g *genConversion) doBuiltin(inType, outType *types.Type, sw *generator.SnippetWriter) { + if inType == outType { + sw.Do("*out = *in\n", nil) + } else { + sw.Do("*out = $.|raw$(*in)\n", outType) + } +} + +func (g *genConversion) doMap(inType, outType *types.Type, sw *generator.SnippetWriter) { + sw.Do("*out = make($.|raw$, len(*in))\n", outType) + if isDirectlyAssignable(inType.Key, outType.Key) { + sw.Do("for key, val := range *in {\n", nil) + if isDirectlyAssignable(inType.Elem, outType.Elem) { + if inType.Key == outType.Key { + sw.Do("(*out)[key] = ", nil) + } else { + sw.Do("(*out)[$.|raw$(key)] = ", outType.Key) + } + if inType.Elem == outType.Elem { + sw.Do("val\n", nil) + } else { + sw.Do("$.|raw$(val)\n", outType.Elem) + } + } else { + conversionExists := true + conditionalConversionExists := false + if function, ok := g.preexists(inType.Elem, outType.Elem); ok { + sw.Do("newVal := new($.|raw$)\n", outType.Elem) + sw.Do("if err := $.|raw$(&val, newVal, s); err != nil {\n", function) + } else if function, ok := g.preexistsPointers(inType.Elem, outType.Elem); ok { + sw.Do("newVal := new($.|raw$)\n", outType.Elem) + sw.Do("if val != nil {\n", nil) + sw.Do("*newVal = new($.|raw$)\n", outType.Elem.Elem) + sw.Do("if err := $.|raw$(val, *newVal, s); err != nil {\n", function) + conditionalConversionExists = true + } else if g.convertibleOnlyWithinPackage(inType.Elem, outType.Elem) { + sw.Do("newVal := new($.|raw$)\n", outType.Elem) + sw.Do("if err := "+nameTmpl+"(&val, newVal, s); err != nil {\n", argsFromType(inType.Elem, outType.Elem)) + } else { + args := argsFromType(inType.Elem, outType.Elem) + sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args) + sw.Do("compileErrorOnMissingConversion()\n", nil) + conversionExists = false + } + if conversionExists { + sw.Do("return err\n", nil) + sw.Do("}\n", nil) + if conditionalConversionExists { + sw.Do("}\n", nil) + } + if inType.Key == outType.Key { + sw.Do("(*out)[key] = *newVal\n", nil) + } else { + sw.Do("(*out)[$.|raw$(key)] = *newVal\n", outType.Key) + } + } + } + } else { + // TODO: Implement it when necessary. + sw.Do("for range *in {\n", nil) + sw.Do("// FIXME: Converting unassignable keys unsupported $.|raw$\n", inType.Key) + } + sw.Do("}\n", nil) +} + +func (g *genConversion) doSlice(inType, outType *types.Type, sw *generator.SnippetWriter) { + sw.Do("*out = make($.|raw$, len(*in))\n", outType) + if inType.Elem == outType.Elem && inType.Elem.Kind == types.Builtin { + sw.Do("copy(*out, *in)\n", nil) + } else { + sw.Do("for i := range *in {\n", nil) + if isDirectlyAssignable(inType.Elem, outType.Elem) { + if inType.Elem == outType.Elem { + sw.Do("(*out)[i] = (*in)[i]\n", nil) + } else { + sw.Do("(*out)[i] = $.|raw$((*in)[i])\n", outType.Elem) + } + } else { + conversionExists := true + conditionalConversionExists := false + if function, ok := g.preexists(inType.Elem, outType.Elem); ok { + sw.Do("if err := $.|raw$(&(*in)[i], &(*out)[i], s); err != nil {\n", function) + } else if function, ok := g.preexistsPointers(inType.Elem, outType.Elem); ok { + sw.Do("if (*in)[i] != nil {\n", nil) + sw.Do("(*out)[i] = new($.|raw$)\n", outType.Elem.Elem) + sw.Do("if err := $.|raw$((*in)[i], (*out)[i], s); err != nil {\n", function) + conditionalConversionExists = true + } else if g.convertibleOnlyWithinPackage(inType.Elem, outType.Elem) { + sw.Do("if err := "+nameTmpl+"(&(*in)[i], &(*out)[i], s); err != nil {\n", argsFromType(inType.Elem, outType.Elem)) + } else { + args := argsFromType(inType.Elem, outType.Elem) + sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args) + sw.Do("compileErrorOnMissingConversion()\n", nil) + conversionExists = false + } + if conversionExists { + sw.Do("return err\n", nil) + sw.Do("}\n", nil) + if conditionalConversionExists { + sw.Do("}\n", nil) + } + } + } + sw.Do("}\n", nil) + } +} + +func (g *genConversion) doStruct(inType, outType *types.Type, sw *generator.SnippetWriter) { + ok, err := g.canUseMemoryCopyConversion(inType, outType) + if err != nil { + klog.Errorf("Type %v: error checking for direct-copy conversion: %v", inType, err) + } + if ok { + args := argsFromType(inType, outType). + With("Pointer", types.Ref("unsafe", "Pointer")) + sw.Do("*out = *(*$.outType|raw$)($.Pointer|raw$(in))\n", args) + return + } + for _, inMember := range inType.Members { + tagvals, err := extractTag(inMember.CommentLines) + if err != nil { + klog.Errorf("Member %v.%v: error extracting tags: %v", inType, inMember.Name, err) + } + if tagvals != nil && tagvals[0] == "false" { + // This field is excluded from conversion. + sw.Do("// INFO: in."+inMember.Name+" opted out of conversion generation\n", nil) + continue + } + outMember, found := findMember(outType, inMember.Name) + if !found { + // This field doesn't exist in the peer. + sw.Do("// WARNING: in."+inMember.Name+" requires manual conversion: does not exist in peer-type\n", nil) + g.skippedFields[inType] = append(g.skippedFields[inType], inMember.Name) + continue + } + + if namer.IsPrivateGoName(inMember.Name) && g.outputPackage != inType.Name.Package { + sw.Do("// WARNING: in."+inMember.Name+" is not exported and cannot be read\n", nil) + g.skippedFields[inType] = append(g.skippedFields[inType], inMember.Name) + continue + } + if namer.IsPrivateGoName(outMember.Name) && g.outputPackage != outType.Name.Package { + sw.Do("// WARNING: out."+inMember.Name+" is not exported and cannot be set\n", nil) + g.skippedFields[inType] = append(g.skippedFields[inType], inMember.Name) + continue + } + + inMemberType, outMemberType := inMember.Type, outMember.Type + // create a copy of both underlying types but give them the top level alias name (since aliases + // are assignable) + if underlying := unwrapAlias(inMemberType); underlying != inMemberType { + copied := *underlying + copied.Name = inMemberType.Name + inMemberType = &copied + } + if underlying := unwrapAlias(outMemberType); underlying != outMemberType { + copied := *underlying + copied.Name = outMemberType.Name + outMemberType = &copied + } + + args := argsFromType(inMemberType, outMemberType).With("name", inMember.Name) + + // try a direct memory copy for any type that has exactly equivalent values + if g.useUnsafe.Equal(inMemberType, outMemberType) { + args = args. + With("Pointer", types.Ref("unsafe", "Pointer")). + With("SliceHeader", types.Ref("reflect", "SliceHeader")) + switch inMemberType.Kind { + case types.Pointer: + sw.Do("out.$.name$ = ($.outType|raw$)($.Pointer|raw$(in.$.name$))\n", args) + continue + case types.Map: + sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args) + continue + case types.Slice: + sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args) + continue + } + } + + // check based on the top level name, not the underlying names + if function, ok := g.preexists(inMember.Type, outMember.Type); ok { + dropFn, err := isDrop(function.CommentLines) + if err != nil { + klog.Errorf("Error extracting drop tag for function %s: %v", function.Name, err) + } else if dropFn { + continue + } + // copy-only functions that are directly assignable can be inlined instead of invoked. + // As an example, conversion functions exist that allow types with private fields to be + // correctly copied between types. These functions are equivalent to a memory assignment, + // and are necessary for the reflection path, but should not block memory conversion. + // Convert_unversioned_Time_to_unversioned_Time is an example of this logic. + copyOnly, copyErr := isCopyOnly(function.CommentLines) + if copyErr != nil { + klog.Errorf("Error extracting copy-only tag for function %s: %v", function.Name, copyErr) + copyOnly = false + } + if !copyOnly || !g.isFastConversion(inMemberType, outMemberType) { + args["function"] = function + sw.Do("if err := $.function|raw$(&in.$.name$, &out.$.name$, s); err != nil {\n", args) + sw.Do("return err\n", nil) + sw.Do("}\n", nil) + continue + } + klog.V(2).Infof("Skipped function %s because it is copy-only and we can use direct assignment", function.Name) + } + + // If we can't auto-convert, punt before we emit any code. + if inMemberType.Kind != outMemberType.Kind { + sw.Do("// WARNING: in."+inMember.Name+" requires manual conversion: inconvertible types ("+ + inMemberType.String()+" vs "+outMemberType.String()+")\n", nil) + g.skippedFields[inType] = append(g.skippedFields[inType], inMember.Name) + continue + } + + switch inMemberType.Kind { + case types.Builtin: + if inMemberType == outMemberType { + sw.Do("out.$.name$ = in.$.name$\n", args) + } else { + sw.Do("out.$.name$ = $.outType|raw$(in.$.name$)\n", args) + } + case types.Map, types.Slice, types.Pointer: + if g.isDirectlyAssignable(inMemberType, outMemberType) { + sw.Do("out.$.name$ = in.$.name$\n", args) + continue + } + + sw.Do("if in.$.name$ != nil {\n", args) + sw.Do("in, out := &in.$.name$, &out.$.name$\n", args) + g.generateFor(inMemberType, outMemberType, sw) + sw.Do("} else {\n", nil) + sw.Do("out.$.name$ = nil\n", args) + sw.Do("}\n", nil) + case types.Struct: + if g.isDirectlyAssignable(inMemberType, outMemberType) { + sw.Do("out.$.name$ = in.$.name$\n", args) + continue + } + conversionExists := true + if g.convertibleOnlyWithinPackage(inMemberType, outMemberType) { + sw.Do("if err := "+nameTmpl+"(&in.$.name$, &out.$.name$, s); err != nil {\n", args) + } else { + args := argsFromType(inMemberType, outMemberType) + sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args) + sw.Do("compileErrorOnMissingConversion()\n", nil) + conversionExists = false + } + if conversionExists { + sw.Do("return err\n", nil) + sw.Do("}\n", nil) + } + case types.Alias: + if isDirectlyAssignable(inMemberType, outMemberType) { + if inMemberType == outMemberType { + sw.Do("out.$.name$ = in.$.name$\n", args) + } else { + sw.Do("out.$.name$ = $.outType|raw$(in.$.name$)\n", args) + } + } else { + conversionExists := true + if g.convertibleOnlyWithinPackage(inMemberType, outMemberType) { + sw.Do("if err := "+nameTmpl+"(&in.$.name$, &out.$.name$, s); err != nil {\n", args) + } else { + args := argsFromType(inMemberType, outMemberType) + sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args) + sw.Do("compileErrorOnMissingConversion()\n", nil) + conversionExists = false + } + if conversionExists { + sw.Do("return err\n", nil) + sw.Do("}\n", nil) + } + } + default: + conversionExists := true + if g.convertibleOnlyWithinPackage(inMemberType, outMemberType) { + sw.Do("if err := "+nameTmpl+"(&in.$.name$, &out.$.name$, s); err != nil {\n", args) + } else { + args := argsFromType(inMemberType, outMemberType) + sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args) + sw.Do("compileErrorOnMissingConversion()\n", nil) + conversionExists = false + } + if conversionExists { + sw.Do("return err\n", nil) + sw.Do("}\n", nil) + } + } + } +} + +// canUseMemoryCopyConversion reports whether two struct types can be converted +// with a single unsafe memory copy rather than field-by-field conversion. This +// returns true only for structs that are both memory-identical and do not have any +// manual conversions (except copy-only conversions, which are allowed). +func (g *genConversion) canUseMemoryCopyConversion(inType, outType *types.Type) (bool, error) { + if !g.useUnsafe.Equal(inType, outType) { + return false, nil + } + for _, inMember := range inType.Members { + tagvals, err := extractTag(inMember.CommentLines) + if err != nil { + return false, err + } + if len(tagvals) > 0 && tagvals[0] == "false" { + return false, nil // opted out of conversion-gen + } + outMember, found := findMember(outType, inMember.Name) + if !found { + return false, nil + } + + if namer.IsPrivateGoName(inMember.Name) && g.outputPackage != inType.Name.Package { + return false, nil + } + if namer.IsPrivateGoName(outMember.Name) && g.outputPackage != outType.Name.Package { + return false, nil + } + // Bail out if there is a manual conversion other than 'copy-only'. + if function, ok := g.preexists(inMember.Type, outMember.Type); ok { + copyOnly, err := isCopyOnly(function.CommentLines) + if err != nil || !copyOnly { + return false, err + } + } + } + return true, nil +} + +func (g *genConversion) isFastConversion(inType, outType *types.Type) bool { + switch inType.Kind { + case types.Builtin: + return true + case types.Map, types.Slice, types.Pointer, types.Struct, types.Alias: + return g.isDirectlyAssignable(inType, outType) + default: + return false + } +} + +func (g *genConversion) isDirectlyAssignable(inType, outType *types.Type) bool { + return unwrapAlias(inType) == unwrapAlias(outType) +} + +func (g *genConversion) doPointer(inType, outType *types.Type, sw *generator.SnippetWriter) { + sw.Do("*out = new($.Elem|raw$)\n", outType) + if isDirectlyAssignable(inType.Elem, outType.Elem) { + if inType.Elem == outType.Elem { + sw.Do("**out = **in\n", nil) + } else { + sw.Do("**out = $.|raw$(**in)\n", outType.Elem) + } + } else { + conversionExists := true + if function, ok := g.preexists(inType.Elem, outType.Elem); ok { + sw.Do("if err := $.|raw$(*in, *out, s); err != nil {\n", function) + } else if g.convertibleOnlyWithinPackage(inType.Elem, outType.Elem) { + sw.Do("if err := "+nameTmpl+"(*in, *out, s); err != nil {\n", argsFromType(inType.Elem, outType.Elem)) + } else { + args := argsFromType(inType.Elem, outType.Elem) + sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args) + sw.Do("compileErrorOnMissingConversion()\n", nil) + conversionExists = false + } + if conversionExists { + sw.Do("return err\n", nil) + sw.Do("}\n", nil) + } + } +} + +func (g *genConversion) doAlias(inType, outType *types.Type, sw *generator.SnippetWriter) { + // TODO: Add support for aliases. + g.doUnknown(inType, outType, sw) +} + +func (g *genConversion) doUnknown(inType, outType *types.Type, sw *generator.SnippetWriter) { + sw.Do("// FIXME: Type $.|raw$ is unsupported.\n", inType) +} + +func (g *genConversion) generateFromURLValues(inType, outType *types.Type, sw *generator.SnippetWriter) { + args := generator.Args{ + "inType": inType, + "outType": outType, + "Scope": types.Ref(conversionPackagePath, "Scope"), + } + sw.Do("func auto"+nameTmpl+"(in *$.inType|raw$, out *$.outType|raw$, s $.Scope|raw$) error {\n", args) + for _, outMember := range outType.Members { + tagvals, err := extractTag(outMember.CommentLines) + if err != nil { + klog.Errorf("Member %v.%v: error extracting tags: %v", outType, outMember.Name, err) + } + if tagvals != nil && tagvals[0] == "false" { + // This field is excluded from conversion. + sw.Do("// INFO: in."+outMember.Name+" opted out of conversion generation\n", nil) + continue + } + jsonTag := reflect.StructTag(outMember.Tags).Get("json") + index := strings.Index(jsonTag, ",") + if index == -1 { + index = len(jsonTag) + } + if index == 0 { + memberArgs := generator.Args{ + "name": outMember.Name, + } + sw.Do("// WARNING: Field $.name$ does not have json tag, skipping.\n\n", memberArgs) + continue + } + memberArgs := generator.Args{ + "name": outMember.Name, + "tag": jsonTag[:index], + } + sw.Do("if values, ok := map[string][]string(*in)[\"$.tag$\"]; ok && len(values) > 0 {\n", memberArgs) + g.fromValuesEntry(inType.Underlying.Elem, outMember, sw) + sw.Do("} else {\n", nil) + g.setZeroValue(outMember, sw) + sw.Do("}\n", nil) + } + sw.Do("return nil\n", nil) + sw.Do("}\n\n", nil) + + if _, found := g.preexists(inType, outType); found { + // There is a public manual Conversion method: use it. + } else { + // Emit a public conversion function. + sw.Do("// "+nameTmpl+" is an autogenerated conversion function.\n", args) + sw.Do("func "+nameTmpl+"(in *$.inType|raw$, out *$.outType|raw$, s $.Scope|raw$) error {\n", args) + sw.Do("return auto"+nameTmpl+"(in, out, s)\n", args) + sw.Do("}\n\n", nil) + } +} + +func (g *genConversion) fromValuesEntry(inType *types.Type, outMember types.Member, sw *generator.SnippetWriter) { + memberArgs := generator.Args{ + "name": outMember.Name, + "type": outMember.Type, + } + if function, ok := g.preexists(inType, outMember.Type); ok { + args := memberArgs.With("function", function) + sw.Do("if err := $.function|raw$(&values, &out.$.name$, s); err != nil {\n", args) + sw.Do("return err\n", nil) + sw.Do("}\n", nil) + return + } + switch { + case outMember.Type == types.String: + sw.Do("out.$.name$ = values[0]\n", memberArgs) + case g.useUnsafe.Equal(inType, outMember.Type): + args := memberArgs.With("Pointer", types.Ref("unsafe", "Pointer")) + switch inType.Kind { + case types.Pointer: + sw.Do("out.$.name$ = ($.type|raw$)($.Pointer|raw$(&values))\n", args) + case types.Map, types.Slice: + sw.Do("out.$.name$ = *(*$.type|raw$)($.Pointer|raw$(&values))\n", args) + default: + // TODO: Support other types to allow more auto-conversions. + sw.Do("// FIXME: out.$.name$ is of not yet supported type and requires manual conversion\n", memberArgs) + } + default: + // TODO: Support other types to allow more auto-conversions. + sw.Do("// FIXME: out.$.name$ is of not yet supported type and requires manual conversion\n", memberArgs) + } +} + +func (g *genConversion) setZeroValue(outMember types.Member, sw *generator.SnippetWriter) { + outMemberType := unwrapAlias(outMember.Type) + memberArgs := generator.Args{ + "name": outMember.Name, + "alias": outMember.Type, + "type": outMemberType, + } + + switch outMemberType.Kind { + case types.Builtin: + switch outMemberType { + case types.String: + sw.Do("out.$.name$ = \"\"\n", memberArgs) + case types.Int64, types.Int32, types.Int16, types.Int, types.Uint64, types.Uint32, types.Uint16, types.Uint: + sw.Do("out.$.name$ = 0\n", memberArgs) + case types.Uintptr, types.Byte: + sw.Do("out.$.name$ = 0\n", memberArgs) + case types.Float64, types.Float32, types.Float: + sw.Do("out.$.name$ = 0\n", memberArgs) + case types.Bool: + sw.Do("out.$.name$ = false\n", memberArgs) + default: + sw.Do("// FIXME: out.$.name$ is of unsupported type and requires manual conversion\n", memberArgs) + } + case types.Struct: + if outMemberType == outMember.Type { + sw.Do("out.$.name$ = $.type|raw${}\n", memberArgs) + } else { + sw.Do("out.$.name$ = $.alias|raw$($.type|raw${})\n", memberArgs) + } + case types.Map, types.Slice, types.Pointer: + sw.Do("out.$.name$ = nil\n", memberArgs) + case types.Alias: + // outMemberType was already unwrapped from aliases - so that should never happen. + sw.Do("// FIXME: unexpected error for out.$.name$\n", memberArgs) + case types.Interface, types.Array: + sw.Do("out.$.name$ = nil\n", memberArgs) + default: + sw.Do("// FIXME: out.$.name$ is of unsupported type and requires manual conversion\n", memberArgs) + } +} + +func isDirectlyAssignable(inType, outType *types.Type) bool { + // TODO: This should maybe check for actual assignability between the two + // types, rather than superficial traits that happen to indicate it is + // assignable in the ways we currently use this code. + return inType.IsAssignable() && (inType.IsPrimitive() || isSamePackage(inType, outType)) +} + +func isSamePackage(inType, outType *types.Type) bool { + return inType.Name.Package == outType.Name.Package +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/generators/conversion_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/generators/conversion_test.go new file mode 100644 index 0000000000..49f636430b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/generators/conversion_test.go @@ -0,0 +1,234 @@ +/* +Copyright 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 generators + +import ( + "testing" + + "k8s.io/gengo/v2/types" +) + +func builtin(name string) *types.Type { + return &types.Type{Name: types.Name{Name: name}, Kind: types.Builtin} +} + +func structOf(pkg, name string, members ...types.Member) *types.Type { + return &types.Type{ + Name: types.Name{Package: pkg, Name: name}, + Kind: types.Struct, + Members: members, + } +} + +func field(name string, t *types.Type, comments ...string) types.Member { + return types.Member{Name: name, Type: t, CommentLines: comments} +} + +func conversionFn(comments ...string) *types.Type { + return &types.Type{Name: types.Name{Name: "ConversionFunc"}, CommentLines: comments} +} + +func TestCanUseMemoryCopyConversion(t *testing.T) { + str := builtin("string") + i := builtin("int") + meta := structOf("meta", "TypeMeta", field("Kind", str)) + metaExt := structOf("ext", "TypeMeta", field("Kind", str)) + metaInt := structOf("int", "TypeMeta", field("Kind", str)) + metaAlias := &types.Type{Name: types.Name{Package: "ext", Name: "MetaAlias"}, Kind: types.Alias, Underlying: metaExt} + + level2Ext := structOf("ext", "Level2", field("Value", str)) + level2Int := structOf("int", "Level2", field("Value", str)) + level1Ext := structOf("ext", "Level1", field("Level2", level2Ext)) + level1Int := structOf("int", "Level1", field("Level2", level2Int)) + aSpecExt := structOf("ext", "ASpec", field("Level1", level1Ext)) + aSpecInt := structOf("int", "ASpec", field("Level1", level1Int)) + aExt := structOf("ext", "A", field("Spec", aSpecExt)) + aInt := structOf("int", "A", field("Spec", aSpecInt)) + + tests := []struct { + name string + outputPackage string + manualConversions conversionFuncMap + in *types.Type + out *types.Type + want bool + wantErr string + }{ + { + name: "identical", + in: structOf("ext", "T", field("Name", str), field("Age", i)), + out: structOf("int", "T", field("Name", str), field("Age", i)), + want: true, + }, + { + name: "not identical", + in: structOf("ext", "T", field("Name", str)), + out: structOf("int", "T", field("Identifier", str)), + want: false, + }, + { + name: "empty struct", + in: structOf("ext", "T"), + out: structOf("int", "T"), + want: true, + }, + { + name: "member opted out of conversion generation", + in: structOf("ext", "T", field("Name", str, "+k8s:conversion-gen=false")), + out: structOf("int", "T", field("Name", str)), + want: false, + }, + { + name: "member missing in peer", + in: structOf("ext", "T", field("Name", str), field("Extra", i)), + out: structOf("int", "T", field("Name", str)), + want: false, + }, + { + name: "unexported member, output package internal", + outputPackage: "int", + in: structOf("ext", "T", field("secret", str)), + out: structOf("int", "T", field("secret", str)), + want: false, + }, + { + name: "unexported member, output package is external", + outputPackage: "ext", + in: structOf("ext", "T", field("secret", str)), + out: structOf("int", "T", field("secret", str)), + want: false, + }, + { + name: "unexported member, but conversion happens inside package", + outputPackage: "p", + in: structOf("p", "T", field("secret", str)), + out: structOf("p", "T", field("secret", str)), + want: true, + }, + { + name: "member has a dropping manual conversion", + manualConversions: conversionFuncMap{{meta, meta}: conversionFn("+k8s:conversion-fn=drop")}, + in: structOf("ext", "T", field("Meta", meta)), + out: structOf("int", "T", field("Meta", meta)), + want: false, + }, + { + name: "member has a manual conversion", + manualConversions: conversionFuncMap{{meta, meta}: conversionFn()}, + in: structOf("ext", "T", field("Meta", meta)), + out: structOf("int", "T", field("Meta", meta)), + want: false, + }, + { + name: "member has a copy-only manual conversion", + manualConversions: conversionFuncMap{{meta, meta}: conversionFn("+k8s:conversion-fn=copy-only")}, + in: structOf("ext", "T", field("Meta", meta)), + out: structOf("int", "T", field("Meta", meta)), + want: true, + }, + { + name: "identical types: drop is honored", + manualConversions: conversionFuncMap{{metaExt, metaInt}: conversionFn("+k8s:conversion-fn=drop")}, + in: structOf("ext", "T", field("Meta", metaExt)), + out: structOf("int", "T", field("Meta", metaInt)), + want: false, + }, + { + name: "identical types: copy-only", + manualConversions: conversionFuncMap{{metaExt, metaInt}: conversionFn("+k8s:conversion-fn=copy-only")}, + in: structOf("ext", "T", field("Meta", metaExt)), + out: structOf("int", "T", field("Meta", metaInt)), + want: true, + }, + { + name: "conversion on alias, not underlying type", + manualConversions: conversionFuncMap{{metaAlias, metaAlias}: conversionFn("+k8s:conversion-fn=drop")}, + in: structOf("ext", "T", field("Meta", metaAlias)), + out: structOf("int", "T", field("Meta", metaAlias)), + want: false, + }, + { + name: "tag parse error", + in: structOf("ext", "T", field("Name", str, "+k8s:conversion-gen(a, b)=false")), + out: structOf("int", "T", field("Name", str)), + wantErr: "multiple arguments must use 'name: value' syntax", + }, + { + name: "equalMemoryTypes compare: identical", + in: structOf("ext", "T", field("Name", str)), + out: structOf("int", "T", field("Name", str)), + want: true, + }, + { + name: "equalMemoryTypes compare: not identical", + in: structOf("ext", "T", field("Name", str)), + out: structOf("int", "T", field("Name", i)), + want: false, + }, + { + name: "two members: not identical", + in: structOf("ext", "T", field("OK", str), field("Bad", i, "+k8s:conversion-gen=false")), + out: structOf("int", "T", field("OK", str), field("Bad", i)), + want: false, + }, + { + name: "conversion-gen=true does not opt out", + in: structOf("ext", "T", field("Name", str, "+k8s:conversion-gen=true")), + out: structOf("int", "T", field("Name", str)), + want: true, + }, + { + name: "nested manual conversion blocks memory copy at every ancestor", + manualConversions: conversionFuncMap{{level2Ext, level2Int}: conversionFn()}, + in: aExt, + out: aInt, + want: false, + }, + { + name: "nested copy-only conversion does not block memory copy", + manualConversions: conversionFuncMap{{level2Ext, level2Int}: conversionFn("+k8s:conversion-fn=copy-only")}, + in: aExt, + out: aInt, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + useUnsafe := equalMemoryTypes{} + for pair, fn := range tt.manualConversions { + copyOnly, _ := isCopyOnly(fn.CommentLines) + if copyOnly { + continue + } + useUnsafe.Skip(pair.inType, pair.outType) + } + g := &genConversion{ + outputPackage: tt.outputPackage, + manualConversions: tt.manualConversions, + useUnsafe: useUnsafe, + } + gotOK, err := g.canUseMemoryCopyConversion(tt.in, tt.out) + if err != nil && err.Error() != tt.wantErr { + t.Fatalf("canUseMemoryCopyConversion() error = %v, wantErr = %v", err, tt.wantErr) + } + if gotOK != tt.want { + t.Errorf("canUseMemoryCopyConversion() = %v, want %v", gotOK, tt.want) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/main.go new file mode 100644 index 0000000000..5aec5025d5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/conversion-gen/main.go @@ -0,0 +1,137 @@ +/* +Copyright 2016 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. +*/ + +// conversion-gen is a tool for auto-generating functions that convert +// between internal and external types. A general conversion code +// generation task involves three sets of packages: (1) a set of +// packages containing internal types, (2) a single package containing +// the external types, and (3) a single destination package (i.e., +// where the generated conversion functions go, and where the +// developer-authored conversion functions are). The packages +// containing the internal types play the role known as "peer +// packages" in the general code-generation framework of Kubernetes. +// +// For each conversion task, `conversion-gen` will generate functions +// that efficiently convert between same-name types in the two +// (internal, external) packages. The generated functions include +// ones named +// +// autoConvert___To__ +// +// for each such pair of types --- both with (pkg1,pkg2) = +// (internal,external) and (pkg1,pkg2) = (external,internal). The +// generated conversion functions recurse on the structure of the data +// types. For structs, source and destination fields are matched up +// according to name; if a source field has no corresponding +// destination or there is a fundamental mismatch in the type of the +// field then the generated autoConvert_... function has just a +// warning comment about that field. The generated conversion +// functions use standard value assignment wherever possible. For +// compound types, the generated conversion functions call the +// `Convert...` functions for the subsidiary types. +// +// For each pair of types `conversion-gen` will also generate a +// function named +// +// Convert___To__ +// +// if both of two conditions are met: (1) the destination package does +// not contain a function of that name in a non-generated file and (2) +// the generation of the corresponding autoConvert_... function did +// not run into trouble with a missing or fundamentally differently +// typed field. A generated Convert_... function simply calls the +// corresponding `autoConvert...` function. `conversion_gen` also +// generates a function that updates a given `runtime.Scheme` by +// registering all the Convert_... functions found and generated. +// Thus developers can override the generated behavior for selected +// type pairs by putting the desired Convert_... functions in +// non-generated files. Further, developers are practically required +// to override the generated behavior when there are missing or +// fundamentally differently typed fields. +// +// `conversion-gen` will scan its `--input-dirs`, looking at the +// package defined in each of those directories for comment tags that +// define a conversion code generation task. A package requests +// conversion code generation by including one or more comment in the +// package's `doc.go` file (currently anywhere in that file is +// acceptable, but the recommended location is above the `package` +// statement), of the form: +// +// // +k8s:conversion-gen= +// +// This introduces a conversion task, for which the destination +// package is the one containing the file with the tag and the tag +// identifies a package containing internal types. If there is also a +// tag of the form +// +// // +k8s:conversion-gen-external-types= +// +// then it identifies the package containing the external types; +// otherwise they are in the destination package. +// +// For each conversion code generation task, the full set of internal +// packages (AKA peer packages) consists of the ones specified in the +// `k8s:conversion-gen` tags PLUS any specified in the +// `--base-peer-dirs` and `--extra-peer-dirs` flags on the command +// line. +// +// When generating for a package, individual types or fields of structs may opt +// out of Conversion generation by specifying a comment on the of the form: +// +// // +k8s:conversion-gen=false +package main + +import ( + "flag" + + "github.com/spf13/pflag" + "k8s.io/klog/v2" + + generatorargs "k8s.io/code-generator/cmd/conversion-gen/args" + "k8s.io/code-generator/cmd/conversion-gen/generators" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" +) + +func main() { + klog.InitFlags(nil) + args := generatorargs.New() + + args.AddFlags(pflag.CommandLine) + flag.Set("logtostderr", "true") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return generators.GetTargets(context, args) + } + + // Run it. + if err := gengo.Execute( + generators.NameSystems(), + generators.DefaultNameSystem(), + myTargets, + args.GeneratedBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go new file mode 100644 index 0000000000..90080c391d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go @@ -0,0 +1,57 @@ +/* +Copyright 2016 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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/pkg/apidefinitions" +) + +type Args struct { + OutputFile string + GoHeaderFile string + + apidefinitions.LintArgs +} + +// New returns default arguments for the generator. +func New() *Args { + return &Args{} +} + +// AddFlags add the generator flags to the flag set. +func (args *Args) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&args.OutputFile, "output-file", "generated.deepcopy.go", + "the name of the file to be generated") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputFile) == 0 { + return fmt.Errorf("--output-file must be specified") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy.go new file mode 100644 index 0000000000..e1ed4f3c0d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy.go @@ -0,0 +1,921 @@ +/* +Copyright 2015 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 generators + +import ( + "fmt" + "io" + "path" + "sort" + "strings" + + "k8s.io/code-generator/cmd/deepcopy-gen/args" + "k8s.io/code-generator/pkg/apidefinitions" + genutil "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +// This is the comment tag that carries parameters for deep-copy generation. +const ( + tagEnabledName = "k8s:deepcopy-gen" + interfacesTagName = tagEnabledName + ":interfaces" + interfacesNonPointerTagName = tagEnabledName + ":nonpointer-interfaces" // attach the DeepCopy methods to the +) + +// Known values for the comment tag. +const tagValuePackage = "package" + +// enabledTagValue holds parameters from a tagName tag. +type enabledTagValue struct { + value string + register bool +} + +func extractEnabledTypeTag(t *types.Type) *enabledTagValue { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + return extractEnabledTag(comments) +} + +func extractEnabledTag(comments []string) *enabledTagValue { + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{tagEnabledName}, comments) + if err != nil { + klog.Fatalf("Error extracting %s tags: %v", tagEnabledName, err) + } + if tags[tagEnabledName] == nil { + // No match for the tag. + return nil + } + // If there are multiple values, abort. + if len(tags[tagEnabledName]) > 1 { + klog.Fatalf("Found %d %s tags: %q", len(tags[tagEnabledName]), tagEnabledName, tags[tagEnabledName]) + } + + // If we got here we are returning something. + tag := &enabledTagValue{} + + // Get the primary value. + parts := strings.Split(tags[tagEnabledName][0], ",") + if len(parts) >= 1 { + tag.value = parts[0] + } + + // Parse extra arguments. + parts = parts[1:] + for i := range parts { + kv := strings.SplitN(parts[i], "=", 2) + k := kv[0] + v := "" + if len(kv) == 2 { + v = kv[1] + } + switch k { + case "register": + if v != "false" { + tag.register = true + } + default: + klog.Fatalf("Unsupported %s param: %q", tagEnabledName, parts[i]) + } + } + return tag +} + +// TODO: This is created only to reduce number of changes in a single PR. +// Remove it and use PublicNamer instead. +func deepCopyNamer() *namer.NameStrategy { + return &namer.NameStrategy{ + Join: func(pre string, in []string, post string) string { + return strings.Join(in, "_") + }, + PrependPackageNames: 1, + } +} + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{ + "public": deepCopyNamer(), + "raw": namer.NewRawNamer("", nil), + } +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, gengo.StdBuildTag, gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + targetList := []generator.Target{} + + for _, i := range context.Inputs { + klog.V(3).Infof("considering pkg %q", i) + pkg := context.Universe[i] + + info, err := apidefinitions.Identify(pkg, apidefinitions.Deepcopy, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + klog.V(3).Infof(" inactive (no +k8s:deepcopy-gen, no type-level opt-in, or =false)") + continue + } + + // extractEnabledTag also parses the comma-separated subparams + // (e.g. ",register=true"), which Target.Values does not. + ptag := extractEnabledTag(pkg.Comments) + ptagValue := "" + ptagRegister := false + if ptag != nil { + ptagValue = ptag.value + if ptagValue != tagValuePackage { + klog.Fatalf("Package %v: unsupported %s value: %q", i, tagEnabledName, ptagValue) + } + ptagRegister = ptag.register + klog.V(3).Infof(" tag.value: %q, tag.register: %t", ptagValue, ptagRegister) + } else { + klog.V(3).Infof(" no tag") + } + + // If the pkg-scoped tag says to generate, we can skip scanning types. + pkgNeedsGeneration := (ptagValue == tagValuePackage) + if !pkgNeedsGeneration { + // If the pkg-scoped tag did not exist, scan all types for one that + // explicitly wants generation. Ensure all types that want generation + // can be copied. + var uncopyable []string + for _, t := range pkg.Types { + klog.V(3).Infof(" considering type %q", t.Name.String()) + ttag := extractEnabledTypeTag(t) + if ttag != nil && ttag.value == "true" { + klog.V(3).Infof(" tag=true") + if !copyableType(t) { + uncopyable = append(uncopyable, fmt.Sprintf("%v", t)) + } else { + pkgNeedsGeneration = true + } + } + } + if len(uncopyable) > 0 { + klog.Fatalf("Types requested deepcopy generation but are not copyable: %s", + strings.Join(uncopyable, ", ")) + } + } else { + // Don't write empty files + hasCopyable := false + for _, t := range pkg.Types { + if copyableType(t) { + hasCopyable = true + break + } + } + if !hasCopyable { + klog.V(3).Infof(" no copyable types; skipping") + pkgNeedsGeneration = false + } + } + + if pkgNeedsGeneration { + klog.V(3).Infof("Package %q needs generation", i) + targetList = append(targetList, + &generator.SimpleTarget{ + PkgName: strings.Split(path.Base(pkg.Path), ".")[0], + PkgPath: pkg.Path, + PkgDir: pkg.Dir, // output pkg is the same as the input + HeaderComment: boilerplate, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + return t.Name.Package == pkg.Path + }, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + return []generator.Generator{ + NewGenDeepCopy(args.OutputFile, pkg.Path, (ptagValue == tagValuePackage), ptagRegister), + } + }, + }) + } + } + return targetList +} + +// genDeepCopy produces a file with autogenerated deep-copy functions. +type genDeepCopy struct { + generator.GoGenerator + targetPackage string + allTypes bool + registerTypes bool + imports namer.ImportTracker + typesForInit []*types.Type +} + +func NewGenDeepCopy(outputFilename, targetPackage string, allTypes, registerTypes bool) generator.Generator { + return &genDeepCopy{ + GoGenerator: generator.GoGenerator{ + OutputFilename: outputFilename, + }, + targetPackage: targetPackage, + allTypes: allTypes, + registerTypes: registerTypes, + imports: generator.NewImportTrackerForPackage(targetPackage), + typesForInit: make([]*types.Type, 0), + } +} + +func (g *genDeepCopy) Namers(c *generator.Context) namer.NameSystems { + // Have the raw namer for this file track what it imports. + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.targetPackage, g.imports), + } +} + +func (g *genDeepCopy) Filter(c *generator.Context, t *types.Type) bool { + // Filter out types not being processed or not copyable within the package. + enabled := g.allTypes + if !enabled { + ttag := extractEnabledTypeTag(t) + if ttag != nil && ttag.value == "true" { + enabled = true + } + } + if !enabled { + return false + } + if !copyableType(t) { + klog.V(3).Infof("Type %v is not copyable", t) + return false + } + klog.V(3).Infof("Type %v is copyable", t) + g.typesForInit = append(g.typesForInit, t) + return true +} + +// deepCopyMethod returns the signature of a DeepCopy() method, nil or an error +// if the type does not match. This allows more efficient deep copy +// implementations to be defined by the type's author. The correct signature +// for a type T is: +// +// func (t T) DeepCopy() T +// +// or: +// +// func (t *T) DeepCopy() *T +func deepCopyMethod(t *types.Type) (*types.Signature, error) { + f, found := t.Methods["DeepCopy"] + if !found { + return nil, nil + } + if len(f.Signature.Parameters) != 0 { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected no parameters", t) + } + if len(f.Signature.Results) != 1 { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected exactly one result", t) + } + + ptrResult := f.Signature.Results[0].Type.Kind == types.Pointer && f.Signature.Results[0].Type.Elem.Name == t.Name + nonPtrResult := f.Signature.Results[0].Type.Name == t.Name + + if !ptrResult && !nonPtrResult { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected to return %s or *%s", t, t.Name.Name, t.Name.Name) + } + + ptrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Kind == types.Pointer && f.Signature.Receiver.Elem.Name == t.Name + nonPtrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Name == t.Name + + if ptrRcvr && !ptrResult { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected a *%s result for a *%s receiver", t, t.Name.Name, t.Name.Name) + } + if nonPtrRcvr && !nonPtrResult { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected a %s result for a %s receiver", t, t.Name.Name, t.Name.Name) + } + + return f.Signature, nil +} + +// deepCopyMethodOrDie returns the signatrue of a DeepCopy method, nil or calls klog.Fatalf +// if the type does not match. +func deepCopyMethodOrDie(t *types.Type) *types.Signature { + ret, err := deepCopyMethod(t) + if err != nil { + klog.Fatal(err) + } + return ret +} + +// deepCopyIntoMethod returns the signature of a DeepCopyInto() method, nil or an error +// if the type is wrong. DeepCopyInto allows more efficient deep copy +// implementations to be defined by the type's author. The correct signature +// for a type T is: +// +// func (t T) DeepCopyInto(t *T) +// +// or: +// +// func (t *T) DeepCopyInto(t *T) +func deepCopyIntoMethod(t *types.Type) (*types.Signature, error) { + f, found := t.Methods["DeepCopyInto"] + if !found { + return nil, nil + } + if len(f.Signature.Parameters) != 1 { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected exactly one parameter", t) + } + if len(f.Signature.Results) != 0 { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected no result type", t) + } + + ptrParam := f.Signature.Parameters[0].Type.Kind == types.Pointer && f.Signature.Parameters[0].Type.Elem.Name == t.Name + + if !ptrParam { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected parameter of type *%s", t, t.Name.Name) + } + + ptrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Kind == types.Pointer && f.Signature.Receiver.Elem.Name == t.Name + nonPtrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Name == t.Name + + if !ptrRcvr && !nonPtrRcvr { + // this should never happen + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected a receiver of type %s or *%s", t, t.Name.Name, t.Name.Name) + } + + return f.Signature, nil +} + +// deepCopyIntoMethodOrDie returns the signature of a DeepCopyInto() method, nil or calls klog.Fatalf +// if the type is wrong. +func deepCopyIntoMethodOrDie(t *types.Type) *types.Signature { + ret, err := deepCopyIntoMethod(t) + if err != nil { + klog.Fatal(err) + } + return ret +} + +func copyableType(t *types.Type) bool { + // If the type opts out of copy-generation, stop. + ttag := extractEnabledTypeTag(t) + if ttag != nil && ttag.value == "false" { + return false + } + + // Filter out private types. + if namer.IsPrivateGoName(t.Name.Name) { + return false + } + + if t.Kind == types.Alias { + // if the underlying built-in is not deepcopy-able, deepcopy is opt-in through definition of custom methods. + // Note that aliases of builtins, maps, slices can have deepcopy methods. + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + return true + } else { + return t.Underlying.Kind != types.Builtin || copyableType(t.Underlying) + } + } + + if t.Kind != types.Struct { + return false + } + + return true +} + +func underlyingType(t *types.Type) *types.Type { + for t.Kind == types.Alias { + t = t.Underlying + } + return t +} + +func (g *genDeepCopy) isOtherPackage(pkg string) bool { + if pkg == g.targetPackage { + return false + } + if strings.HasSuffix(pkg, "\""+g.targetPackage+"\"") { + return false + } + return true +} + +func (g *genDeepCopy) Imports(c *generator.Context) (imports []string) { + importLines := []string{} + for _, singleImport := range g.imports.ImportLines() { + if g.isOtherPackage(singleImport) { + importLines = append(importLines, singleImport) + } + } + return importLines +} + +func argsFromType(ts ...*types.Type) generator.Args { + a := generator.Args{ + "type": ts[0], + } + for i, t := range ts { + a[fmt.Sprintf("type%d", i+1)] = t + } + return a +} + +func (g *genDeepCopy) Init(c *generator.Context, w io.Writer) error { + return nil +} + +func (g *genDeepCopy) needsGeneration(t *types.Type) bool { + tag := extractEnabledTypeTag(t) + tv := "" + if tag != nil { + tv = tag.value + if tv != "true" && tv != "false" { + klog.Fatalf("Type %v: unsupported %s value: %q", t, tagEnabledName, tag.value) + } + } + if g.allTypes && tv == "false" { + // The whole package is being generated, but this type has opted out. + klog.V(2).Infof("Not generating for type %v because type opted out", t) + return false + } + if !g.allTypes && tv != "true" { + // The whole package is NOT being generated, and this type has NOT opted in. + klog.V(2).Infof("Not generating for type %v because type did not opt in", t) + return false + } + return true +} + +func extractInterfacesTag(t *types.Type) []string { + var result []string + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{interfacesTagName}, comments) + if err != nil { + klog.Fatalf("Error extracting %s tags: %v", interfacesTagName, err) + } + for _, v := range tags[interfacesTagName] { + if len(v) == 0 { + continue + } + intfs := strings.Split(v, ",") + for _, intf := range intfs { + if intf == "" { + continue + } + result = append(result, intf) + } + } + return result +} + +func extractNonPointerInterfaces(t *types.Type) (bool, error) { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{interfacesNonPointerTagName}, comments) + if err != nil { + return false, fmt.Errorf("failed to parse comments: %w", err) + } + + values := tags[interfacesNonPointerTagName] + if len(values) == 0 { + return false, nil + } + result := values[0] == "true" + for _, v := range values { + if v == "true" != result { + return false, fmt.Errorf("contradicting %v value %q found to previous value %v", interfacesNonPointerTagName, v, result) + } + } + return result, nil +} + +func (g *genDeepCopy) deepCopyableInterfacesInner(c *generator.Context, t *types.Type) ([]*types.Type, error) { + if t.Kind != types.Struct { + return nil, nil + } + + intfs := extractInterfacesTag(t) + + var ts []*types.Type + for _, intf := range intfs { + t := types.ParseFullyQualifiedName(intf) + klog.V(3).Infof("Loading package for interface %v", intf) + _, err := c.LoadPackages(t.Package) + if err != nil { + return nil, err + } + intfT := c.Universe.Type(t) + if intfT == nil { + return nil, fmt.Errorf("unknown type %q in %s tag of type %s", intf, interfacesTagName, intfT) + } + if intfT.Kind != types.Interface { + return nil, fmt.Errorf("type %q in %s tag of type %s is not an interface, but: %q", intf, interfacesTagName, t, intfT.Kind) + } + g.imports.AddType(intfT) + ts = append(ts, intfT) + } + + return ts, nil +} + +// deepCopyableInterfaces returns the interface types to implement and whether they apply to a non-pointer receiver. +func (g *genDeepCopy) deepCopyableInterfaces(c *generator.Context, t *types.Type) ([]*types.Type, bool, error) { + ts, err := g.deepCopyableInterfacesInner(c, t) + if err != nil { + return nil, false, err + } + + set := map[string]*types.Type{} + for _, t := range ts { + set[t.String()] = t + } + + result := []*types.Type{} + for _, t := range set { + result = append(result, t) + } + + TypeSlice(result).Sort() // we need a stable sorting because it determines the order in generation + + nonPointerReceiver, err := extractNonPointerInterfaces(t) + if err != nil { + return nil, false, err + } + + return result, nonPointerReceiver, nil +} + +type TypeSlice []*types.Type + +func (s TypeSlice) Len() int { return len(s) } +func (s TypeSlice) Less(i, j int) bool { return s[i].String() < s[j].String() } +func (s TypeSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s TypeSlice) Sort() { sort.Sort(s) } + +func (g *genDeepCopy) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + if !g.needsGeneration(t) { + return nil + } + klog.V(2).Infof("Generating deepcopy functions for type %v", t) + + sw := generator.NewSnippetWriter(w, c, "$", "$") + args := argsFromType(t) + + if deepCopyIntoMethodOrDie(t) == nil { + sw.Do("// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\n", args) + if isReference(t) { + sw.Do("func (in $.type|raw$) DeepCopyInto(out *$.type|raw$) {\n", args) + sw.Do("{in:=&in\n", nil) + } else { + sw.Do("func (in *$.type|raw$) DeepCopyInto(out *$.type|raw$) {\n", args) + } + if deepCopyMethodOrDie(t) != nil { + if t.Methods["DeepCopy"].Signature.Receiver.Kind == types.Pointer { + sw.Do("clone := in.DeepCopy()\n", nil) + sw.Do("*out = *clone\n", nil) + } else { + sw.Do("*out = in.DeepCopy()\n", nil) + } + sw.Do("return\n", nil) + } else { + g.generateFor(t, sw) + sw.Do("return\n", nil) + } + if isReference(t) { + sw.Do("}\n", nil) + } + sw.Do("}\n\n", nil) + } + + if deepCopyMethodOrDie(t) == nil { + sw.Do("// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new $.type|raw$.\n", args) + if isReference(t) { + sw.Do("func (in $.type|raw$) DeepCopy() $.type|raw$ {\n", args) + } else { + sw.Do("func (in *$.type|raw$) DeepCopy() *$.type|raw$ {\n", args) + } + sw.Do("if in == nil { return nil }\n", nil) + sw.Do("out := new($.type|raw$)\n", args) + sw.Do("in.DeepCopyInto(out)\n", nil) + if isReference(t) { + sw.Do("return *out\n", nil) + } else { + sw.Do("return out\n", nil) + } + sw.Do("}\n\n", nil) + } + + intfs, nonPointerReceiver, err := g.deepCopyableInterfaces(c, t) + if err != nil { + return err + } + for _, intf := range intfs { + sw.Do(fmt.Sprintf("// DeepCopy%s is an autogenerated deepcopy function, copying the receiver, creating a new $.type2|raw$.\n", intf.Name.Name), argsFromType(t, intf)) + if nonPointerReceiver { + sw.Do(fmt.Sprintf("func (in $.type|raw$) DeepCopy%s() $.type2|raw$ {\n", intf.Name.Name), argsFromType(t, intf)) + sw.Do("return *in.DeepCopy()", nil) + sw.Do("}\n\n", nil) + } else { + sw.Do(fmt.Sprintf("func (in *$.type|raw$) DeepCopy%s() $.type2|raw$ {\n", intf.Name.Name), argsFromType(t, intf)) + sw.Do("if c := in.DeepCopy(); c != nil {\n", nil) + sw.Do("return c\n", nil) + sw.Do("}\n", nil) + sw.Do("return nil\n", nil) + sw.Do("}\n\n", nil) + } + } + + return sw.Error() +} + +// isReference return true for pointer, maps, slices and aliases of those. +func isReference(t *types.Type) bool { + if t.Kind == types.Pointer || t.Kind == types.Map || t.Kind == types.Slice { + return true + } + return t.Kind == types.Alias && isReference(underlyingType(t)) +} + +// we use the system of shadowing 'in' and 'out' so that the same code is valid +// at any nesting level. This makes the autogenerator easy to understand, and +// the compiler shouldn't care. +func (g *genDeepCopy) generateFor(t *types.Type, sw *generator.SnippetWriter) { + // derive inner types if t is an alias. We call the do* methods below with the alias type. + // basic rule: generate according to inner type, but construct objects with the alias type. + ut := underlyingType(t) + + var f func(*types.Type, *generator.SnippetWriter) + switch ut.Kind { + case types.Builtin: + f = g.doBuiltin + case types.Map: + f = g.doMap + case types.Slice: + f = g.doSlice + case types.Struct: + f = g.doStruct + case types.Pointer: + f = g.doPointer + case types.Interface: + // interfaces are handled in-line in the other cases + klog.Fatalf("Hit an interface type %v. This should never happen.", t) + case types.Alias: + // can never happen because we branch on the underlying type which is never an alias + klog.Fatalf("Hit an alias type %v. This should never happen.", t) + default: + klog.Fatalf("Hit an unsupported type %v.", t) + } + f(t, sw) +} + +// doBuiltin generates code for a builtin or an alias to a builtin. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doBuiltin(t *types.Type, sw *generator.SnippetWriter) { + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + sw.Do("*out = in.DeepCopy()\n", nil) + return + } + + sw.Do("*out = *in\n", nil) +} + +// doMap generates code for a map or an alias to a map. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doMap(t *types.Type, sw *generator.SnippetWriter) { + ut := underlyingType(t) + uet := underlyingType(ut.Elem) + + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + sw.Do("*out = in.DeepCopy()\n", nil) + return + } + + if !ut.Key.IsAssignable() { + klog.Fatalf("Hit an unsupported type %v for: %v", uet, t) + } + + sw.Do("*out = make($.|raw$, len(*in))\n", t) + sw.Do("for key, val := range *in {\n", nil) + dc, dci := deepCopyMethodOrDie(ut.Elem), deepCopyIntoMethodOrDie(ut.Elem) + switch { + case dc != nil || dci != nil: + // Note: a DeepCopy exists because it is added if DeepCopyInto is manually defined + leftPointer := ut.Elem.Kind == types.Pointer + rightPointer := !isReference(ut.Elem) + if dc != nil { + rightPointer = dc.Results[0].Type.Kind == types.Pointer + } + if leftPointer == rightPointer { + sw.Do("(*out)[key] = val.DeepCopy()\n", nil) + } else if leftPointer { + sw.Do("x := val.DeepCopy()\n", nil) + sw.Do("(*out)[key] = &x\n", nil) + } else { + sw.Do("(*out)[key] = *val.DeepCopy()\n", nil) + } + case ut.Elem.IsAnonymousStruct(): // not uet here because it needs type cast + sw.Do("(*out)[key] = val\n", nil) + case uet.IsAssignable(): + sw.Do("(*out)[key] = val\n", nil) + case uet.Kind == types.Interface: + // Note: do not generate code that won't compile as `DeepCopyinterface{}()` is not a valid function + if uet.Name.Name == "interface{}" { + klog.Fatalf("DeepCopy of %q is unsupported. Instead, use named interfaces with DeepCopy as one of the methods.", uet.Name.Name) + } + sw.Do("if val == nil {(*out)[key]=nil} else {\n", nil) + // Note: if t.Elem has been an alias "J" of an interface "I" in Go, we will see it + // as kind Interface of name "J" here, i.e. generate val.DeepCopyJ(). The golang + // parser does not give us the underlying interface name. So we cannot do any better. + sw.Do(fmt.Sprintf("(*out)[key] = val.DeepCopy%s()\n", uet.Name.Name), nil) + sw.Do("}\n", nil) + case uet.Kind == types.Slice || uet.Kind == types.Map || uet.Kind == types.Pointer: + sw.Do("var outVal $.|raw$\n", uet) + sw.Do("if val == nil { (*out)[key] = nil } else {\n", nil) + sw.Do("in, out := &val, &outVal\n", uet) + g.generateFor(ut.Elem, sw) + sw.Do("}\n", nil) + sw.Do("(*out)[key] = outVal\n", nil) + case uet.Kind == types.Struct: + sw.Do("(*out)[key] = *val.DeepCopy()\n", uet) + default: + klog.Fatalf("Hit an unsupported type %v for %v", uet, t) + } + sw.Do("}\n", nil) +} + +// doSlice generates code for a slice or an alias to a slice. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doSlice(t *types.Type, sw *generator.SnippetWriter) { + ut := underlyingType(t) + uet := underlyingType(ut.Elem) + + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + sw.Do("*out = in.DeepCopy()\n", nil) + return + } + + sw.Do("*out = make($.|raw$, len(*in))\n", t) + if deepCopyMethodOrDie(ut.Elem) != nil || deepCopyIntoMethodOrDie(ut.Elem) != nil { + sw.Do("for i := range *in {\n", nil) + // Note: a DeepCopyInto exists because it is added if DeepCopy is manually defined + sw.Do("(*in)[i].DeepCopyInto(&(*out)[i])\n", nil) + sw.Do("}\n", nil) + } else if uet.Kind == types.Builtin || uet.IsAssignable() { + sw.Do("copy(*out, *in)\n", nil) + } else { + sw.Do("for i := range *in {\n", nil) + if uet.Kind == types.Slice || uet.Kind == types.Map || uet.Kind == types.Pointer || deepCopyMethodOrDie(ut.Elem) != nil || deepCopyIntoMethodOrDie(ut.Elem) != nil { + sw.Do("if (*in)[i] != nil {\n", nil) + sw.Do("in, out := &(*in)[i], &(*out)[i]\n", nil) + g.generateFor(ut.Elem, sw) + sw.Do("}\n", nil) + } else if uet.Kind == types.Interface { + // Note: do not generate code that won't compile as `DeepCopyinterface{}()` is not a valid function + if uet.Name.Name == "interface{}" { + klog.Fatalf("DeepCopy of %q is unsupported. Instead, use named interfaces with DeepCopy as one of the methods.", uet.Name.Name) + } + sw.Do("if (*in)[i] != nil {\n", nil) + // Note: if t.Elem has been an alias "J" of an interface "I" in Go, we will see it + // as kind Interface of name "J" here, i.e. generate val.DeepCopyJ(). The golang + // parser does not give us the underlying interface name. So we cannot do any better. + sw.Do(fmt.Sprintf("(*out)[i] = (*in)[i].DeepCopy%s()\n", uet.Name.Name), nil) + sw.Do("}\n", nil) + } else if uet.Kind == types.Struct { + sw.Do("(*in)[i].DeepCopyInto(&(*out)[i])\n", nil) + } else { + klog.Fatalf("Hit an unsupported type %v for %v", uet, t) + } + sw.Do("}\n", nil) + } +} + +// doStruct generates code for a struct or an alias to a struct. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doStruct(t *types.Type, sw *generator.SnippetWriter) { + ut := underlyingType(t) + + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + sw.Do("*out = in.DeepCopy()\n", nil) + return + } + + // Simple copy covers a lot of cases. + sw.Do("*out = *in\n", nil) + + // Now fix-up fields as needed. + for _, m := range ut.Members { + ft := m.Type + uft := underlyingType(ft) + + args := generator.Args{ + "type": ft, + "kind": ft.Kind, + "name": m.Name, + } + dc, dci := deepCopyMethodOrDie(ft), deepCopyIntoMethodOrDie(ft) + switch { + case dc != nil || dci != nil: + // Note: a DeepCopyInto exists because it is added if DeepCopy is manually defined + leftPointer := ft.Kind == types.Pointer + rightPointer := !isReference(ft) + if dc != nil { + rightPointer = dc.Results[0].Type.Kind == types.Pointer + } + if leftPointer == rightPointer { + sw.Do("out.$.name$ = in.$.name$.DeepCopy()\n", args) + } else if leftPointer { + sw.Do("x := in.$.name$.DeepCopy()\n", args) + sw.Do("out.$.name$ = = &x\n", args) + } else { + sw.Do("in.$.name$.DeepCopyInto(&out.$.name$)\n", args) + } + case uft.Kind == types.Builtin: + // the initial *out = *in was enough + case uft.Kind == types.Map, uft.Kind == types.Slice, uft.Kind == types.Pointer: + // Fixup non-nil reference-semantic types. + sw.Do("if in.$.name$ != nil {\n", args) + sw.Do("in, out := &in.$.name$, &out.$.name$\n", args) + g.generateFor(ft, sw) + sw.Do("}\n", nil) + case uft.Kind == types.Array: + sw.Do("out.$.name$ = in.$.name$\n", args) + case uft.Kind == types.Struct: + if ft.IsAssignable() { + sw.Do("out.$.name$ = in.$.name$\n", args) + } else { + sw.Do("in.$.name$.DeepCopyInto(&out.$.name$)\n", args) + } + case uft.Kind == types.Interface: + // Note: do not generate code that won't compile as `DeepCopyinterface{}()` is not a valid function + if uft.Name.Name == "interface{}" { + klog.Fatalf("DeepCopy of %q is unsupported. Instead, use named interfaces with DeepCopy as one of the methods.", uft.Name.Name) + } + sw.Do("if in.$.name$ != nil {\n", args) + // Note: if t.Elem has been an alias "J" of an interface "I" in Go, we will see it + // as kind Interface of name "J" here, i.e. generate val.DeepCopyJ(). The golang + // parser does not give us the underlying interface name. So we cannot do any better. + sw.Do(fmt.Sprintf("out.$.name$ = in.$.name$.DeepCopy%s()\n", uft.Name.Name), args) + sw.Do("}\n", nil) + default: + klog.Fatalf("Hit an unsupported type '%v' for '%v', from %v.%v", uft, ft, t, m.Name) + } + } +} + +// doPointer generates code for a pointer or an alias to a pointer. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doPointer(t *types.Type, sw *generator.SnippetWriter) { + ut := underlyingType(t) + uet := underlyingType(ut.Elem) + + dc, dci := deepCopyMethodOrDie(ut.Elem), deepCopyIntoMethodOrDie(ut.Elem) + switch { + case dc != nil || dci != nil: + rightPointer := !isReference(ut.Elem) + if dc != nil { + rightPointer = dc.Results[0].Type.Kind == types.Pointer + } + if rightPointer { + sw.Do("*out = (*in).DeepCopy()\n", nil) + } else { + sw.Do("x := (*in).DeepCopy()\n", nil) + sw.Do("*out = &x\n", nil) + } + case uet.IsAssignable(): + sw.Do("*out = new($.Elem|raw$)\n", ut) + sw.Do("**out = **in", nil) + case uet.Kind == types.Map, uet.Kind == types.Slice, uet.Kind == types.Pointer: + sw.Do("*out = new($.Elem|raw$)\n", ut) + sw.Do("if **in != nil {\n", nil) + sw.Do("in, out := *in, *out\n", nil) + g.generateFor(uet, sw) + sw.Do("}\n", nil) + case uet.Kind == types.Struct: + sw.Do("*out = new($.Elem|raw$)\n", ut) + sw.Do("(*in).DeepCopyInto(*out)\n", nil) + default: + klog.Fatalf("Hit an unsupported type %v for %v", uet, t) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy_targets_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy_targets_test.go new file mode 100644 index 0000000000..c6d5024eab --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy_targets_test.go @@ -0,0 +1,223 @@ +/* +Copyright 2025 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 generators + +import ( + "reflect" + "sort" + "testing" + + "k8s.io/code-generator/cmd/deepcopy-gen/args" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" +) + +// copyableStruct returns a struct type with a single primitive int32 field. +// copyableType in deepcopy.go accepts a non-private struct kind. +func copyableStruct(pkgPath, name string, comments []string) *types.Type { + return &types.Type{ + Name: types.Name{Package: pkgPath, Name: name}, + Kind: types.Struct, + CommentLines: comments, + Members: []types.Member{ + {Name: "X", Type: types.Int32}, + }, + } +} + +func TestGetTargets(t *testing.T) { + type pkgSpec struct { + path string + comments []string + // types maps type-name to its CommentLines (a copyable struct is + // synthesized for each entry). nil means no types in the package. + types map[string][]string + } + + cases := []struct { + name string + pkgs []pkgSpec + wantPkgs []string + // wantAllTypes maps PkgPath -> expected genDeepCopy.allTypes value. + wantAllTypes map[string]bool + // wantRegister maps PkgPath -> expected genDeepCopy.registerTypes value. + wantRegister map[string]bool + }{ + { + name: "package tag with copyable struct activates", + pkgs: []pkgSpec{ + { + path: "example.com/pkg/a", + comments: []string{"+k8s:deepcopy-gen=package"}, + types: map[string][]string{"T": nil}, + }, + }, + wantPkgs: []string{"example.com/pkg/a"}, + wantAllTypes: map[string]bool{"example.com/pkg/a": true}, + wantRegister: map[string]bool{"example.com/pkg/a": false}, + }, + { + name: "package tag with register=false activates with register=false", + pkgs: []pkgSpec{ + { + path: "example.com/pkg/b", + comments: []string{"+k8s:deepcopy-gen=package,register=false"}, + types: map[string][]string{"T": nil}, + }, + }, + wantPkgs: []string{"example.com/pkg/b"}, + wantAllTypes: map[string]bool{"example.com/pkg/b": true}, + wantRegister: map[string]bool{"example.com/pkg/b": false}, + }, + { + name: "package tag with register=true activates with register=true", + pkgs: []pkgSpec{ + { + path: "example.com/pkg/c", + comments: []string{"+k8s:deepcopy-gen=package,register=true"}, + types: map[string][]string{"T": nil}, + }, + }, + wantPkgs: []string{"example.com/pkg/c"}, + wantAllTypes: map[string]bool{"example.com/pkg/c": true}, + wantRegister: map[string]bool{"example.com/pkg/c": true}, + }, + { + name: "package opted out is skipped", + pkgs: []pkgSpec{ + { + path: "example.com/pkg/d", + comments: []string{"+k8s:deepcopy-gen=false"}, + types: map[string][]string{"T": nil}, + }, + }, + wantPkgs: nil, + }, + { + name: "no package tag but type opts in activates", + pkgs: []pkgSpec{ + { + path: "example.com/pkg/e", + types: map[string][]string{ + "T": {"+k8s:deepcopy-gen=true"}, + }, + }, + }, + wantPkgs: []string{"example.com/pkg/e"}, + wantAllTypes: map[string]bool{"example.com/pkg/e": false}, + wantRegister: map[string]bool{"example.com/pkg/e": false}, + }, + { + name: "package tag but no copyable types is skipped", + pkgs: []pkgSpec{ + { + path: "example.com/pkg/f", + comments: []string{"+k8s:deepcopy-gen=package"}, + types: nil, + }, + }, + wantPkgs: nil, + }, + { + name: "no tag and no opt-in types is skipped", + pkgs: []pkgSpec{ + { + path: "example.com/pkg/g", + types: map[string][]string{"T": nil}, + }, + }, + wantPkgs: nil, + }, + // Ecosystem regression: a third-party generator's tag in the same + // doc.go must NOT cause deepcopy-gen to fail. The deepcopy-gen + // tag still activates as expected. + { + name: "foreign third-party generator tag is ignored", + pkgs: []pkgSpec{ + { + path: "example.com/pkg/h", + comments: []string{ + "+k8s:my-custom-gen=value", + "+k8s:deepcopy-gen=package", + }, + types: map[string][]string{"T": nil}, + }, + }, + wantPkgs: []string{"example.com/pkg/h"}, + wantAllTypes: map[string]bool{"example.com/pkg/h": true}, + wantRegister: map[string]bool{"example.com/pkg/h": false}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + universe := types.Universe{} + var inputs []string + for _, ps := range tc.pkgs { + pkg := &types.Package{ + Path: ps.path, + Dir: ps.path, + Name: "pkg", + Comments: ps.comments, + Types: map[string]*types.Type{}, + } + for tname, tcomments := range ps.types { + pkg.Types[tname] = copyableStruct(ps.path, tname, tcomments) + } + universe[ps.path] = pkg + inputs = append(inputs, ps.path) + } + + ctx := &generator.Context{ + Universe: universe, + Inputs: inputs, + } + + result := GetTargets(ctx, args.New()) + + var gotPkgs []string + for _, tgt := range result { + gotPkgs = append(gotPkgs, tgt.Path()) + } + sort.Strings(gotPkgs) + want := append([]string(nil), tc.wantPkgs...) + sort.Strings(want) + if !reflect.DeepEqual(gotPkgs, want) { + t.Errorf("PkgPaths = %v, want %v", gotPkgs, want) + } + + for _, tgt := range result { + gens := tgt.Generators(ctx) + if len(gens) != 1 { + t.Errorf("pkg %q: got %d generators, want 1", tgt.Path(), len(gens)) + continue + } + gdc, ok := gens[0].(*genDeepCopy) + if !ok { + t.Errorf("pkg %q: generator type = %T, want *genDeepCopy", tgt.Path(), gens[0]) + continue + } + if want, ok := tc.wantAllTypes[tgt.Path()]; ok && gdc.allTypes != want { + t.Errorf("pkg %q: allTypes = %v, want %v", tgt.Path(), gdc.allTypes, want) + } + if want, ok := tc.wantRegister[tgt.Path()]; ok && gdc.registerTypes != want { + t.Errorf("pkg %q: registerTypes = %v, want %v", tgt.Path(), gdc.registerTypes, want) + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy_test.go new file mode 100644 index 0000000000..f1c3427db1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/generators/deepcopy_test.go @@ -0,0 +1,696 @@ +/* +Copyright 2016 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 generators + +import ( + "reflect" + "testing" + + "k8s.io/gengo/v2/types" +) + +func Test_deepCopyMethod(t *testing.T) { + testCases := []struct { + typ types.Type + expect bool + error bool + }{ + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + // No DeepCopy method. + Methods: map[string]*types.Type{}, + }, + expect: false, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // No DeepCopy method. + "method": { + Name: types.Name{Package: "pkgname", Name: "func()"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{}, + }, + }, + }, + }, + expect: false, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature (no result). + "DeepCopy": { + Name: types.Name{Package: "pkgname", Name: "func()"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{}, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature (wrong result). + "DeepCopy": { + Name: types.Name{Package: "pkgname", Name: "func() int"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{ + { + Type: &types.Type{Name: types.Name{Name: "int"}, Kind: types.Builtin}, + }, + }, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature with pointer receiver, but non-pointer result. + "DeepCopy": { + Name: types.Name{Package: "pkgname", Name: "func() pkgname.typename"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{ + { + Type: &types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + }, + }, + }, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature with non-pointer receiver, but pointer result. + "DeepCopy": { + Name: types.Name{Package: "pkgname", Name: "func() pkgname.typename"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{ + { + Type: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + }, + }, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Correct signature with non-pointer receiver. + "DeepCopy": { + Name: types.Name{Package: "pkgname", Name: "func() pkgname.typename"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{ + { + Type: &types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + }, + }, + }, + }, + }, + }, + }, + expect: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Correct signature with pointer receiver. + "DeepCopy": { + Name: types.Name{Package: "pkgname", Name: "func() pkgname.typename"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{ + { + Type: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + }, + }, + }, + }, + }, + }, + expect: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature (has params). + "DeepCopy": { + Name: types.Name{Package: "pkgname", Name: "func(int) pkgname.typename"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{ + { + Type: &types.Type{ + Name: types.Name{Name: "int"}, + Kind: types.Builtin, + }, + }, + }, + Results: []*types.ParamResult{ + { + Type: &types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + }, + }, + }, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature (extra results). + "DeepCopy": { + Name: types.Name{Package: "pkgname", Name: "func() (pkgname.typename, int)"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{ + { + Type: &types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + }, + }, + { + Type: &types.Type{ + Name: types.Name{Name: "int"}, + Kind: types.Builtin, + }, + }, + }, + }, + }, + }, + }, + expect: false, + error: true, + }, + } + + for i, tc := range testCases { + r, err := deepCopyMethod(&tc.typ) + if tc.error && err == nil { + t.Errorf("case[%d]: expected an error, got none", i) + } else if !tc.error && err != nil { + t.Errorf("case[%d]: expected no error, got: %v", i, err) + } else if !tc.error && (r != nil) != tc.expect { + t.Errorf("case[%d]: expected result %v, got: %v", i, tc.expect, r) + } + } +} + +func Test_deepCopyIntoMethod(t *testing.T) { + testCases := []struct { + typ types.Type + expect bool + error bool + }{ + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + // No DeepCopyInto method. + Methods: map[string]*types.Type{}, + }, + expect: false, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // No DeepCopyInto method. + "method": { + Name: types.Name{Package: "pkgname", Name: "func()"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{}, + }, + }, + }, + }, + expect: false, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature (no parameter). + "DeepCopyInto": { + Name: types.Name{Package: "pkgname", Name: "func()"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{}, + Results: []*types.ParamResult{}, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature (unexpected result). + "DeepCopyInto": { + Name: types.Name{Package: "pkgname", Name: "func(*pkgname.typename) int"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{ + { + Type: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + }, + }, + Results: []*types.ParamResult{ + { + Type: &types.Type{ + Name: types.Name{Name: "int"}, + Kind: types.Builtin, + }, + }, + }, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature (non-pointer parameter, pointer receiver). + "DeepCopyInto": { + Name: types.Name{Package: "pkgname", Name: "func(pkgname.typename)"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{ + { + Type: &types.Type{ + Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + }, + Results: []*types.ParamResult{}, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Wrong signature (non-pointer parameter, non-pointer receiver). + "DeepCopyInto": { + Name: types.Name{Package: "pkgname", Name: "func(pkgname.typename)"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + Parameters: []*types.ParamResult{ + { + + Type: &types.Type{ + Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + }, + Results: []*types.ParamResult{}, + }, + }, + }, + }, + expect: false, + error: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Correct signature with non-pointer receiver. + "DeepCopyInto": { + Name: types.Name{Package: "pkgname", Name: "func(*pkgname.typename)"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + Parameters: []*types.ParamResult{ + { + Type: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + }, + }, + Results: []*types.ParamResult{}, + }, + }, + }, + }, + expect: true, + }, + { + typ: types.Type{ + Name: types.Name{Package: "pkgname", Name: "typename"}, + Kind: types.Builtin, + Methods: map[string]*types.Type{ + // Correct signature with pointer receiver. + "DeepCopyInto": { + Name: types.Name{Package: "pkgname", Name: "func(*pkgname.typename)"}, + Kind: types.Func, + Signature: &types.Signature{ + Receiver: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + Parameters: []*types.ParamResult{ + { + Type: &types.Type{ + Kind: types.Pointer, + Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}}, + }, + }, + }, + Results: []*types.ParamResult{}, + }, + }, + }, + }, + expect: true, + }, + } + + for i, tc := range testCases { + r, err := deepCopyIntoMethod(&tc.typ) + if tc.error && err == nil { + t.Errorf("case[%d]: expected an error, got none", i) + } else if !tc.error && err != nil { + t.Errorf("case[%d]: expected no error, got: %v", i, err) + } else if !tc.error && (r != nil) != tc.expect { + t.Errorf("case[%d]: expected result %v, got: %v", i, tc.expect, r) + } + } +} + +func Test_extractTagParams(t *testing.T) { + testCases := []struct { + comments []string + expect *enabledTagValue + }{ + { + comments: []string{ + "Human comment", + }, + expect: nil, + }, + { + comments: []string{ + "Human comment", + "+k8s:deepcopy-gen", + }, + expect: &enabledTagValue{ + value: "", + register: false, + }, + }, + { + comments: []string{ + "Human comment", + "+k8s:deepcopy-gen=package", + }, + expect: &enabledTagValue{ + value: "package", + register: false, + }, + }, + { + comments: []string{ + "Human comment", + "+k8s:deepcopy-gen=package,register", + }, + expect: &enabledTagValue{ + value: "package", + register: true, + }, + }, + { + comments: []string{ + "Human comment", + "+k8s:deepcopy-gen=package,register=true", + }, + expect: &enabledTagValue{ + value: "package", + register: true, + }, + }, + { + comments: []string{ + "Human comment", + "+k8s:deepcopy-gen=package,register=false", + }, + expect: &enabledTagValue{ + value: "package", + register: false, + }, + }, + } + + for i, tc := range testCases { + r := extractEnabledTag(tc.comments) + if r == nil && tc.expect != nil { + t.Errorf("case[%d]: expected non-nil", i) + } + if r != nil && tc.expect == nil { + t.Errorf("case[%d]: expected nil, got %v", i, *r) + } + if r != nil && *r != *tc.expect { + t.Errorf("case[%d]: expected %v, got %v", i, *tc.expect, *r) + } + } +} + +func Test_extractInterfacesTag(t *testing.T) { + testCases := []struct { + comments, secondComments []string + expect []string + }{ + { + comments: []string{}, + expect: nil, + }, + { + comments: []string{ + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object", + }, + expect: []string{ + "k8s.io/kubernetes/runtime.Object", + }, + }, + { + comments: []string{ + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object", + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.List", + }, + expect: []string{ + "k8s.io/kubernetes/runtime.Object", + "k8s.io/kubernetes/runtime.List", + }, + }, + { + comments: []string{ + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object", + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object", + }, + expect: []string{ + "k8s.io/kubernetes/runtime.Object", + "k8s.io/kubernetes/runtime.Object", + }, + }, + { + secondComments: []string{ + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object", + }, + expect: []string{ + "k8s.io/kubernetes/runtime.Object", + }, + }, + { + comments: []string{ + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object", + }, + secondComments: []string{ + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.List", + }, + expect: []string{ + "k8s.io/kubernetes/runtime.List", + "k8s.io/kubernetes/runtime.Object", + }, + }, + { + comments: []string{ + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object", + }, + secondComments: []string{ + "+k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object", + }, + expect: []string{ + "k8s.io/kubernetes/runtime.Object", + "k8s.io/kubernetes/runtime.Object", + }, + }, + } + + for i, tc := range testCases { + typ := &types.Type{ + CommentLines: tc.comments, + SecondClosestCommentLines: tc.secondComments, + } + r := extractInterfacesTag(typ) + if r == nil && tc.expect != nil { + t.Errorf("case[%d]: expected non-nil", i) + } + if r != nil && tc.expect == nil { + t.Errorf("case[%d]: expected nil, got %v", i, r) + } + if r != nil && !reflect.DeepEqual(r, tc.expect) { + t.Errorf("case[%d]: expected %v, got %v", i, tc.expect, r) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/main.go new file mode 100644 index 0000000000..aaa3155a01 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/main.go @@ -0,0 +1,110 @@ +/* +Copyright 2015 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. +*/ + +// deepcopy-gen is a tool for auto-generating DeepCopy functions. +// +// Given a list of input directories, it will generate DeepCopy and +// DeepCopyInto methods that efficiently perform a full deep-copy of each type. +// If these methods already exist (are predefined by the developer), they are +// used instead of generating new ones. Generated code will use standard value +// assignment whenever possible. If that is not possible it will try to call +// its own generated copy function for the type. Failing that, it will fall +// back on `conversion.Cloner.DeepCopy(val)` to make the copy. The resulting +// file will be stored in the same directory as the processed source package. +// +// If interfaces are referenced in types, it is expected that corresponding +// DeepCopyInterfaceName methods exist, e.g. DeepCopyObject for runtime.Object. +// These can be predefined by the developer or generated through tags, see +// below. They must be added to the interfaces themselves manually, e.g. +// +// type Object interface { +// ... +// DeepCopyObject() Object +// } +// +// Generation is governed by comment tags in the source. Any package may +// request DeepCopy generation by including a comment in the file-comments of +// one file, of the form: +// +// // +k8s:deepcopy-gen=package +// +// DeepCopy functions can be generated for individual types, rather than the +// entire package by specifying a comment on the type definition of the form: +// +// // +k8s:deepcopy-gen=true +// +// When generating for a whole package, individual types may opt out of +// DeepCopy generation by specifying a comment on the type definition of the +// form: +// +// // +k8s:deepcopy-gen=false +// +// Additional DeepCopyInterfaceName methods can be generated by specifying a +// comment on the type definition of the form: +// +// // +k8s:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object,k8s.io/kubernetes/runtime.List +// +// This leads to the generation of DeepCopyObject and DeepCopyList with the given +// interfaces as return types. We say that the tagged type implements deepcopy for the +// interfaces. +// +// The deepcopy funcs for interfaces using "+k8s:deepcopy-gen:interfaces" use the pointer +// of the type as receiver. For those special cases where the non-pointer object should +// implement the interface, this can be done with: +// +// // +k8s:deepcopy-gen:nonpointer-interfaces=true +package main + +import ( + "flag" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/deepcopy-gen/args" + "k8s.io/code-generator/cmd/deepcopy-gen/generators" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + args := args.New() + + args.AddFlags(pflag.CommandLine) + flag.Set("logtostderr", "true") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return generators.GetTargets(context, args) + } + + // Run it. + if err := gengo.Execute( + generators.NameSystems(), + generators.DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases/doc.go new file mode 100644 index 0000000000..d007d34f78 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases/doc.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 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. +*/ + +// +k8s:deepcopy-gen=package + +// This is a test package. +package aliases + +// Note: the following AliasInterface and AliasAliasInterface +k8s:deepcopy-gen:interfaces tags +// are necessary because Golang flattens interface alias in the type system. I.e. an alias J of +// an interface I is actually equivalent to I. So support deepcopies of those aliases, we have +// to implement all aliases of that interface. + +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases.Interface +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases.AliasInterface +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases.AliasAliasInterface +type Foo struct { + X int +} + +type Interface interface { + DeepCopyInterface() Interface + DeepCopyAliasInterface() AliasInterface + DeepCopyAliasAliasInterface() AliasAliasInterface +} + +type Builtin int +type Slice []int +type Pointer *int +type PointerAlias *Builtin +type Struct Foo +type Map map[string]int + +type FooAlias Foo +type FooSlice []Foo +type FooPointer *Foo +type FooMap map[string]Foo + +type AliasBuiltin Builtin +type AliasSlice Slice +type AliasPointer Pointer +type AliasStruct Struct +type AliasMap Map + +type AliasInterface Interface +type AliasAliasInterface AliasInterface +type AliasInterfaceMap map[string]AliasInterface +type AliasInterfaceSlice []AliasInterface + +// Aliases +type Ttest struct { + Builtin Builtin + Slice Slice + Pointer Pointer + PointerAlias PointerAlias + Struct Struct + Map Map + SliceSlice []Slice + MapSlice map[string]Slice + + FooAlias FooAlias + FooSlice FooSlice + FooPointer FooPointer + FooMap FooMap + + AliasBuiltin AliasBuiltin + AliasSlice AliasSlice + AliasPointer AliasPointer + AliasStruct AliasStruct + AliasMap AliasMap + + AliasInterface AliasInterface + AliasAliasInterface AliasAliasInterface + AliasInterfaceMap AliasInterfaceMap + AliasInterfaceSlice AliasInterfaceSlice +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases/zz_generated.deepcopy.go new file mode 100644 index 0000000000..e7c6827f02 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases/zz_generated.deepcopy.go @@ -0,0 +1,413 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package aliases + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in AliasInterfaceMap) DeepCopyInto(out *AliasInterfaceMap) { + { + in := &in + *out = make(AliasInterfaceMap, len(*in)) + for key, val := range *in { + if val == nil { + (*out)[key] = nil + } else { + (*out)[key] = val.DeepCopyAliasInterface() + } + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasInterfaceMap. +func (in AliasInterfaceMap) DeepCopy() AliasInterfaceMap { + if in == nil { + return nil + } + out := new(AliasInterfaceMap) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in AliasInterfaceSlice) DeepCopyInto(out *AliasInterfaceSlice) { + { + in := &in + *out = make(AliasInterfaceSlice, len(*in)) + for i := range *in { + if (*in)[i] != nil { + (*out)[i] = (*in)[i].DeepCopyAliasInterface() + } + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasInterfaceSlice. +func (in AliasInterfaceSlice) DeepCopy() AliasInterfaceSlice { + if in == nil { + return nil + } + out := new(AliasInterfaceSlice) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in AliasMap) DeepCopyInto(out *AliasMap) { + { + in := &in + *out = make(AliasMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasMap. +func (in AliasMap) DeepCopy() AliasMap { + if in == nil { + return nil + } + out := new(AliasMap) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in AliasSlice) DeepCopyInto(out *AliasSlice) { + { + in := &in + *out = make(AliasSlice, len(*in)) + copy(*out, *in) + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasSlice. +func (in AliasSlice) DeepCopy() AliasSlice { + if in == nil { + return nil + } + out := new(AliasSlice) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AliasStruct) DeepCopyInto(out *AliasStruct) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasStruct. +func (in *AliasStruct) DeepCopy() *AliasStruct { + if in == nil { + return nil + } + out := new(AliasStruct) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Foo) DeepCopyInto(out *Foo) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Foo. +func (in *Foo) DeepCopy() *Foo { + if in == nil { + return nil + } + out := new(Foo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyAliasAliasInterface is an autogenerated deepcopy function, copying the receiver, creating a new AliasAliasInterface. +func (in *Foo) DeepCopyAliasAliasInterface() AliasAliasInterface { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyAliasInterface is an autogenerated deepcopy function, copying the receiver, creating a new AliasInterface. +func (in *Foo) DeepCopyAliasInterface() AliasInterface { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Interface. +func (in *Foo) DeepCopyInterface() Interface { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FooAlias) DeepCopyInto(out *FooAlias) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FooAlias. +func (in *FooAlias) DeepCopy() *FooAlias { + if in == nil { + return nil + } + out := new(FooAlias) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in FooMap) DeepCopyInto(out *FooMap) { + { + in := &in + *out = make(FooMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FooMap. +func (in FooMap) DeepCopy() FooMap { + if in == nil { + return nil + } + out := new(FooMap) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in FooSlice) DeepCopyInto(out *FooSlice) { + { + in := &in + *out = make(FooSlice, len(*in)) + copy(*out, *in) + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FooSlice. +func (in FooSlice) DeepCopy() FooSlice { + if in == nil { + return nil + } + out := new(FooSlice) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Map) DeepCopyInto(out *Map) { + { + in := &in + *out = make(Map, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Map. +func (in Map) DeepCopy() Map { + if in == nil { + return nil + } + out := new(Map) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Slice) DeepCopyInto(out *Slice) { + { + in := &in + *out = make(Slice, len(*in)) + copy(*out, *in) + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Slice. +func (in Slice) DeepCopy() Slice { + if in == nil { + return nil + } + out := new(Slice) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Struct) DeepCopyInto(out *Struct) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Struct. +func (in *Struct) DeepCopy() *Struct { + if in == nil { + return nil + } + out := new(Struct) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + if in.Slice != nil { + in, out := &in.Slice, &out.Slice + *out = make(Slice, len(*in)) + copy(*out, *in) + } + if in.Pointer != nil { + in, out := &in.Pointer, &out.Pointer + *out = new(int) + **out = **in + } + if in.PointerAlias != nil { + in, out := &in.PointerAlias, &out.PointerAlias + *out = new(Builtin) + **out = **in + } + out.Struct = in.Struct + if in.Map != nil { + in, out := &in.Map, &out.Map + *out = make(Map, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.SliceSlice != nil { + in, out := &in.SliceSlice, &out.SliceSlice + *out = make([]Slice, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make(Slice, len(*in)) + copy(*out, *in) + } + } + } + if in.MapSlice != nil { + in, out := &in.MapSlice, &out.MapSlice + *out = make(map[string]Slice, len(*in)) + for key, val := range *in { + var outVal []int + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(Slice, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + out.FooAlias = in.FooAlias + if in.FooSlice != nil { + in, out := &in.FooSlice, &out.FooSlice + *out = make(FooSlice, len(*in)) + copy(*out, *in) + } + if in.FooPointer != nil { + in, out := &in.FooPointer, &out.FooPointer + *out = new(Foo) + **out = **in + } + if in.FooMap != nil { + in, out := &in.FooMap, &out.FooMap + *out = make(FooMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.AliasSlice != nil { + in, out := &in.AliasSlice, &out.AliasSlice + *out = make(AliasSlice, len(*in)) + copy(*out, *in) + } + if in.AliasPointer != nil { + in, out := &in.AliasPointer, &out.AliasPointer + *out = new(int) + **out = **in + } + out.AliasStruct = in.AliasStruct + if in.AliasMap != nil { + in, out := &in.AliasMap, &out.AliasMap + *out = make(AliasMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.AliasInterface != nil { + out.AliasInterface = in.AliasInterface.DeepCopyAliasInterface() + } + if in.AliasAliasInterface != nil { + out.AliasAliasInterface = in.AliasAliasInterface.DeepCopyAliasAliasInterface() + } + if in.AliasInterfaceMap != nil { + in, out := &in.AliasInterfaceMap, &out.AliasInterfaceMap + *out = make(AliasInterfaceMap, len(*in)) + for key, val := range *in { + if val == nil { + (*out)[key] = nil + } else { + (*out)[key] = val.DeepCopyAliasInterface() + } + } + } + if in.AliasInterfaceSlice != nil { + in, out := &in.AliasInterfaceSlice, &out.AliasInterfaceSlice + *out = make(AliasInterfaceSlice, len(*in)) + for i := range *in { + if (*in)[i] != nil { + (*out)[i] = (*in)[i].DeepCopyAliasInterface() + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/builtins/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/builtins/doc.go new file mode 100644 index 0000000000..34097c84df --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/builtins/doc.go @@ -0,0 +1,35 @@ +/* +Copyright 2016 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. +*/ + +// +k8s:deepcopy-gen=package + +// This is a test package. +package builtins + +type Ttest struct { + Byte byte + // Int8 int8 // TODO: int8 becomes byte in SnippetWriter + Int16 int16 + Int32 int32 + Int64 int64 + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Float32 float32 + Float64 float64 + String string +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/builtins/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/builtins/zz_generated.deepcopy.go new file mode 100644 index 0000000000..afc71ffb07 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/builtins/zz_generated.deepcopy.go @@ -0,0 +1,38 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package builtins + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/generate.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/generate.go new file mode 100644 index 0000000000..9ae99ee9ba --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/generate.go @@ -0,0 +1,18 @@ +/* +Copyright 2024 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. +*/ + +//go:generate go run k8s.io/code-generator/cmd/deepcopy-gen --output-file zz_generated.deepcopy.go --go-header-file=../../../examples/hack/boilerplate.go.txt k8s.io/code-generator/cmd/deepcopy-gen/output_tests/... +package outputtests diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interface_fuzzer.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interface_fuzzer.go new file mode 100644 index 0000000000..d6f960f1dd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interface_fuzzer.go @@ -0,0 +1,131 @@ +/* +Copyright 2018 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 outputtests + +import ( + "sigs.k8s.io/randfill" + + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases" + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces" +) + +// interfaceFuzzers contains fuzzer that set all interface to nil because our +// JSON deepcopy does not work with it. +// TODO: test also interface deepcopy +var interfaceFuzzers = []interface{}{ + func(s *aliases.AliasAliasInterface, c randfill.Continue) { + if c.Bool() { + *s = nil + } else { + *s = &aliasAliasInterfaceInstance{X: c.Int()} + } + }, + func(s *aliases.AliasInterface, c randfill.Continue) { + if c.Bool() { + *s = nil + } else { + *s = &aliasAliasInterfaceInstance{X: c.Int()} + } + }, + func(s *aliases.Interface, c randfill.Continue) { + if c.Bool() { + *s = nil + } else { + *s = &aliasAliasInterfaceInstance{X: c.Int()} + } + }, + func(s *aliases.AliasInterfaceMap, c randfill.Continue) { + if c.Bool() { + *s = nil + } else { + *s = make(aliases.AliasInterfaceMap) + for i := 0; i < c.Intn(3); i++ { + if c.Bool() { + (*s)[c.String(0)] = nil + } else { + (*s)[c.String(0)] = &aliasAliasInterfaceInstance{X: c.Int()} + } + } + } + + }, + func(s *aliases.AliasInterfaceSlice, c randfill.Continue) { + if c.Bool() { + *s = nil + } else { + *s = make(aliases.AliasInterfaceSlice, 0) + for i := 0; i < c.Intn(3); i++ { + if c.Bool() { + *s = append(*s, nil) + } else { + *s = append(*s, &aliasAliasInterfaceInstance{X: c.Int()}) + } + } + } + }, + func(s *interfaces.Inner, c randfill.Continue) { + if c.Bool() { + *s = nil + } else { + *s = &interfacesInnerInstance{X: c.Float64()} + } + }, +} + +type aliasAliasInterfaceInstance struct { + X int +} + +func (i *aliasAliasInterfaceInstance) DeepCopyInterface() aliases.Interface { + if i == nil { + return nil + } + + return &aliasAliasInterfaceInstance{X: i.X} +} + +func (i *aliasAliasInterfaceInstance) DeepCopyAliasInterface() aliases.AliasInterface { + if i == nil { + return nil + } + + return &aliasAliasInterfaceInstance{X: i.X} +} + +func (i *aliasAliasInterfaceInstance) DeepCopyAliasAliasInterface() aliases.AliasAliasInterface { + if i == nil { + return nil + } + + return &aliasAliasInterfaceInstance{X: i.X} +} + +type interfacesInnerInstance struct { + X float64 +} + +func (i *interfacesInnerInstance) DeepCopyInner() interfaces.Inner { + if i == nil { + return nil + } + + return &interfacesInnerInstance{X: i.X} +} + +func (i *interfacesInnerInstance) Function() float64 { + return i.X +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces/doc.go new file mode 100644 index 0000000000..2d01f2e601 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces/doc.go @@ -0,0 +1,29 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:deepcopy-gen=package + +// This is a test package. +package interfaces + +type Inner interface { + Function() float64 + DeepCopyInner() Inner +} + +type Ttest struct { + I []Inner +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces/zz_generated.deepcopy.go new file mode 100644 index 0000000000..abaa51b163 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces/zz_generated.deepcopy.go @@ -0,0 +1,47 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package interfaces + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + if in.I != nil { + in, out := &in.I, &out.I + *out = make([]Inner, len(*in)) + for i := range *in { + if (*in)[i] != nil { + (*out)[i] = (*in)[i].DeepCopyInner() + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/maps/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/maps/doc.go new file mode 100644 index 0000000000..01871e44d4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/maps/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 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. +*/ + +// +k8s:deepcopy-gen=package + +// This is a test package. +package maps + +type Ttest struct { + Byte map[string]byte + // Int8 map[string]int8 //TODO: int8 becomes byte in SnippetWriter + Int16 map[string]int16 + Int32 map[string]int32 + Int64 map[string]int64 + Uint8 map[string]uint8 + Uint16 map[string]uint16 + Uint32 map[string]uint32 + Uint64 map[string]uint64 + Float32 map[string]float32 + Float64 map[string]float64 + String map[string]string + StringPtr map[string]*string + StringPtrPtr map[string]**string + Map map[string]map[string]string + MapPtr map[string]*map[string]string + Slice map[string][]string + SlicePtr map[string]*[]string + Struct map[string]Ttest + StructPtr map[string]*Ttest +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/maps/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/maps/zz_generated.deepcopy.go new file mode 100644 index 0000000000..77103245a7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/maps/zz_generated.deepcopy.go @@ -0,0 +1,243 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package maps + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + if in.Byte != nil { + in, out := &in.Byte, &out.Byte + *out = make(map[string]byte, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Int16 != nil { + in, out := &in.Int16, &out.Int16 + *out = make(map[string]int16, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Int32 != nil { + in, out := &in.Int32, &out.Int32 + *out = make(map[string]int32, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Int64 != nil { + in, out := &in.Int64, &out.Int64 + *out = make(map[string]int64, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Uint8 != nil { + in, out := &in.Uint8, &out.Uint8 + *out = make(map[string]byte, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Uint16 != nil { + in, out := &in.Uint16, &out.Uint16 + *out = make(map[string]uint16, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Uint32 != nil { + in, out := &in.Uint32, &out.Uint32 + *out = make(map[string]uint32, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Uint64 != nil { + in, out := &in.Uint64, &out.Uint64 + *out = make(map[string]uint64, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Float32 != nil { + in, out := &in.Float32, &out.Float32 + *out = make(map[string]float32, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Float64 != nil { + in, out := &in.Float64, &out.Float64 + *out = make(map[string]float64, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.String != nil { + in, out := &in.String, &out.String + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.StringPtr != nil { + in, out := &in.StringPtr, &out.StringPtr + *out = make(map[string]*string, len(*in)) + for key, val := range *in { + var outVal *string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(string) + **out = **in + } + (*out)[key] = outVal + } + } + if in.StringPtrPtr != nil { + in, out := &in.StringPtrPtr, &out.StringPtrPtr + *out = make(map[string]**string, len(*in)) + for key, val := range *in { + var outVal **string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(*string) + if **in != nil { + in, out := *in, *out + *out = new(string) + **out = **in + } + } + (*out)[key] = outVal + } + } + if in.Map != nil { + in, out := &in.Map, &out.Map + *out = make(map[string]map[string]string, len(*in)) + for key, val := range *in { + var outVal map[string]string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + (*out)[key] = outVal + } + } + if in.MapPtr != nil { + in, out := &in.MapPtr, &out.MapPtr + *out = make(map[string]*map[string]string, len(*in)) + for key, val := range *in { + var outVal *map[string]string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + (*out)[key] = outVal + } + } + if in.Slice != nil { + in, out := &in.Slice, &out.Slice + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + if in.SlicePtr != nil { + in, out := &in.SlicePtr, &out.SlicePtr + *out = make(map[string]*[]string, len(*in)) + for key, val := range *in { + var outVal *[]string + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + (*out)[key] = outVal + } + } + if in.Struct != nil { + in, out := &in.Struct, &out.Struct + *out = make(map[string]Ttest, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + if in.StructPtr != nil { + in, out := &in.StructPtr, &out.StructPtr + *out = make(map[string]*Ttest, len(*in)) + for key, val := range *in { + var outVal *Ttest + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(Ttest) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg/interfaces.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg/interfaces.go new file mode 100644 index 0000000000..980fab0131 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg/interfaces.go @@ -0,0 +1,25 @@ +/* +Copyright 2017 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 otherpkg + +type Object interface { + DeepCopyObject() Object +} + +type List interface { + DeepCopyList() List +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/output_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/output_test.go new file mode 100644 index 0000000000..957aee6135 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/output_test.go @@ -0,0 +1,173 @@ +/* +Copyright 2024 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 outputtests + +import ( + "fmt" + "reflect" + "testing" + + "sigs.k8s.io/randfill" + + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/aliases" + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/builtins" + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/interfaces" + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/maps" + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/pointer" + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/slices" + "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/structs" + "k8s.io/utils/dump" +) + +func TestWithValueFuzzer(t *testing.T) { + tests := []interface{}{ + aliases.Ttest{}, + builtins.Ttest{}, + interfaces.Ttest{}, + maps.Ttest{}, + pointer.Ttest{}, + slices.Ttest{}, + structs.Ttest{}, + } + + fuzzer := randfill.New() + fuzzer.NilChance(0.5) + fuzzer.NumElements(0, 2) + fuzzer.Funcs(interfaceFuzzers...) + + for _, test := range tests { + t.Run(fmt.Sprintf("%T", test), func(t *testing.T) { + N := 1000 + for i := 0; i < N; i++ { + original := reflect.New(reflect.TypeOf(test)).Interface() + + fuzzer.Fill(original) + + reflectCopy := ReflectDeepCopy(original) + + if !reflect.DeepEqual(original, reflectCopy) { + t.Errorf("original and reflectCopy are different:\n\n original = %s\n\n jsonCopy = %s", dump.Pretty(original), dump.Pretty(reflectCopy)) + } + + deepCopy := reflect.ValueOf(original).MethodByName("DeepCopy").Call(nil)[0].Interface() + + if !reflect.DeepEqual(original, deepCopy) { + t.Fatalf("original and deepCopy are different:\n\n original = %s\n\n deepCopy() = %s", dump.Pretty(original), dump.Pretty(deepCopy)) + } + + ValueFuzz(original) + + if !reflect.DeepEqual(reflectCopy, deepCopy) { + t.Fatalf("reflectCopy and deepCopy are different:\n\n origin = %s\n\n jsonCopy() = %s", dump.Pretty(original), dump.Pretty(deepCopy)) + } + } + }) + } +} + +func BenchmarkReflectDeepCopy(b *testing.B) { + fourtytwo := "fourtytwo" + fourtytwoPtr := &fourtytwo + var nilMap map[string]string + var nilSlice []string + mapPtr := &map[string]string{"0": "fourtytwo", "1": "fourtytwo"} + slicePtr := &[]string{"fourtytwo", "fourtytwo", "fourtytwo"} + structPtr := &pointer.Ttest{ + Builtin: &fourtytwo, + Ptr: &fourtytwoPtr, + } + + tests := []interface{}{ + maps.Ttest{ + Byte: map[string]byte{"0": 42, "1": 42, "3": 42}, + Int16: map[string]int16{"0": 42, "1": 42, "3": 42}, + Int32: map[string]int32{"0": 42, "1": 42, "3": 42}, + Int64: map[string]int64{"0": 42, "1": 42, "3": 42}, + Uint8: map[string]uint8{"0": 42, "1": 42, "3": 42}, + Uint16: map[string]uint16{"0": 42, "1": 42, "3": 42}, + Uint32: map[string]uint32{"0": 42, "1": 42, "3": 42}, + Uint64: map[string]uint64{"0": 42, "1": 42, "3": 42}, + Float32: map[string]float32{"0": 42.0, "1": 42.0, "3": 42.0}, + Float64: map[string]float64{"0": 42, "1": 42, "3": 42}, + String: map[string]string{"0": "fourtytwo", "1": "fourtytwo", "3": "fourtytwo"}, + StringPtr: map[string]*string{"0": &fourtytwo, "1": &fourtytwo, "3": &fourtytwo}, + StringPtrPtr: map[string]**string{"0": &fourtytwoPtr, "1": &fourtytwoPtr, "3": &fourtytwoPtr}, + Map: map[string]map[string]string{"0": nil, "1": {"a": fourtytwo, "b": fourtytwo}, "3": {}}, + MapPtr: map[string]*map[string]string{"0": nil, "1": {"a": fourtytwo, "b": fourtytwo}, "3": &nilMap}, + Slice: map[string][]string{"0": nil, "1": {"a", "b"}, "2": {}}, + SlicePtr: map[string]*[]string{"0": nil, "1": {"a", "b"}, "2": &nilSlice}, + Struct: map[string]maps.Ttest{"0": {}, "1": {Byte: map[string]byte{"0": 42, "1": 42, "3": 42}}}, + StructPtr: map[string]*maps.Ttest{"0": nil, "1": {}, "2": {Byte: map[string]byte{"0": 42, "1": 42, "3": 42}}}, + }, + slices.Ttest{ + Byte: []byte{42, 42, 42}, + Int16: []int16{42, 42, 42}, + Int32: []int32{42, 42, 42}, + Int64: []int64{42, 42, 42}, + Uint8: []uint8{42, 42, 42}, + Uint16: []uint16{42, 42, 42}, + Uint32: []uint32{42, 42, 42}, + Uint64: []uint64{42, 42, 42}, + Float32: []float32{42.0, 42.0, 42.0}, + Float64: []float64{42, 42, 42}, + String: []string{"fourtytwo", "fourtytwo", "fourtytwo"}, + StringPtr: []*string{&fourtytwo, &fourtytwo, &fourtytwo}, + StringPtrPtr: []**string{&fourtytwoPtr, &fourtytwoPtr, &fourtytwoPtr}, + Map: []map[string]string{nil, {"a": fourtytwo, "b": fourtytwo}, {}}, + MapPtr: []*map[string]string{nil, {"a": fourtytwo, "b": fourtytwo}, &nilMap}, + Slice: [][]string{nil, {"a", "b"}, {}}, + SlicePtr: []*[]string{nil, {"a", "b"}, &nilSlice}, + Struct: []slices.Ttest{{}, {Byte: []byte{42, 42, 42}}}, + StructPtr: []*slices.Ttest{nil, {}, {Byte: []byte{42, 42, 42}}}, + }, + pointer.Ttest{ + Builtin: &fourtytwo, + Ptr: &fourtytwoPtr, + Map: &map[string]string{"0": "fourtytwo", "1": "fourtytwo"}, + Slice: &[]string{"fourtytwo", "fourtytwo", "fourtytwo"}, + MapPtr: &mapPtr, + SlicePtr: &slicePtr, + Struct: &pointer.Ttest{ + Builtin: &fourtytwo, + Ptr: &fourtytwoPtr, + }, + StructPtr: &structPtr, + }, + } + + fuzzer := randfill.New() + fuzzer.NilChance(0.5) + fuzzer.NumElements(0, 2) + fuzzer.Funcs(interfaceFuzzers...) + + for _, test := range tests { + b.Run(fmt.Sprintf("%T", test), func(b *testing.B) { + for i := 0; i < b.N; i++ { + switch t := test.(type) { + case maps.Ttest: + t.DeepCopy() + case slices.Ttest: + t.DeepCopy() + case pointer.Ttest: + t.DeepCopy() + default: + b.Fatalf("missing type case in switch for %T", t) + } + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/pointer/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/pointer/doc.go new file mode 100644 index 0000000000..fd461101b1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/pointer/doc.go @@ -0,0 +1,31 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:deepcopy-gen=package + +// This is a test package. +package pointer + +type Ttest struct { + Builtin *string + Ptr **string + Map *map[string]string + Slice *[]string + MapPtr **map[string]string + SlicePtr **[]string + Struct *Ttest + StructPtr **Ttest +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/pointer/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/pointer/zz_generated.deepcopy.go new file mode 100644 index 0000000000..ceb9a52dcf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/pointer/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package pointer + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + if in.Builtin != nil { + in, out := &in.Builtin, &out.Builtin + *out = new(string) + **out = **in + } + if in.Ptr != nil { + in, out := &in.Ptr, &out.Ptr + *out = new(*string) + if **in != nil { + in, out := *in, *out + *out = new(string) + **out = **in + } + } + if in.Map != nil { + in, out := &in.Map, &out.Map + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + if in.Slice != nil { + in, out := &in.Slice, &out.Slice + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + if in.MapPtr != nil { + in, out := &in.MapPtr, &out.MapPtr + *out = new(*map[string]string) + if **in != nil { + in, out := *in, *out + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + } + if in.SlicePtr != nil { + in, out := &in.SlicePtr, &out.SlicePtr + *out = new(*[]string) + if **in != nil { + in, out := *in, *out + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } + if in.Struct != nil { + in, out := &in.Struct, &out.Struct + *out = new(Ttest) + (*in).DeepCopyInto(*out) + } + if in.StructPtr != nil { + in, out := &in.StructPtr, &out.StructPtr + *out = new(*Ttest) + if **in != nil { + in, out := *in, *out + *out = new(Ttest) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/reflect_deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/reflect_deepcopy.go new file mode 100644 index 0000000000..ffb3241af8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/reflect_deepcopy.go @@ -0,0 +1,81 @@ +/* +Copyright 2018 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 outputtests + +import ( + "fmt" + "reflect" +) + +// ReflectDeepCopy deep copies the object using reflection. +func ReflectDeepCopy(in interface{}) interface{} { + return reflectDeepCopy(reflect.ValueOf(in)).Interface() +} + +func reflectDeepCopy(src reflect.Value) reflect.Value { + switch src.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + if src.IsNil() { + return src + } + } + + switch src.Kind() { + case reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Uintptr: + panic(fmt.Sprintf("cannot deep copy kind: %s", src.Kind())) + case reflect.Array: + dst := reflect.New(src.Type()) + for i := 0; i < src.Len(); i++ { + dst.Elem().Index(i).Set(reflectDeepCopy(src.Index(i))) + } + return dst.Elem() + case reflect.Interface: + return reflectDeepCopy(src.Elem()) + case reflect.Map: + dst := reflect.MakeMap(src.Type()) + for _, k := range src.MapKeys() { + dst.SetMapIndex(k, reflectDeepCopy(src.MapIndex(k))) + } + return dst + case reflect.Ptr: + dst := reflect.New(src.Type().Elem()) + dst.Elem().Set(reflectDeepCopy(src.Elem())) + return dst + case reflect.Slice: + dst := reflect.MakeSlice(src.Type(), 0, src.Len()) + for i := 0; i < src.Len(); i++ { + dst = reflect.Append(dst, reflectDeepCopy(src.Index(i))) + } + return dst + case reflect.Struct: + dst := reflect.New(src.Type()) + for i := 0; i < src.NumField(); i++ { + if !dst.Elem().Field(i).CanSet() { + // Can't set private fields. At this point, the + // best we can do is a shallow copy. For + // example, time.Time is a value type with + // private members that can be shallow copied. + return src + } + dst.Elem().Field(i).Set(reflectDeepCopy(src.Field(i))) + } + return dst.Elem() + default: + // Value types like numbers, booleans, and strings. + return src + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/slices/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/slices/doc.go new file mode 100644 index 0000000000..183d73628c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/slices/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 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. +*/ + +// +k8s:deepcopy-gen=package + +// This is a test package. +package slices + +type Ttest struct { + Byte []byte + // Int8 []int8 //TODO: int8 becomes byte in SnippetWriter + Int16 []int16 + Int32 []int32 + Int64 []int64 + Uint8 []uint8 + Uint16 []uint16 + Uint32 []uint32 + Uint64 []uint64 + Float32 []float32 + Float64 []float64 + String []string + StringPtr []*string + StringPtrPtr []**string + Map []map[string]string + MapPtr []*map[string]string + Slice [][]string + SlicePtr []*[]string + Struct []Ttest + StructPtr []*Ttest +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/slices/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/slices/zz_generated.deepcopy.go new file mode 100644 index 0000000000..04d4276a9d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/slices/zz_generated.deepcopy.go @@ -0,0 +1,193 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package slices + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + if in.Byte != nil { + in, out := &in.Byte, &out.Byte + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.Int16 != nil { + in, out := &in.Int16, &out.Int16 + *out = make([]int16, len(*in)) + copy(*out, *in) + } + if in.Int32 != nil { + in, out := &in.Int32, &out.Int32 + *out = make([]int32, len(*in)) + copy(*out, *in) + } + if in.Int64 != nil { + in, out := &in.Int64, &out.Int64 + *out = make([]int64, len(*in)) + copy(*out, *in) + } + if in.Uint8 != nil { + in, out := &in.Uint8, &out.Uint8 + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.Uint16 != nil { + in, out := &in.Uint16, &out.Uint16 + *out = make([]uint16, len(*in)) + copy(*out, *in) + } + if in.Uint32 != nil { + in, out := &in.Uint32, &out.Uint32 + *out = make([]uint32, len(*in)) + copy(*out, *in) + } + if in.Uint64 != nil { + in, out := &in.Uint64, &out.Uint64 + *out = make([]uint64, len(*in)) + copy(*out, *in) + } + if in.Float32 != nil { + in, out := &in.Float32, &out.Float32 + *out = make([]float32, len(*in)) + copy(*out, *in) + } + if in.Float64 != nil { + in, out := &in.Float64, &out.Float64 + *out = make([]float64, len(*in)) + copy(*out, *in) + } + if in.String != nil { + in, out := &in.String, &out.String + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.StringPtr != nil { + in, out := &in.StringPtr, &out.StringPtr + *out = make([]*string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(string) + **out = **in + } + } + } + if in.StringPtrPtr != nil { + in, out := &in.StringPtrPtr, &out.StringPtrPtr + *out = make([]**string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(*string) + if **in != nil { + in, out := *in, *out + *out = new(string) + **out = **in + } + } + } + } + if in.Map != nil { + in, out := &in.Map, &out.Map + *out = make([]map[string]string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + } + if in.MapPtr != nil { + in, out := &in.MapPtr, &out.MapPtr + *out = make([]*map[string]string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + } + } + if in.Slice != nil { + in, out := &in.Slice, &out.Slice + *out = make([][]string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } + if in.SlicePtr != nil { + in, out := &in.SlicePtr, &out.SlicePtr + *out = make([]*[]string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } + } + if in.Struct != nil { + in, out := &in.Struct, &out.Struct + *out = make([]Ttest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.StructPtr != nil { + in, out := &in.StructPtr, &out.StructPtr + *out = make([]*Ttest, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Ttest) + (*in).DeepCopyInto(*out) + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/structs/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/structs/doc.go new file mode 100644 index 0000000000..7e40800b23 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/structs/doc.go @@ -0,0 +1,40 @@ +/* +Copyright 2016 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. +*/ + +// +k8s:deepcopy-gen=package + +// This is a test package. +package structs + +type Inner struct { + Byte byte + // Int8 int8 //TODO: int8 becomes byte in SnippetWriter + Int16 int16 + Int32 int32 + Int64 int64 + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Float32 float32 + Float64 float64 + String string +} + +type Ttest struct { + Inner1 Inner + Inner2 Inner +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/structs/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/structs/zz_generated.deepcopy.go new file mode 100644 index 0000000000..992f9b9c2d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/structs/zz_generated.deepcopy.go @@ -0,0 +1,56 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package structs + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Inner) DeepCopyInto(out *Inner) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Inner. +func (in *Inner) DeepCopy() *Inner { + if in == nil { + return nil + } + out := new(Inner) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + out.Inner1 = in.Inner1 + out.Inner2 = in.Inner2 + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/value_fuzzer.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/value_fuzzer.go new file mode 100644 index 0000000000..6104aa3cb1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/value_fuzzer.go @@ -0,0 +1,86 @@ +/* +Copyright 2018 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 outputtests + +import ( + "reflect" +) + +// ValueFuzz recursively changes all basic type values in an object. Any kind of references will not +// be touched, i.e. the addresses of slices, maps, pointers will stay unchanged. +func ValueFuzz(obj interface{}) { + valueFuzz(reflect.ValueOf(obj)) +} + +func valueFuzz(obj reflect.Value) { + switch obj.Kind() { + case reflect.Array: + for i := 0; i < obj.Len(); i++ { + valueFuzz(obj.Index(i)) + } + case reflect.Slice: + if obj.IsNil() { + // TODO: set non-nil value + } else { + for i := 0; i < obj.Len(); i++ { + valueFuzz(obj.Index(i)) + } + } + case reflect.Interface, reflect.Ptr: + if obj.IsNil() { + // TODO: set non-nil value + } else { + valueFuzz(obj.Elem()) + } + case reflect.Struct: + for i, n := 0, obj.NumField(); i < n; i++ { + valueFuzz(obj.Field(i)) + } + case reflect.Map: + if obj.IsNil() { + // TODO: set non-nil value + } else { + for _, k := range obj.MapKeys() { + // map values are not addressable. We need a copy. + v := obj.MapIndex(k) + copy := reflect.New(v.Type()) + copy.Elem().Set(v) + valueFuzz(copy.Elem()) + obj.SetMapIndex(k, copy.Elem()) + } + // TODO: set some new value + } + case reflect.Func: // ignore, we don't have function types in our API + default: + if !obj.CanSet() { + return + } + switch obj.Kind() { + case reflect.String: + obj.SetString(obj.String() + "x") + case reflect.Bool: + obj.SetBool(!obj.Bool()) + case reflect.Float32, reflect.Float64: + obj.SetFloat(obj.Float()*2.0 + 1.0) + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + obj.SetInt(obj.Int() + 1) + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + obj.SetUint(obj.Uint() + 1) + default: + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/a.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/a.go new file mode 100644 index 0000000000..fbebe86424 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/a.go @@ -0,0 +1,171 @@ +/* +Copyright 2016 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 wholepkg + +import "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg" + +// Trivial +type StructEmpty struct{} + +// Only primitives +type StructPrimitives struct { + BoolField bool + IntField int + StringField string + FloatField float64 +} +type StructPrimitivesAlias StructPrimitives +type StructEmbedStructPrimitives struct { + StructPrimitives +} +type StructEmbedInt struct { + int //nolint:unused +} +type StructStructPrimitives struct { + StructField StructPrimitives +} + +// Manual DeepCopy method +type ManualStruct struct { + StringField string +} + +func (m ManualStruct) DeepCopy() ManualStruct { + return m +} + +type ManualStructAlias ManualStruct + +type StructEmbedManualStruct struct { + ManualStruct +} + +// Only pointers to primitives +type StructPrimitivePointers struct { + BoolPtrField *bool + IntPtrField *int + StringPtrField *string + FloatPtrField *float64 +} +type StructPrimitivePointersAlias StructPrimitivePointers +type StructEmbedStructPrimitivePointers struct { + StructPrimitivePointers +} +type StructEmbedPointer struct { + *int +} +type StructStructPrimitivePointers struct { + StructField StructPrimitivePointers +} + +// Manual DeepCopy method +type ManualSlice []string + +func (m ManualSlice) DeepCopy() ManualSlice { + r := make(ManualSlice, len(m)) + copy(r, m) + return r +} + +// Slices +type StructSlices struct { + SliceBoolField []bool + SliceByteField []byte + SliceIntField []int + SliceStringField []string + SliceFloatField []float64 + SliceStructPrimitivesField []StructPrimitives + SliceStructPrimitivesAliasField []StructPrimitivesAlias + SliceStructPrimitivePointersField []StructPrimitivePointers + SliceStructPrimitivePointersAliasField []StructPrimitivePointersAlias + SliceSliceIntField [][]int + SliceManualStructField []ManualStruct + ManualSliceField ManualSlice +} +type StructSlicesAlias StructSlices +type StructEmbedStructSlices struct { + StructSlices +} +type StructStructSlices struct { + StructField StructSlices +} + +// Everything +type StructEverything struct { + BoolField bool + IntField int + StringField string + FloatField float64 + StructField StructPrimitives + EmptyStructField StructEmpty + ManualStructField ManualStruct + ManualStructAliasField ManualStructAlias + BoolPtrField *bool + IntPtrField *int + StringPtrField *string + FloatPtrField *float64 + PrimitivePointersField StructPrimitivePointers + ManualStructPtrField *ManualStruct + ManualStructAliasPtrField *ManualStructAlias + SliceBoolField []bool + SliceByteField []byte + SliceIntField []int + SliceStringField []string + SliceFloatField []float64 + SlicesField StructSlices + SliceManualStructField []ManualStruct + ManualSliceField ManualSlice +} + +// An Object +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg.Object +type StructExplicitObject struct { + x int //nolint:unused +} + +// An Object which is used a non-pointer +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg.Object +// +k8s:deepcopy-gen:nonpointer-interfaces=true +type StructNonPointerExplicitObject struct { + x int //nolint:unused +} + +// +k8s:deepcopy-gen=false +type StructTypeMeta struct { +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg.Object +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg.List +type StructObjectAndList struct { +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg.Object +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg.Object +type StructObjectAndObject struct { +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg.Selector +// +k8s:deepcopy-gen:interfaces=k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg.Object +type StructExplicitSelectorExplicitObject struct { + StructTypeMeta +} + +type StructInterfaces struct { + ObjectField otherpkg.Object + NilObjectField otherpkg.Object + SelectorField Selector +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/b.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/b.go new file mode 100644 index 0000000000..3eb8c01fc8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/b.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 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 wholepkg + +// Another type in another file. +type StructB struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/deepcopy_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/deepcopy_test.go new file mode 100644 index 0000000000..0f66a1d0d0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/deepcopy_test.go @@ -0,0 +1,145 @@ +/* +Copyright 2016 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 wholepkg + +import ( + "reflect" + "testing" + + "sigs.k8s.io/randfill" +) + +func TestDeepCopyPrimitives(t *testing.T) { + x := StructPrimitives{} + y := StructPrimitives{} + + if !reflect.DeepEqual(&x, &y) { + t.Errorf("objects should be equal to start, but are not") + } + + fuzzer := randfill.New() + fuzzer.Fill(&x) + fuzzer.Fill(&y) + + if reflect.DeepEqual(&x, &y) { + t.Errorf("objects should not be equal, but are") + } + + x.DeepCopyInto(&y) + if !reflect.DeepEqual(&x, &y) { + t.Errorf("objects should be equal, but are not") + } +} + +func TestDeepCopyInterfaceFields(t *testing.T) { + x := StructInterfaces{} + y := StructInterfaces{} + + if !reflect.DeepEqual(&x, &y) { + t.Errorf("objects should be equal to start, but are not") + } + + fuzzer := randfill.New() + + obj := StructExplicitObject{} + fuzzer.Fill(&obj) + x.ObjectField = &obj + + sel := StructExplicitSelectorExplicitObject{} + fuzzer.Fill(&sel) + x.SelectorField = &sel + + if reflect.DeepEqual(&x, &y) { + t.Errorf("objects should not be equal, but are") + } + + x.DeepCopyInto(&y) + if !reflect.DeepEqual(&x, &y) { + t.Errorf("objects should be equal, but are not") + } +} + +func TestNilCopy(t *testing.T) { + var x *StructB + y := x.DeepCopy() + if y != nil { + t.Errorf("Expected nil as deepcopy of nil, got %+v", y) + } +} + +func assertMethod(t *testing.T, typ reflect.Type, name string) { + if _, found := typ.MethodByName(name); !found { + t.Errorf("StructExplicitObject must have %v method", name) + } +} + +func assertNotMethod(t *testing.T, typ reflect.Type, name string) { + if _, found := typ.MethodByName(name); found { + t.Errorf("%v must not have %v method", typ, name) + } +} + +func TestInterfaceTypes(t *testing.T) { + explicitObject := reflect.TypeOf(&StructExplicitObject{}) + assertMethod(t, explicitObject, "DeepCopyObject") + + typeMeta := reflect.TypeOf(&StructTypeMeta{}) + assertNotMethod(t, typeMeta, "DeepCopy") + + objectAndList := reflect.TypeOf(&StructObjectAndList{}) + assertMethod(t, objectAndList, "DeepCopyObject") + assertMethod(t, objectAndList, "DeepCopyList") + + objectAndObject := reflect.TypeOf(&StructObjectAndObject{}) + assertMethod(t, objectAndObject, "DeepCopyObject") + + explicitSelectorExplicitObject := reflect.TypeOf(&StructExplicitSelectorExplicitObject{}) + assertMethod(t, explicitSelectorExplicitObject, "DeepCopySelector") + assertMethod(t, explicitSelectorExplicitObject, "DeepCopyObject") +} + +func TestInterfaceDeepCopy(t *testing.T) { + x := StructExplicitObject{} + + fuzzer := randfill.New() + fuzzer.Fill(&x) + + yObj := x.DeepCopyObject() + y, ok := yObj.(*StructExplicitObject) + if !ok { + t.Fatalf("epxected StructExplicitObject from StructExplicitObject.DeepCopyObject, got: %t", yObj) + } + if !reflect.DeepEqual(y, &x) { + t.Error("objects should be equal, but are not") + } +} + +func TestInterfaceNonPointerDeepCopy(t *testing.T) { + x := StructNonPointerExplicitObject{} + + fuzzer := randfill.New() + fuzzer.Fill(&x) + + yObj := x.DeepCopyObject() + y, ok := yObj.(StructNonPointerExplicitObject) + if !ok { + t.Fatalf("epxected StructNonPointerExplicitObject from StructNonPointerExplicitObject.DeepCopyObject, got: %t", yObj) + } + if !reflect.DeepEqual(y, x) { + t.Error("objects should be equal, but are not") + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/doc.go new file mode 100644 index 0000000000..10399986f8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 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. +*/ + +// +k8s:deepcopy-gen=package + +// This is a test package. +package wholepkg diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/interfaces.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/interfaces.go new file mode 100644 index 0000000000..e5b3c7de40 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/interfaces.go @@ -0,0 +1,21 @@ +/* +Copyright 2017 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 wholepkg + +type Selector interface { + DeepCopySelector() Selector +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/zz_generated.deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/zz_generated.deepcopy.go new file mode 100644 index 0000000000..0c7b4699e2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/deepcopy-gen/output_tests/wholepkg/zz_generated.deepcopy.go @@ -0,0 +1,761 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package wholepkg + +import ( + otherpkg "k8s.io/code-generator/cmd/deepcopy-gen/output_tests/otherpkg" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ManualSlice) DeepCopyInto(out *ManualSlice) { + { + in := &in + *out = in.DeepCopy() + return + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManualStruct) DeepCopyInto(out *ManualStruct) { + *out = in.DeepCopy() + return +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManualStructAlias) DeepCopyInto(out *ManualStructAlias) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManualStructAlias. +func (in *ManualStructAlias) DeepCopy() *ManualStructAlias { + if in == nil { + return nil + } + out := new(ManualStructAlias) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructB) DeepCopyInto(out *StructB) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructB. +func (in *StructB) DeepCopy() *StructB { + if in == nil { + return nil + } + out := new(StructB) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEmbedInt) DeepCopyInto(out *StructEmbedInt) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEmbedInt. +func (in *StructEmbedInt) DeepCopy() *StructEmbedInt { + if in == nil { + return nil + } + out := new(StructEmbedInt) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEmbedManualStruct) DeepCopyInto(out *StructEmbedManualStruct) { + *out = *in + out.ManualStruct = in.ManualStruct.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEmbedManualStruct. +func (in *StructEmbedManualStruct) DeepCopy() *StructEmbedManualStruct { + if in == nil { + return nil + } + out := new(StructEmbedManualStruct) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEmbedPointer) DeepCopyInto(out *StructEmbedPointer) { + *out = *in + if in.int != nil { + in, out := &in.int, &out.int + *out = new(int) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEmbedPointer. +func (in *StructEmbedPointer) DeepCopy() *StructEmbedPointer { + if in == nil { + return nil + } + out := new(StructEmbedPointer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEmbedStructPrimitivePointers) DeepCopyInto(out *StructEmbedStructPrimitivePointers) { + *out = *in + in.StructPrimitivePointers.DeepCopyInto(&out.StructPrimitivePointers) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEmbedStructPrimitivePointers. +func (in *StructEmbedStructPrimitivePointers) DeepCopy() *StructEmbedStructPrimitivePointers { + if in == nil { + return nil + } + out := new(StructEmbedStructPrimitivePointers) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEmbedStructPrimitives) DeepCopyInto(out *StructEmbedStructPrimitives) { + *out = *in + out.StructPrimitives = in.StructPrimitives + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEmbedStructPrimitives. +func (in *StructEmbedStructPrimitives) DeepCopy() *StructEmbedStructPrimitives { + if in == nil { + return nil + } + out := new(StructEmbedStructPrimitives) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEmbedStructSlices) DeepCopyInto(out *StructEmbedStructSlices) { + *out = *in + in.StructSlices.DeepCopyInto(&out.StructSlices) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEmbedStructSlices. +func (in *StructEmbedStructSlices) DeepCopy() *StructEmbedStructSlices { + if in == nil { + return nil + } + out := new(StructEmbedStructSlices) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEmpty) DeepCopyInto(out *StructEmpty) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEmpty. +func (in *StructEmpty) DeepCopy() *StructEmpty { + if in == nil { + return nil + } + out := new(StructEmpty) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEverything) DeepCopyInto(out *StructEverything) { + *out = *in + out.StructField = in.StructField + out.EmptyStructField = in.EmptyStructField + out.ManualStructField = in.ManualStructField.DeepCopy() + out.ManualStructAliasField = in.ManualStructAliasField + if in.BoolPtrField != nil { + in, out := &in.BoolPtrField, &out.BoolPtrField + *out = new(bool) + **out = **in + } + if in.IntPtrField != nil { + in, out := &in.IntPtrField, &out.IntPtrField + *out = new(int) + **out = **in + } + if in.StringPtrField != nil { + in, out := &in.StringPtrField, &out.StringPtrField + *out = new(string) + **out = **in + } + if in.FloatPtrField != nil { + in, out := &in.FloatPtrField, &out.FloatPtrField + *out = new(float64) + **out = **in + } + in.PrimitivePointersField.DeepCopyInto(&out.PrimitivePointersField) + if in.ManualStructPtrField != nil { + in, out := &in.ManualStructPtrField, &out.ManualStructPtrField + x := (*in).DeepCopy() + *out = &x + } + if in.ManualStructAliasPtrField != nil { + in, out := &in.ManualStructAliasPtrField, &out.ManualStructAliasPtrField + *out = new(ManualStructAlias) + **out = **in + } + if in.SliceBoolField != nil { + in, out := &in.SliceBoolField, &out.SliceBoolField + *out = make([]bool, len(*in)) + copy(*out, *in) + } + if in.SliceByteField != nil { + in, out := &in.SliceByteField, &out.SliceByteField + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.SliceIntField != nil { + in, out := &in.SliceIntField, &out.SliceIntField + *out = make([]int, len(*in)) + copy(*out, *in) + } + if in.SliceStringField != nil { + in, out := &in.SliceStringField, &out.SliceStringField + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SliceFloatField != nil { + in, out := &in.SliceFloatField, &out.SliceFloatField + *out = make([]float64, len(*in)) + copy(*out, *in) + } + in.SlicesField.DeepCopyInto(&out.SlicesField) + if in.SliceManualStructField != nil { + in, out := &in.SliceManualStructField, &out.SliceManualStructField + *out = make([]ManualStruct, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.ManualSliceField = in.ManualSliceField.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEverything. +func (in *StructEverything) DeepCopy() *StructEverything { + if in == nil { + return nil + } + out := new(StructEverything) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructExplicitObject) DeepCopyInto(out *StructExplicitObject) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructExplicitObject. +func (in *StructExplicitObject) DeepCopy() *StructExplicitObject { + if in == nil { + return nil + } + out := new(StructExplicitObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new otherpkg.Object. +func (in *StructExplicitObject) DeepCopyObject() otherpkg.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructExplicitSelectorExplicitObject) DeepCopyInto(out *StructExplicitSelectorExplicitObject) { + *out = *in + out.StructTypeMeta = in.StructTypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructExplicitSelectorExplicitObject. +func (in *StructExplicitSelectorExplicitObject) DeepCopy() *StructExplicitSelectorExplicitObject { + if in == nil { + return nil + } + out := new(StructExplicitSelectorExplicitObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new otherpkg.Object. +func (in *StructExplicitSelectorExplicitObject) DeepCopyObject() otherpkg.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopySelector is an autogenerated deepcopy function, copying the receiver, creating a new Selector. +func (in *StructExplicitSelectorExplicitObject) DeepCopySelector() Selector { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructInterfaces) DeepCopyInto(out *StructInterfaces) { + *out = *in + if in.ObjectField != nil { + out.ObjectField = in.ObjectField.DeepCopyObject() + } + if in.NilObjectField != nil { + out.NilObjectField = in.NilObjectField.DeepCopyObject() + } + if in.SelectorField != nil { + out.SelectorField = in.SelectorField.DeepCopySelector() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructInterfaces. +func (in *StructInterfaces) DeepCopy() *StructInterfaces { + if in == nil { + return nil + } + out := new(StructInterfaces) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructNonPointerExplicitObject) DeepCopyInto(out *StructNonPointerExplicitObject) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructNonPointerExplicitObject. +func (in *StructNonPointerExplicitObject) DeepCopy() *StructNonPointerExplicitObject { + if in == nil { + return nil + } + out := new(StructNonPointerExplicitObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new otherpkg.Object. +func (in StructNonPointerExplicitObject) DeepCopyObject() otherpkg.Object { + return *in.DeepCopy() +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructObjectAndList) DeepCopyInto(out *StructObjectAndList) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructObjectAndList. +func (in *StructObjectAndList) DeepCopy() *StructObjectAndList { + if in == nil { + return nil + } + out := new(StructObjectAndList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyList is an autogenerated deepcopy function, copying the receiver, creating a new otherpkg.List. +func (in *StructObjectAndList) DeepCopyList() otherpkg.List { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new otherpkg.Object. +func (in *StructObjectAndList) DeepCopyObject() otherpkg.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructObjectAndObject) DeepCopyInto(out *StructObjectAndObject) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructObjectAndObject. +func (in *StructObjectAndObject) DeepCopy() *StructObjectAndObject { + if in == nil { + return nil + } + out := new(StructObjectAndObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new otherpkg.Object. +func (in *StructObjectAndObject) DeepCopyObject() otherpkg.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructPrimitivePointers) DeepCopyInto(out *StructPrimitivePointers) { + *out = *in + if in.BoolPtrField != nil { + in, out := &in.BoolPtrField, &out.BoolPtrField + *out = new(bool) + **out = **in + } + if in.IntPtrField != nil { + in, out := &in.IntPtrField, &out.IntPtrField + *out = new(int) + **out = **in + } + if in.StringPtrField != nil { + in, out := &in.StringPtrField, &out.StringPtrField + *out = new(string) + **out = **in + } + if in.FloatPtrField != nil { + in, out := &in.FloatPtrField, &out.FloatPtrField + *out = new(float64) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructPrimitivePointers. +func (in *StructPrimitivePointers) DeepCopy() *StructPrimitivePointers { + if in == nil { + return nil + } + out := new(StructPrimitivePointers) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructPrimitivePointersAlias) DeepCopyInto(out *StructPrimitivePointersAlias) { + *out = *in + if in.BoolPtrField != nil { + in, out := &in.BoolPtrField, &out.BoolPtrField + *out = new(bool) + **out = **in + } + if in.IntPtrField != nil { + in, out := &in.IntPtrField, &out.IntPtrField + *out = new(int) + **out = **in + } + if in.StringPtrField != nil { + in, out := &in.StringPtrField, &out.StringPtrField + *out = new(string) + **out = **in + } + if in.FloatPtrField != nil { + in, out := &in.FloatPtrField, &out.FloatPtrField + *out = new(float64) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructPrimitivePointersAlias. +func (in *StructPrimitivePointersAlias) DeepCopy() *StructPrimitivePointersAlias { + if in == nil { + return nil + } + out := new(StructPrimitivePointersAlias) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructPrimitives) DeepCopyInto(out *StructPrimitives) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructPrimitives. +func (in *StructPrimitives) DeepCopy() *StructPrimitives { + if in == nil { + return nil + } + out := new(StructPrimitives) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructPrimitivesAlias) DeepCopyInto(out *StructPrimitivesAlias) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructPrimitivesAlias. +func (in *StructPrimitivesAlias) DeepCopy() *StructPrimitivesAlias { + if in == nil { + return nil + } + out := new(StructPrimitivesAlias) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructSlices) DeepCopyInto(out *StructSlices) { + *out = *in + if in.SliceBoolField != nil { + in, out := &in.SliceBoolField, &out.SliceBoolField + *out = make([]bool, len(*in)) + copy(*out, *in) + } + if in.SliceByteField != nil { + in, out := &in.SliceByteField, &out.SliceByteField + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.SliceIntField != nil { + in, out := &in.SliceIntField, &out.SliceIntField + *out = make([]int, len(*in)) + copy(*out, *in) + } + if in.SliceStringField != nil { + in, out := &in.SliceStringField, &out.SliceStringField + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SliceFloatField != nil { + in, out := &in.SliceFloatField, &out.SliceFloatField + *out = make([]float64, len(*in)) + copy(*out, *in) + } + if in.SliceStructPrimitivesField != nil { + in, out := &in.SliceStructPrimitivesField, &out.SliceStructPrimitivesField + *out = make([]StructPrimitives, len(*in)) + copy(*out, *in) + } + if in.SliceStructPrimitivesAliasField != nil { + in, out := &in.SliceStructPrimitivesAliasField, &out.SliceStructPrimitivesAliasField + *out = make([]StructPrimitivesAlias, len(*in)) + copy(*out, *in) + } + if in.SliceStructPrimitivePointersField != nil { + in, out := &in.SliceStructPrimitivePointersField, &out.SliceStructPrimitivePointersField + *out = make([]StructPrimitivePointers, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SliceStructPrimitivePointersAliasField != nil { + in, out := &in.SliceStructPrimitivePointersAliasField, &out.SliceStructPrimitivePointersAliasField + *out = make([]StructPrimitivePointersAlias, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SliceSliceIntField != nil { + in, out := &in.SliceSliceIntField, &out.SliceSliceIntField + *out = make([][]int, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make([]int, len(*in)) + copy(*out, *in) + } + } + } + if in.SliceManualStructField != nil { + in, out := &in.SliceManualStructField, &out.SliceManualStructField + *out = make([]ManualStruct, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.ManualSliceField = in.ManualSliceField.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructSlices. +func (in *StructSlices) DeepCopy() *StructSlices { + if in == nil { + return nil + } + out := new(StructSlices) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructSlicesAlias) DeepCopyInto(out *StructSlicesAlias) { + *out = *in + if in.SliceBoolField != nil { + in, out := &in.SliceBoolField, &out.SliceBoolField + *out = make([]bool, len(*in)) + copy(*out, *in) + } + if in.SliceByteField != nil { + in, out := &in.SliceByteField, &out.SliceByteField + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.SliceIntField != nil { + in, out := &in.SliceIntField, &out.SliceIntField + *out = make([]int, len(*in)) + copy(*out, *in) + } + if in.SliceStringField != nil { + in, out := &in.SliceStringField, &out.SliceStringField + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SliceFloatField != nil { + in, out := &in.SliceFloatField, &out.SliceFloatField + *out = make([]float64, len(*in)) + copy(*out, *in) + } + if in.SliceStructPrimitivesField != nil { + in, out := &in.SliceStructPrimitivesField, &out.SliceStructPrimitivesField + *out = make([]StructPrimitives, len(*in)) + copy(*out, *in) + } + if in.SliceStructPrimitivesAliasField != nil { + in, out := &in.SliceStructPrimitivesAliasField, &out.SliceStructPrimitivesAliasField + *out = make([]StructPrimitivesAlias, len(*in)) + copy(*out, *in) + } + if in.SliceStructPrimitivePointersField != nil { + in, out := &in.SliceStructPrimitivePointersField, &out.SliceStructPrimitivePointersField + *out = make([]StructPrimitivePointers, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SliceStructPrimitivePointersAliasField != nil { + in, out := &in.SliceStructPrimitivePointersAliasField, &out.SliceStructPrimitivePointersAliasField + *out = make([]StructPrimitivePointersAlias, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SliceSliceIntField != nil { + in, out := &in.SliceSliceIntField, &out.SliceSliceIntField + *out = make([][]int, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make([]int, len(*in)) + copy(*out, *in) + } + } + } + if in.SliceManualStructField != nil { + in, out := &in.SliceManualStructField, &out.SliceManualStructField + *out = make([]ManualStruct, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.ManualSliceField = in.ManualSliceField.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructSlicesAlias. +func (in *StructSlicesAlias) DeepCopy() *StructSlicesAlias { + if in == nil { + return nil + } + out := new(StructSlicesAlias) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructStructPrimitivePointers) DeepCopyInto(out *StructStructPrimitivePointers) { + *out = *in + in.StructField.DeepCopyInto(&out.StructField) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructStructPrimitivePointers. +func (in *StructStructPrimitivePointers) DeepCopy() *StructStructPrimitivePointers { + if in == nil { + return nil + } + out := new(StructStructPrimitivePointers) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructStructPrimitives) DeepCopyInto(out *StructStructPrimitives) { + *out = *in + out.StructField = in.StructField + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructStructPrimitives. +func (in *StructStructPrimitives) DeepCopy() *StructStructPrimitives { + if in == nil { + return nil + } + out := new(StructStructPrimitives) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructStructSlices) DeepCopyInto(out *StructStructSlices) { + *out = *in + in.StructField.DeepCopyInto(&out.StructField) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructStructSlices. +func (in *StructStructSlices) DeepCopy() *StructStructSlices { + if in == nil { + return nil + } + out := new(StructStructSlices) + in.DeepCopyInto(out) + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/args/args.go new file mode 100644 index 0000000000..1163a2bd28 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/args/args.go @@ -0,0 +1,71 @@ +/* +Copyright 2017 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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2" +) + +type Args struct { + OutputFile string + ExtraPeerDirs []string // Always consider these as last-ditch possibilities for conversions. + GoHeaderFile string + + // GeneratedBuildTag is the tag used to identify code generated by execution + // of this type. Each generator should use a different tag, and different + // groups of generators (external API that depends on Kube generations) should + // keep tags distinct as well. + GeneratedBuildTag string + + apidefinitions.LintArgs +} + +// New returns default arguments for the generator. +func New() *Args { + return &Args{ + GeneratedBuildTag: gengo.StdBuildTag, + } +} + +// AddFlags add the generator flags to the flag set. +func (args *Args) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&args.OutputFile, "output-file", "generated.defaults.go", + "the name of the file to be generated") + fs.StringSliceVar(&args.ExtraPeerDirs, "extra-peer-dirs", args.ExtraPeerDirs, + "Comma-separated list of import paths which are considered, after tag-specified peers, for conversions.") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + fs.StringVar(&args.GeneratedBuildTag, "build-tag", args.GeneratedBuildTag, "A Go build tag to use to identify files generated by this command. Should be unique.") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputFile) == 0 { + return fmt.Errorf("--output-file must be specified") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/generators/defaulter.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/generators/defaulter.go new file mode 100644 index 0000000000..488f88714e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/generators/defaulter.go @@ -0,0 +1,1294 @@ +/* +Copyright 2016 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 generators + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "path" + "reflect" + "regexp" + "strconv" + "strings" + + "k8s.io/code-generator/cmd/defaulter-gen/args" + "k8s.io/code-generator/pkg/apidefinitions" + genutil "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +var typeZeroValue = map[string]interface{}{ + "uint": 0., + "uint8": 0., + "uint16": 0., + "uint32": 0., + "uint64": 0., + "int": 0., + "int8": 0., + "int16": 0., + "int32": 0., + "int64": 0., + "byte": 0., + "float64": 0., + "float32": 0., + "bool": false, + "time.Time": "", + "string": "", + "integer": 0., + "number": 0., + "boolean": false, + "[]byte": "", // base64 encoded characters + "interface{}": interface{}(nil), +} + +// These are the comment tags that carry parameters for defaulter generation. +const tagName = "k8s:defaulter-gen" +const defaultTagName = "default" + +func extractDefaultTag(comments []string) ([]string, error) { + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{defaultTagName}, comments) + if err != nil { + return nil, err + } + return tags[defaultTagName], nil +} + +func extractTag(comments []string) ([]string, bool) { + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{tagName}, comments) + if err != nil { + klog.Fatalf("Error extracting %s tag: %v", tagName, err) + } + + values, found := tags[tagName] + if !found || len(values) == 0 { + return nil, false + } + + return values, true +} + +// defaulterMatchType returns the values to be defaulted for pkg, or false +// if defaulter-gen should not run. +func defaulterMatchType(pkg *types.Package, idOpts []apidefinitions.Option) ([]string, bool) { + info, err := apidefinitions.Identify(pkg, apidefinitions.Defaulter, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + return nil, false + } + return info.TypeFilters(), true +} + +func checkTag(comments []string, require ...string) (bool, error) { + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{tagName}, comments) + if err != nil { + return false, err + } + + if len(require) == 0 { + return len(tags[tagName]) == 1 && tags[tagName][0] == "", nil + } + return reflect.DeepEqual(tags[tagName], require), nil +} + +func defaultFnNamer() *namer.NameStrategy { + return &namer.NameStrategy{ + Prefix: "SetDefaults_", + Join: func(pre string, in []string, post string) string { + return pre + strings.Join(in, "_") + post + }, + } +} + +func objectDefaultFnNamer() *namer.NameStrategy { + return &namer.NameStrategy{ + Prefix: "SetObjectDefaults_", + Join: func(pre string, in []string, post string) string { + return pre + strings.Join(in, "_") + post + }, + } +} + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{ + "public": namer.NewPublicNamer(1), + "raw": namer.NewRawNamer("", nil), + "defaultfn": defaultFnNamer(), + "objectdefaultfn": objectDefaultFnNamer(), + } +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +// defaults holds the declared defaulting functions for a given type (all defaulting functions +// are expected to be func(1)) +type defaults struct { + // object is the defaulter function for a top level type (typically one with TypeMeta) that + // invokes all child defaulters. May be nil if the object defaulter has not yet been generated. + object *types.Type + // base is a defaulter function defined for a type SetDefaults_Pod which does not invoke all + // child defaults - the base defaulter alone is insufficient to default a type + base *types.Type + // additional is zero or more defaulter functions of the form SetDefaults_Pod_XXXX that can be + // included in the Object defaulter. + additional []*types.Type +} + +// All of the types in conversions map are of type "DeclarationOf" with +// the underlying type being "Func". +type defaulterFuncMap map[*types.Type]defaults + +// Returns all manually-defined defaulting functions in the package. +func getManualDefaultingFunctions(context *generator.Context, pkg *types.Package, manualMap defaulterFuncMap) { + buffer := &bytes.Buffer{} + sw := generator.NewSnippetWriter(buffer, context, "$", "$") + + for _, f := range pkg.Functions { + if f.Underlying == nil || f.Underlying.Kind != types.Func { + klog.Errorf("Malformed function: %#v", f) + continue + } + if f.Underlying.Signature == nil { + klog.Errorf("Function without signature: %#v", f) + continue + } + signature := f.Underlying.Signature + // Check whether the function is defaulting function. + // Note that all of them have signature: + // object: func SetObjectDefaults_inType(*inType) + // base: func SetDefaults_inType(*inType) + // additional: func SetDefaults_inType_Qualifier(*inType) + if signature.Receiver != nil { + continue + } + if len(signature.Parameters) != 1 { + continue + } + if len(signature.Results) != 0 { + continue + } + inType := signature.Parameters[0].Type + if inType.Kind != types.Pointer { + continue + } + // Check if this is the primary defaulter. + args := defaultingArgsFromType(inType.Elem) + sw.Do("$.inType|defaultfn$", args) + switch { + case f.Name.Name == buffer.String(): + key := inType.Elem + // We might scan the same package twice, and that's OK. + v, ok := manualMap[key] + if ok && v.base != nil && v.base.Name.Package != pkg.Path { + panic(fmt.Sprintf("duplicate static defaulter defined: %#v", key)) + } + v.base = f + manualMap[key] = v + klog.V(6).Infof("found base defaulter function for %s from %s", key.Name, f.Name) + // Is one of the additional defaulters - a top level defaulter on a type that is + // also invoked. + case strings.HasPrefix(f.Name.Name, buffer.String()+"_"): + key := inType.Elem + v, ok := manualMap[key] + if ok { + exists := false + for _, existing := range v.additional { + if existing.Name == f.Name { + exists = true + break + } + } + if exists { + continue + } + } + v.additional = append(v.additional, f) + manualMap[key] = v + klog.V(6).Infof("found additional defaulter function for %s from %s", key.Name, f.Name) + } + buffer.Reset() + sw.Do("$.inType|objectdefaultfn$", args) + if f.Name.Name == buffer.String() { + key := inType.Elem + // We might scan the same package twice, and that's OK. + v, ok := manualMap[key] + if ok && v.base != nil && v.base.Name.Package != pkg.Path { + panic(fmt.Sprintf("duplicate static defaulter defined: %#v", key)) + } + v.object = f + manualMap[key] = v + klog.V(6).Infof("found object defaulter function for %s from %s", key.Name, f.Name) + } + buffer.Reset() + } +} + +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, args.GeneratedBuildTag, gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + targetList := []generator.Target{} + + // Accumulate pre-existing default functions. + // TODO: This is too ad-hoc. We need a better way. + existingDefaulters := defaulterFuncMap{} + + buffer := &bytes.Buffer{} + sw := generator.NewSnippetWriter(buffer, context, "$", "$") + + // First load other "input" packages. We do this as a single call because + // it is MUCH faster. + inputPkgs := make([]string, 0, len(context.Inputs)) + pkgToInput := map[string]string{} + for _, i := range context.Inputs { + klog.V(5).Infof("considering pkg %q", i) + pkg := context.Universe[i] + + info, err := apidefinitions.Identify(pkg, apidefinitions.Defaulter, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + continue + } + + // +k8s:defaulter-gen-input may direct the generator at types in + // a different package than the one where defaulters will be emitted. + inputPath := info.ExternalTypes() + if inputPath != pkg.Path { + klog.V(5).Infof(" input pkg %v", inputPath) + inputPkgs = append(inputPkgs, inputPath) + pkgToInput[i] = inputPath + } else { + pkgToInput[i] = i + } + } + + // Make sure explicit peer-packages are added. + var peerPkgs []string + for _, pkg := range args.ExtraPeerDirs { + // In case someone specifies a peer as a path into vendor, convert + // it to its "real" package path. + if i := strings.Index(pkg, "/vendor/"); i != -1 { + pkg = pkg[i+len("/vendor/"):] + } + peerPkgs = append(peerPkgs, pkg) + } + if expanded, err := context.FindPackages(peerPkgs...); err != nil { + klog.Fatalf("cannot find peer packages: %v", err) + } else { + peerPkgs = expanded // now in fully canonical form + } + inputPkgs = append(inputPkgs, peerPkgs...) + + if len(inputPkgs) > 0 { + if _, err := context.LoadPackages(inputPkgs...); err != nil { + klog.Fatalf("cannot load packages: %v", err) + } + } + // update context.Order to the latest context.Universe + orderer := namer.Orderer{Namer: namer.NewPublicNamer(1)} + context.Order = orderer.OrderUniverse(context.Universe) + + for _, i := range context.Inputs { + pkg := context.Universe[i] + + // typesPkg is where the types that needs defaulter are defined. + // Sometimes it is different from pkg. For example, kubernetes core/v1 + // types are defined in k8s.io/api/core/v1, while the pkg which holds + // defaulter code is at k/k/pkg/api/v1. + typesPkg := pkg + + // Add defaulting functions. + getManualDefaultingFunctions(context, pkg, existingDefaulters) + + // Also look for defaulting functions in peer-packages. + for _, pp := range peerPkgs { + getManualDefaultingFunctions(context, context.Universe[pp], existingDefaulters) + } + + typesWith, found := defaulterMatchType(pkg, idOpts) + if !found { + klog.V(2).InfoS(" did not find required tag", "tag", tagName) + continue + } + shouldCreateObjectDefaulterFn := func(t *types.Type) bool { + if defaults, ok := existingDefaulters[t]; ok && defaults.object != nil { + // A default generator is defined + baseTypeName := "" + if defaults.base != nil { + baseTypeName = defaults.base.Name.String() + } + klog.V(5).Infof(" an object defaulter already exists as %s", baseTypeName) + return false + } + // opt-out + optOut, err := checkTag(t.SecondClosestCommentLines, "false") + if err != nil { + klog.Fatalf("Error extracting %s tags: %v", tagName, err) + } + if optOut { + return false + } + // opt-in + optIn, err := checkTag(t.SecondClosestCommentLines, "true") + if err != nil { + klog.Fatalf("Error extracting %s tags: %v", tagName, err) + } + if optIn { + return true + } + // For every k8s:defaulter-gen tag at the package level, interpret the value as a + // field name (like TypeMeta, ListMeta, ObjectMeta) and trigger defaulter generation + // for any type with any of the matching field names. Provides a more useful package + // level defaulting than global (because we only need defaulters on a subset of objects - + // usually those with TypeMeta). + if t.Kind == types.Struct && len(typesWith) > 0 { + for _, field := range t.Members { + for _, s := range typesWith { + if field.Name == s { + return true + } + } + } + } + return false + } + + // Find the right input pkg, which might not be this one. + inputPath := pkgToInput[i] + typesPkg = context.Universe[inputPath] + + newDefaulters := defaulterFuncMap{} + for _, t := range typesPkg.Types { + if !shouldCreateObjectDefaulterFn(t) { + continue + } + if namer.IsPrivateGoName(t.Name.Name) { + // We won't be able to convert to a private type. + klog.V(5).Infof(" found a type %v, but it is a private name", t) + continue + } + + // create a synthetic type we can use during generation + newDefaulters[t] = defaults{} + } + + // only generate defaulters for objects that actually have defined defaulters + // prevents empty defaulters from being registered + for { + promoted := 0 + for t, d := range newDefaulters { + if d.object != nil { + continue + } + if newCallTreeForType(existingDefaulters, newDefaulters).build(t, true) != nil { + args := defaultingArgsFromType(t) + sw.Do("$.inType|objectdefaultfn$", args) + newDefaulters[t] = defaults{ + object: &types.Type{ + Name: types.Name{ + Package: pkg.Path, + Name: buffer.String(), + }, + Kind: types.Func, + }, + } + buffer.Reset() + promoted++ + } + } + if promoted != 0 { + continue + } + + // prune any types that were not used + for t, d := range newDefaulters { + if d.object == nil { + klog.V(6).Infof("did not generate defaulter for %s because no child defaulters were registered", t.Name) + delete(newDefaulters, t) + } + } + break + } + + if len(newDefaulters) == 0 { + klog.V(5).Infof("no defaulters in package %s", pkg.Name) + if _, hasTag := extractTag(pkg.Comments); !hasTag { + continue + } + } + + targetList = append(targetList, + &generator.SimpleTarget{ + PkgName: path.Base(pkg.Path), + PkgPath: pkg.Path, + PkgDir: pkg.Dir, // output pkg is the same as the input + HeaderComment: boilerplate, + + FilterFunc: func(c *generator.Context, t *types.Type) bool { + return t.Name.Package == typesPkg.Path + }, + + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + return []generator.Generator{ + NewGenDefaulter(args.OutputFile, typesPkg.Path, pkg.Path, existingDefaulters, newDefaulters, peerPkgs), + } + }, + }) + } + return targetList +} + +// callTreeForType contains fields necessary to build a tree for types. +type callTreeForType struct { + existingDefaulters defaulterFuncMap + newDefaulters defaulterFuncMap + currentlyBuildingTypes map[*types.Type]bool +} + +func newCallTreeForType(existingDefaulters, newDefaulters defaulterFuncMap) *callTreeForType { + return &callTreeForType{ + existingDefaulters: existingDefaulters, + newDefaulters: newDefaulters, + currentlyBuildingTypes: make(map[*types.Type]bool), + } +} + +// resolveType follows pointers and aliases of `t` until reaching the first +// non-pointer type in `t's` herarchy +func resolveTypeAndDepth(t *types.Type) (*types.Type, int) { + var prev *types.Type + depth := 0 + for prev != t { + prev = t + if t.Kind == types.Alias { + t = t.Underlying + } else if t.Kind == types.Pointer { + t = t.Elem + depth += 1 + } + } + return t, depth +} + +// getPointerElementPath follows pointers and aliases to returns all +// pointer elements in the path from the given type, to its base value type. +// +// Example: +// +// type MyString string +// type MyStringPointer *MyString +// type MyStringPointerPointer *MyStringPointer +// type MyStringAlias MyStringPointer +// type MyStringAliasPointer *MyStringAlias +// type MyStringAliasDoublePointer **MyStringAlias +// +// t | defaultPointerElementPath(t) +// ---------------------------|---------------------------------------- +// MyString | [] +// MyStringPointer | [MyString] +// MyStringPointerPointer | [MyStringPointer, MyString] +// MyStringAlias | [MyStringPointer, MyString] +// MyStringAliasPointer | [MyStringAlias, MyStringPointer, MyString] +// MyStringAliasDoublePointer | [*MyStringAlias, MyStringAlias, MyStringPointer, MyString] +func getPointerElementPath(t *types.Type) []*types.Type { + var path []*types.Type + for t != nil { + switch t.Kind { + case types.Alias: + t = t.Underlying + case types.Pointer: + t = t.Elem + path = append(path, t) + default: + t = nil + } + } + return path +} + +// getNestedDefault returns the first default value when resolving alias types +func getNestedDefault(t *types.Type) (string, error) { + var prev *types.Type + for prev != t { + prev = t + defaultMap, err := extractDefaultTag(t.CommentLines) + if err != nil { + return "", err + } + if len(defaultMap) == 1 && defaultMap[0] != "" { + return defaultMap[0], nil + } + if t.Kind == types.Alias { + t = t.Underlying + } else if t.Kind == types.Pointer { + t = t.Elem + } + } + return "", nil +} + +var refRE = regexp.MustCompile(`^ref\((?P[^"]+)\)$`) +var refREIdentIndex = refRE.SubexpIndex("reference") + +// parseSymbolReference looks for strings that match one of the following: +// - ref(Ident) +// - ref(pkgpath.Ident) +// If the input string matches either of these, it will return the (optional) +// pkgpath, the Ident, and true. Otherwise it will return empty strings and +// false. +func parseSymbolReference(s, sourcePackage string) (types.Name, bool) { + matches := refRE.FindStringSubmatch(s) + if len(matches) < refREIdentIndex || matches[refREIdentIndex] == "" { + return types.Name{}, false + } + + contents := matches[refREIdentIndex] + name := types.ParseFullyQualifiedName(contents) + if len(name.Package) == 0 { + name.Package = sourcePackage + } + return name, true +} + +func populateDefaultValue(node *callNode, t *types.Type, tags string, commentLines []string, commentPackage string) *callNode { + defaultMap, err := extractDefaultTag(commentLines) + if err != nil { + klog.Fatalf("Error extracting default tag: %v", err) + } + + var defaultString string + if len(defaultMap) == 1 { + defaultString = defaultMap[0] + } else if len(defaultMap) > 1 { + klog.Fatalf("Found more than one default tag for %v", t.Kind) + } + + baseT, depth := resolveTypeAndDepth(t) + if depth > 0 && defaultString == "" { + defaultString, err = getNestedDefault(t) + if err != nil { + klog.Fatalf("Error extracting nested default tag: %v", err) + } + } + + if len(defaultString) == 0 { + return node + } + var symbolReference types.Name + var defaultValue interface{} + if id, ok := parseSymbolReference(defaultString, commentPackage); ok { + symbolReference = id + defaultString = "" + } else if err := json.Unmarshal([]byte(defaultString), &defaultValue); err != nil { + klog.Fatalf("Failed to unmarshal default: %v", err) + } + + if defaultValue != nil { + zero := typeZeroValue[t.String()] + if reflect.DeepEqual(defaultValue, zero) { + // If the default value annotation matches the default value for the type, + // do not generate any defaulting function + return node + } + } + + // callNodes are not automatically generated for primitive types. Generate one if the callNode does not exist + if node == nil { + node = &callNode{} + node.markerOnly = true + } + + node.defaultIsPrimitive = baseT.IsPrimitive() + node.defaultType = baseT + node.defaultTopLevelType = t + node.defaultValue.InlineConstant = defaultString + node.defaultValue.SymbolReference = symbolReference + return node +} + +// build creates a tree of paths to fields (based on how they would be accessed in Go - pointer, elem, +// slice, or key) and the functions that should be invoked on each field. An in-order traversal of the resulting tree +// can be used to generate a Go function that invokes each nested function on the appropriate type. The return +// value may be nil if there are no functions to call on type or the type is a primitive (Defaulters can only be +// invoked on structs today). When root is true this function will not use a newDefaulter. existingDefaulters should +// contain all defaulting functions by type defined in code - newDefaulters should contain all object defaulters +// that could be or will be generated. If newDefaulters has an entry for a type, but the 'object' field is nil, +// this function skips adding that defaulter - this allows us to avoid generating object defaulter functions for +// list types that call empty defaulters. +func (c *callTreeForType) build(t *types.Type, root bool) *callNode { + parent := &callNode{} + + if root { + // the root node is always a pointer + parent.elem = true + } + + defaults := c.existingDefaulters[t] + newDefaults, generated := c.newDefaulters[t] + switch { + case !root && generated && newDefaults.object != nil: + parent.call = append(parent.call, newDefaults.object) + // if we will be generating the defaulter, it by definition is a covering + // defaulter, so we halt recursion + klog.V(6).Infof("the defaulter %s will be generated as an object defaulter", t.Name) + return parent + + case defaults.object != nil: + // object defaulters are always covering + parent.call = append(parent.call, defaults.object) + return parent + + case defaults.base != nil: + parent.call = append(parent.call, defaults.base) + // if the base function indicates it "covers" (it already includes defaulters) + // we can halt recursion + isCovers, err := checkTag(defaults.base.CommentLines, "covers") + if err != nil { + klog.Fatalf("error extracting %s tag: %v", tagName, err) + } + if isCovers { + klog.V(6).Infof("the defaulter %s indicates it covers all sub generators", t.Name) + return parent + } + } + + // base has been added already, now add any additional defaulters defined for this object + parent.call = append(parent.call, defaults.additional...) + + // if the type already exists, don't build the tree for it and don't generate anything. + // This is used to avoid recursion for nested recursive types. + if c.currentlyBuildingTypes[t] { + return nil + } + // if type doesn't exist, mark it as existing + c.currentlyBuildingTypes[t] = true + + defer func() { + // The type will now acts as a parent, not a nested recursive type. + // We can now build the tree for it safely. + c.currentlyBuildingTypes[t] = false + }() + + switch t.Kind { + case types.Pointer: + if child := c.build(t.Elem, false); child != nil { + child.elem = true + parent.children = append(parent.children, *child) + } + case types.Slice, types.Array: + if child := c.build(t.Elem, false); child != nil { + child.index = true + if t.Elem.Kind == types.Pointer { + child.elem = true + } + parent.children = append(parent.children, *child) + } else if member := populateDefaultValue(nil, t.Elem, "", t.Elem.CommentLines, t.Elem.Name.Package); member != nil { + member.index = true + parent.children = append(parent.children, *member) + } + case types.Map: + if child := c.build(t.Elem, false); child != nil { + child.key = true + parent.children = append(parent.children, *child) + } else if member := populateDefaultValue(nil, t.Elem, "", t.Elem.CommentLines, t.Elem.Name.Package); member != nil { + member.key = true + parent.children = append(parent.children, *member) + } + + case types.Struct: + for _, field := range t.Members { + name := field.Name + if len(name) == 0 { + if field.Type.Kind == types.Pointer { + name = field.Type.Elem.Name.Name + } else { + name = field.Type.Name.Name + } + } + if child := c.build(field.Type, false); child != nil { + child.field = name + populateDefaultValue(child, field.Type, field.Tags, field.CommentLines, field.Type.Name.Package) + parent.children = append(parent.children, *child) + } else if member := populateDefaultValue(nil, field.Type, field.Tags, field.CommentLines, t.Name.Package); member != nil { + member.field = name + parent.children = append(parent.children, *member) + } + } + case types.Alias: + if child := c.build(t.Underlying, false); child != nil { + parent.children = append(parent.children, *child) + } + } + if len(parent.children) == 0 && len(parent.call) == 0 { + // klog.V(6).Infof("decided type %s needs no generation", t.Name) + return nil + } + return parent +} + +const ( + runtimePackagePath = "k8s.io/apimachinery/pkg/runtime" + conversionPackagePath = "k8s.io/apimachinery/pkg/conversion" +) + +// genDefaulter produces a file with a autogenerated conversions. +type genDefaulter struct { + generator.GoGenerator + typesPackage string + outputPackage string + peerPackages []string + newDefaulters defaulterFuncMap + existingDefaulters defaulterFuncMap + imports namer.ImportTracker + typesForInit []*types.Type +} + +func NewGenDefaulter(outputFilename, typesPackage, outputPackage string, existingDefaulters, newDefaulters defaulterFuncMap, peerPkgs []string) generator.Generator { + return &genDefaulter{ + GoGenerator: generator.GoGenerator{ + OutputFilename: outputFilename, + }, + typesPackage: typesPackage, + outputPackage: outputPackage, + peerPackages: peerPkgs, + newDefaulters: newDefaulters, + existingDefaulters: existingDefaulters, + imports: generator.NewImportTrackerForPackage(outputPackage), + typesForInit: make([]*types.Type, 0), + } +} + +func (g *genDefaulter) Namers(c *generator.Context) namer.NameSystems { + // Have the raw namer for this file track what it imports. + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *genDefaulter) isOtherPackage(pkg string) bool { + if pkg == g.outputPackage { + return false + } + if strings.HasSuffix(pkg, `"`+g.outputPackage+`"`) { + return false + } + return true +} + +func (g *genDefaulter) Filter(c *generator.Context, t *types.Type) bool { + defaults, ok := g.newDefaulters[t] + if !ok || defaults.object == nil { + return false + } + g.typesForInit = append(g.typesForInit, t) + return true +} + +func (g *genDefaulter) Imports(c *generator.Context) (imports []string) { + var importLines []string + for _, singleImport := range g.imports.ImportLines() { + if g.isOtherPackage(singleImport) { + importLines = append(importLines, singleImport) + } + } + return importLines +} + +func (g *genDefaulter) Init(c *generator.Context, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + scheme := c.Universe.Type(types.Name{Package: runtimePackagePath, Name: "Scheme"}) + schemePtr := &types.Type{ + Kind: types.Pointer, + Elem: scheme, + } + sw.Do("// RegisterDefaults adds defaulters functions to the given scheme.\n", nil) + sw.Do("// Public to allow building arbitrary schemes.\n", nil) + sw.Do("// All generated defaulters are covering - they call all nested defaulters.\n", nil) + sw.Do("func RegisterDefaults(scheme $.|raw$) error {\n", schemePtr) + for _, t := range g.typesForInit { + args := defaultingArgsFromType(t) + sw.Do("scheme.AddTypeDefaultingFunc(&$.inType|raw${}, func(obj interface{}) { $.inType|objectdefaultfn$(obj.(*$.inType|raw$)) })\n", args) + } + sw.Do("return nil\n", nil) + sw.Do("}\n\n", nil) + return sw.Error() +} + +func (g *genDefaulter) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + if _, ok := g.newDefaulters[t]; !ok { + return nil + } + + klog.V(5).Infof("generating for type %v", t) + + callTree := newCallTreeForType(g.existingDefaulters, g.newDefaulters).build(t, true) + if callTree == nil { + klog.V(5).Infof(" no defaulters defined") + return nil + } + i := 0 + callTree.VisitInOrder(func(ancestors []*callNode, current *callNode) { + if ref := ¤t.defaultValue.SymbolReference; len(ref.Name) > 0 { + // Ensure package for symbol is imported in output generation + g.imports.AddSymbol(*ref) + + // Rewrite the fully qualified name using the local package name + // from the imports + ref.Package = g.imports.LocalNameOf(ref.Package) + } + + if len(current.call) == 0 { + return + } + path := callPath(append(ancestors, current)) + klog.V(5).Infof(" %d: %s", i, path) + i++ + }) + + sw := generator.NewSnippetWriter(w, c, "$", "$") + g.generateDefaulter(c, t, callTree, sw) + return sw.Error() +} + +func defaultingArgsFromType(inType *types.Type) generator.Args { + return generator.Args{ + "inType": inType, + } +} + +func (g *genDefaulter) generateDefaulter(c *generator.Context, inType *types.Type, callTree *callNode, sw *generator.SnippetWriter) { + sw.Do("func $.inType|objectdefaultfn$(in *$.inType|raw$) {\n", defaultingArgsFromType(inType)) + callTree.WriteMethod(c, "in", 0, nil, sw) + sw.Do("}\n\n", nil) +} + +// callNode represents an entry in a tree of Go type accessors - the path from the root to a leaf represents +// how in Go code an access would be performed. For example, if a defaulting function exists on a container +// lifecycle hook, to invoke that defaulter correctly would require this Go code: +// +// for i := range pod.Spec.Containers { +// o := &pod.Spec.Containers[i] +// if o.LifecycleHook != nil { +// SetDefaults_LifecycleHook(o.LifecycleHook) +// } +// } +// +// That would be represented by a call tree like: +// +// callNode +// field: "Spec" +// children: +// - field: "Containers" +// children: +// - index: true +// children: +// - field: "LifecycleHook" +// elem: true +// call: +// - SetDefaults_LifecycleHook +// +// which we can traverse to build that Go struct (you must call the field Spec, then Containers, then range over +// that field, then check whether the LifecycleHook field is nil, before calling SetDefaults_LifecycleHook on +// the pointer to that field). +type callNode struct { + // field is the name of the Go member to access + field string + // key is true if this is a map and we must range over the key and values + key bool + // index is true if this is a slice and we must range over the slice values + index bool + // elem is true if the previous elements refer to a pointer (typically just field) + elem bool + + // call is all of the functions that must be invoked on this particular node, in order + call []*types.Type + // children is the child call nodes that must also be traversed + children []callNode + + // defaultValue is the defaultValue of a callNode struct + // Only primitive types and pointer types are eligible to have a default value + defaultValue defaultValue + + // defaultIsPrimitive is used to determine how to assign the default value. + // Primitive types will be directly assigned while complex types will use JSON unmarshalling + defaultIsPrimitive bool + + // markerOnly is true if the callNode exists solely to fill in a default value + markerOnly bool + + // defaultType is the transitive underlying/element type of the node. + // The provided default value literal or reference is expected to be + // convertible to this type. + // + // e.g: + // node type = *string -> defaultType = string + // node type = StringPointerAlias -> defaultType = string + // Only populated if defaultIsPrimitive is true + defaultType *types.Type + + // defaultTopLevelType is the final type the value should resolve to + // This is in constrast with default type, which resolves aliases and pointers. + defaultTopLevelType *types.Type +} + +type defaultValue struct { + // The value was written directly in the marker comment and + // has been parsed as JSON + InlineConstant string + // The name of the symbol relative to the parsed package path + // i.e. k8s.io/pkg.apis.v1.Foo if from another package or simply `Foo` + // if within the same package. + SymbolReference types.Name +} + +func (d defaultValue) IsEmpty() bool { + resolved := d.Resolved() + return resolved == "" +} + +func (d defaultValue) Resolved() string { + if len(d.InlineConstant) > 0 { + return d.InlineConstant + } + return d.SymbolReference.String() +} + +// CallNodeVisitorFunc is a function for visiting a call tree. ancestors is the list of all parents +// of this node to the root of the tree - will be empty at the root. +type CallNodeVisitorFunc func(ancestors []*callNode, node *callNode) + +func (n *callNode) VisitInOrder(fn CallNodeVisitorFunc) { + n.visitInOrder(nil, fn) +} + +func (n *callNode) visitInOrder(ancestors []*callNode, fn CallNodeVisitorFunc) { + fn(ancestors, n) + ancestors = append(ancestors, n) + for i := range n.children { + n.children[i].visitInOrder(ancestors, fn) + } +} + +var ( + indexVariables = "ijklmnop" + localVariables = "abcdefgh" +) + +// varsForDepth creates temporary variables guaranteed to be unique within lexical Go scopes +// of this depth in a function. It uses canonical Go loop variables for the first 7 levels +// and then resorts to uglier prefixes. +func varsForDepth(depth int) (index, local string) { + if depth > len(indexVariables) { + index = fmt.Sprintf("i%d", depth) + } else { + index = indexVariables[depth : depth+1] + } + if depth > len(localVariables) { + local = fmt.Sprintf("local%d", depth) + } else { + local = localVariables[depth : depth+1] + } + return +} + +// writeCalls generates a list of function calls based on the calls field for the provided variable +// name and pointer. +func (n *callNode) writeCalls(varName string, isVarPointer bool, sw *generator.SnippetWriter) { + accessor := varName + if !isVarPointer { + accessor = "&" + accessor + } + for _, fn := range n.call { + sw.Do("$.fn|raw$($.var$)\n", generator.Args{ + "fn": fn, + "var": accessor, + }) + } +} + +func getTypeZeroValue(t string) (interface{}, error) { + defaultZero, ok := typeZeroValue[t] + if !ok { + return nil, fmt.Errorf("cannot find zero value for type %v in typeZeroValue", t) + } + + // To generate the code for empty string, they must be quoted + if defaultZero == "" { + defaultZero = strconv.Quote(defaultZero.(string)) + } + return defaultZero, nil +} + +func (n *callNode) writeDefaulter(c *generator.Context, varName string, index string, isVarPointer bool, sw *generator.SnippetWriter) { + if n.defaultValue.IsEmpty() { + return + } + + jsonUnmarshalType := c.Universe.Type(types.Name{Package: "encoding/json", Name: "Unmarshal"}) + + args := generator.Args{ + "defaultValue": n.defaultValue.Resolved(), + "varName": varName, + "index": index, + "varTopType": n.defaultTopLevelType, + "jsonUnmarshal": jsonUnmarshalType, + } + + variablePlaceholder := "" + + if n.index { + // Defaulting for array + variablePlaceholder = "$.varName$[$.index$]" + } else if n.key { + // Defaulting for map + variablePlaceholder = "$.varName$[$.index$]" + mapDefaultVar := args["index"].(string) + "_default" + args["mapDefaultVar"] = mapDefaultVar + } else { + // Defaulting for primitive type + variablePlaceholder = "$.varName$" + } + + // defaultIsPrimitive is true if the type or underlying type (in an array/map) is primitive + // or is a pointer to a primitive type + // (Eg: int, map[string]*string, []int) + if n.defaultIsPrimitive { + // If the default value is a primitive when the assigned type is a pointer + // keep using the address-of operator on the primitive value until the types match + if pointerPath := getPointerElementPath(n.defaultTopLevelType); len(pointerPath) > 0 { + // If the destination is a pointer, the last element in + // defaultDepth is the element type of the bottommost pointer: + // the base type of our default value. + destElemType := pointerPath[len(pointerPath)-1] + pointerArgs := args.WithArgs(generator.Args{ + "varDepth": len(pointerPath), + "baseElemType": destElemType, + }) + + sw.Do(fmt.Sprintf("if %s == nil {\n", variablePlaceholder), pointerArgs) + if len(n.defaultValue.InlineConstant) > 0 { + // If default value is a literal then it can be assigned via var stmt + sw.Do("var ptrVar$.varDepth$ $.baseElemType|raw$ = $.defaultValue$\n", pointerArgs) + } else { + // If default value is not a literal then it may need to be casted + // to the base type of the destination pointer + sw.Do("ptrVar$.varDepth$ := $.baseElemType|raw$($.defaultValue$)\n", pointerArgs) + } + + for i := len(pointerPath); i >= 1; i-- { + dest := fmt.Sprintf("ptrVar%d", i-1) + assignment := ":=" + if i == 1 { + // Last assignment is into the storage destination + dest = variablePlaceholder + assignment = "=" + } + + sourceType := "*" + destElemType.String() + if i == len(pointerPath) { + // Initial value is not a pointer + sourceType = destElemType.String() + } + destElemType = pointerPath[i-1] + + // Cannot include `dest` into args since its value may be + // `variablePlaceholder` which is a template, not a value + elementArgs := pointerArgs.WithArgs(generator.Args{ + "assignment": assignment, + "source": fmt.Sprintf("ptrVar%d", i), + "destElemType": destElemType, + }) + + // Skip cast if type is exact match + if destElemType.String() == sourceType { + sw.Do(fmt.Sprintf("%v $.assignment$ &$.source$\n", dest), elementArgs) + } else { + sw.Do(fmt.Sprintf("%v $.assignment$ (*$.destElemType|raw$)(&$.source$)\n", dest), elementArgs) + } + } + } else { + // For primitive types, nil checks cannot be used and the zero value must be determined + defaultZero, err := getTypeZeroValue(n.defaultType.String()) + if err != nil { + klog.Error(err) + } + args["defaultZero"] = defaultZero + + sw.Do(fmt.Sprintf("if %s == $.defaultZero$ {\n", variablePlaceholder), args) + + if len(n.defaultValue.InlineConstant) > 0 { + sw.Do(fmt.Sprintf("%s = $.defaultValue$", variablePlaceholder), args) + } else { + sw.Do(fmt.Sprintf("%s = $.varTopType|raw$($.defaultValue$)", variablePlaceholder), args) + } + } + } else { + sw.Do(fmt.Sprintf("if %s == nil {\n", variablePlaceholder), args) + // Map values are not directly addressable and we need a temporary variable to do json unmarshalling + // This applies to maps with non-primitive values (eg: map[string]SubStruct) + if n.key { + sw.Do("$.mapDefaultVar$ := $.varName$[$.index$]\n", args) + sw.Do("if err := $.jsonUnmarshal|raw$([]byte(`$.defaultValue$`), &$.mapDefaultVar$); err != nil {\n", args) + } else { + variablePointer := variablePlaceholder + if !isVarPointer { + variablePointer = "&" + variablePointer + } + sw.Do(fmt.Sprintf("if err := $.jsonUnmarshal|raw$([]byte(`$.defaultValue$`), %s); err != nil {\n", variablePointer), args) + } + sw.Do("panic(err)\n", nil) + sw.Do("}\n", nil) + if n.key { + sw.Do("$.varName$[$.index$] = $.mapDefaultVar$\n", args) + } + } + sw.Do("}\n", nil) +} + +// WriteMethod performs an in-order traversal of the calltree, generating loops and if blocks as necessary +// to correctly turn the call tree into a method body that invokes all calls on all child nodes of the call tree. +// Depth is used to generate local variables at the proper depth. +func (n *callNode) WriteMethod(c *generator.Context, varName string, depth int, ancestors []*callNode, sw *generator.SnippetWriter) { + // if len(n.call) > 0 { + // sw.Do(fmt.Sprintf("// %s\n", callPath(append(ancestors, n)).String()), nil) + // } + + if len(n.field) > 0 { + varName = varName + "." + n.field + } + + index, local := varsForDepth(depth) + vars := generator.Args{ + "index": index, + "local": local, + "var": varName, + } + + isPointer := n.elem && !n.index + if isPointer && len(ancestors) > 0 { + sw.Do("if $.var$ != nil {\n", vars) + } + + switch { + case n.index: + sw.Do("for $.index$ := range $.var$ {\n", vars) + if !n.markerOnly { + if n.elem { + sw.Do("$.local$ := $.var$[$.index$]\n", vars) + } else { + sw.Do("$.local$ := &$.var$[$.index$]\n", vars) + } + } + + n.writeDefaulter(c, varName, index, isPointer, sw) + n.writeCalls(local, true, sw) + for i := range n.children { + n.children[i].WriteMethod(c, local, depth+1, append(ancestors, n), sw) + } + sw.Do("}\n", nil) + case n.key: + if !n.defaultValue.IsEmpty() { + // Map keys are typed and cannot share the same index variable as arrays and other maps + index = index + "_" + ancestors[len(ancestors)-1].field + vars["index"] = index + sw.Do("for $.index$ := range $.var$ {\n", vars) + n.writeDefaulter(c, varName, index, isPointer, sw) + sw.Do("}\n", nil) + } + default: + n.writeDefaulter(c, varName, index, isPointer, sw) + n.writeCalls(varName, isPointer, sw) + for i := range n.children { + n.children[i].WriteMethod(c, varName, depth, append(ancestors, n), sw) + } + } + + if isPointer && len(ancestors) > 0 { + sw.Do("}\n", nil) + } +} + +type callPath []*callNode + +// String prints a representation of a callPath that roughly approximates what a Go accessor +// would look like. Used for debugging only. +func (path callPath) String() string { + if len(path) == 0 { + return "" + } + var parts []string + for _, p := range path { + last := len(parts) - 1 + switch { + case p.elem: + if len(parts) > 0 { + parts[last] = "*" + parts[last] + } else { + parts = append(parts, "*") + } + case p.index: + if len(parts) > 0 { + parts[last] += "[i]" + } else { + parts = append(parts, "[i]") + } + case p.key: + if len(parts) > 0 { + parts[last] += "[key]" + } else { + parts = append(parts, "[key]") + } + default: + if len(p.field) > 0 { + parts = append(parts, p.field) + } else { + parts = append(parts, "") + } + } + } + var calls []string + for _, fn := range path[len(path)-1].call { + calls = append(calls, fn.Name.String()) + } + if len(calls) == 0 { + calls = append(calls, "") + } + + return strings.Join(parts, ".") + " calls " + strings.Join(calls, ", ") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/main.go new file mode 100644 index 0000000000..0250c1328f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/main.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 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. +*/ + +// defaulter-gen is a tool for auto-generating Defaulter functions. +// +// Given a list of input directories, it will scan for top level types +// and generate efficient defaulters for an entire object from the sum +// of the SetDefault_* methods contained in the object tree. +// +// Generation is governed by comment tags in the source. Any package may +// request defaulter generation by including one or more comment tags at +// the package comment level: +// +// // +k8s:defaulter-gen= +// +// which will create defaulters for any type that contains the provided +// field name (if the type has defaulters). Any type may request explicit +// defaulting by providing the comment tag: +// +// // +k8s:defaulter-gen=true|false +// +// An existing defaulter method (`SetDefaults_TYPE`) can provide the +// comment tag: +// +// // +k8s:defaulter-gen=covers +// +// to indicate that the defaulter does not or should not call any nested +// defaulters. +package main + +import ( + "flag" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/defaulter-gen/args" + "k8s.io/code-generator/cmd/defaulter-gen/generators" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + args := args.New() + + args.AddFlags(pflag.CommandLine) + flag.Set("logtostderr", "true") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return generators.GetTargets(context, args) + } + + // Run it. + if err := gengo.Execute( + generators.NameSystems(), + generators.DefaultNameSystem(), + myTargets, + args.GeneratedBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/doc.go new file mode 100644 index 0000000000..2b338eec93 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2018 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. +*/ + +// +k8s:defaulter-gen=covers + +// This is a test package. +package empty diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/type.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/type.go new file mode 100644 index 0000000000..707e6fa4a6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/type.go @@ -0,0 +1,29 @@ +/* +Copyright 2018 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 empty + +// Only test +type Ttest struct { + BoolField bool + IntField int + StringField string + FloatField float64 +} + +type TypeMeta struct { + Fortest bool +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/zz_generated.defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/zz_generated.defaults.go new file mode 100644 index 0000000000..4c6ab363bc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty/zz_generated.defaults.go @@ -0,0 +1,33 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package empty + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/generate.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/generate.go new file mode 100644 index 0000000000..486ddacf80 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/generate.go @@ -0,0 +1,27 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2024 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. +*/ + +// Ignore this file to prevent zz_generated for this package + +//go:generate go run k8s.io/code-generator/cmd/defaulter-gen --output-file zz_generated.defaults.go --go-header-file=../../../examples/hack/boilerplate.go.txt k8s.io/code-generator/cmd/defaulter-gen/output_tests/... +package outputtests + +import ( + // For go-generate + _ "k8s.io/code-generator/cmd/defaulter-gen/generators" +) diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/defaults.go new file mode 100644 index 0000000000..7e27d5412b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/defaults.go @@ -0,0 +1,32 @@ +/* +Copyright 2020 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 marker + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +//nolint:unused +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_DefaultedWithFunction(obj *DefaultedWithFunction) { + if obj.S1 == "" { + obj.S1 = "default_function" + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/doc.go new file mode 100644 index 0000000000..6db42e14e7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2020 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. +*/ + +// +k8s:defaulter-gen=TypeMeta + +// This is a test package. +package marker diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/constant.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/constant.go new file mode 100644 index 0000000000..d04b8e02bf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/constant.go @@ -0,0 +1,20 @@ +/* +Copyright 2024 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 external + +// Used for test with multiple packages of the same name +const AConstant string = "AConstantString" diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/external/constant.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/external/constant.go new file mode 100644 index 0000000000..494da10541 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/external/constant.go @@ -0,0 +1,20 @@ +/* +Copyright 2024 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 external + +// Used for test with multiple packages of the same name +const AnotherConstant string = "AnotherConstantString" diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external2/type.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external2/type.go new file mode 100644 index 0000000000..df0b94bcbd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external2/type.go @@ -0,0 +1,19 @@ +/* +Copyright 2024 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 external2 + +type String string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external3/constant.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external3/constant.go new file mode 100644 index 0000000000..a97f469841 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external3/constant.go @@ -0,0 +1,21 @@ +/* +Copyright 2024 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 external3 + +import "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external2" + +type StringPointer *external2.String diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/fake_deepcopy_conversion.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/fake_deepcopy_conversion.go new file mode 100644 index 0000000000..5ccc12b63e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/fake_deepcopy_conversion.go @@ -0,0 +1,107 @@ +/* +Copyright 2020 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 marker + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func (in *Defaulted) DeepCopy() *Defaulted { + if in == nil { + return nil + } + out := new(Defaulted) + in.DeepCopyInto(out) + return out +} + +func (in *Defaulted) DeepCopyInto(out *Defaulted) { + *out = *in +} + +func (in *Defaulted) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +func (in *DefaultedOmitempty) DeepCopy() *DefaultedOmitempty { + if in == nil { + return nil + } + out := new(DefaultedOmitempty) + in.DeepCopyInto(out) + return out +} + +func (in *DefaultedOmitempty) DeepCopyInto(out *DefaultedOmitempty) { + *out = *in +} + +func (in *DefaultedOmitempty) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +func (in *DefaultedWithFunction) DeepCopy() *DefaultedWithFunction { + if in == nil { + return nil + } + out := new(DefaultedWithFunction) + in.DeepCopyInto(out) + return out +} + +func (in *DefaultedWithFunction) DeepCopyInto(out *DefaultedWithFunction) { + *out = *in +} + +func (in *DefaultedWithFunction) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +func (in *Defaulted) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *DefaultedOmitempty) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *DefaultedWithFunction) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *DefaultedWithReference) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } + +func (in *DefaultedWithReference) DeepCopy() *DefaultedWithReference { + if in == nil { + return nil + } + out := new(DefaultedWithReference) + in.DeepCopyInto(out) + return out +} + +func (in *DefaultedWithReference) DeepCopyInto(out *DefaultedWithReference) { + *out = *in +} + +func (in *DefaultedWithReference) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/marker_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/marker_test.go new file mode 100644 index 0000000000..c86f1e1d49 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/marker_test.go @@ -0,0 +1,409 @@ +/* +Copyright 2020 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 marker + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + + "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external" + externalexternal "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/external" + "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external2" + "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external3" + "k8s.io/utils/ptr" +) + +var ( + defaultInt32 int32 = 32 + defaultInt64 int64 = 64 +) + +func Test_Marker(t *testing.T) { + testcases := []struct { + name string + in Defaulted + out Defaulted + }{ + { + name: "default", + in: Defaulted{}, + out: Defaulted{ + StringDefault: "bar", + StringEmptyDefault: "", + StringEmpty: "", + StringPointer: ptr.To("default"), + Int64: &defaultInt64, + Int32: &defaultInt32, + IntDefault: 1, + IntEmptyDefault: 0, + IntEmpty: 0, + FloatDefault: 0.5, + FloatEmptyDefault: 0.0, + FloatEmpty: 0.0, + List: []Item{ + ptr.To("foo"), + ptr.To("bar"), + }, + Sub: &SubStruct{ + S: "foo", + I: 5, + }, + OtherSub: SubStruct{ + S: "", + I: 1, + }, + StructList: []SubStruct{ + { + S: "foo1", + I: 1, + }, + { + S: "foo2", + I: 1, + }, + }, + PtrStructList: []*SubStruct{ + { + S: "foo1", + I: 1, + }, + { + S: "foo2", + I: 1, + }, + }, + StringList: []string{ + "foo", + }, + Map: map[string]Item{ + "foo": ptr.To("bar"), + }, + StructMap: map[string]SubStruct{ + "foo": { + S: "string", + I: 1, + }, + }, + PtrStructMap: map[string]*SubStruct{ + "foo": { + S: "string", + I: 1, + }, + }, + AliasPtr: ptr.To("banana"), + }, + }, + { + name: "values-omitempty", + in: Defaulted{ + StringDefault: "changed", + IntDefault: 5, + }, + out: Defaulted{ + StringDefault: "changed", + StringEmptyDefault: "", + StringEmpty: "", + StringPointer: ptr.To("default"), + Int64: &defaultInt64, + Int32: &defaultInt32, + IntDefault: 5, + IntEmptyDefault: 0, + IntEmpty: 0, + FloatDefault: 0.5, + FloatEmptyDefault: 0.0, + FloatEmpty: 0.0, + List: []Item{ + ptr.To("foo"), + ptr.To("bar"), + }, + Sub: &SubStruct{ + S: "foo", + I: 5, + }, + StructList: []SubStruct{ + { + S: "foo1", + I: 1, + }, + { + S: "foo2", + I: 1, + }, + }, + PtrStructList: []*SubStruct{ + { + S: "foo1", + I: 1, + }, + { + S: "foo2", + I: 1, + }, + }, + StringList: []string{ + "foo", + }, + OtherSub: SubStruct{ + S: "", + I: 1, + }, + Map: map[string]Item{ + "foo": ptr.To("bar"), + }, + StructMap: map[string]SubStruct{ + "foo": { + S: "string", + I: 1, + }, + }, + PtrStructMap: map[string]*SubStruct{ + "foo": { + S: "string", + I: 1, + }, + }, + AliasPtr: ptr.To("banana"), + }, + }, + { + name: "lists", + in: Defaulted{ + List: []Item{ + nil, + ptr.To("bar"), + }, + }, + out: Defaulted{ + StringDefault: "bar", + StringEmptyDefault: "", + StringEmpty: "", + StringPointer: ptr.To("default"), + Int64: &defaultInt64, + Int32: &defaultInt32, + IntDefault: 1, + IntEmptyDefault: 0, + IntEmpty: 0, + FloatDefault: 0.5, + FloatEmptyDefault: 0.0, + FloatEmpty: 0.0, + List: []Item{ + ptr.To("apple"), + ptr.To("bar"), + }, + Sub: &SubStruct{ + S: "foo", + I: 5, + }, + StructList: []SubStruct{ + { + S: "foo1", + I: 1, + }, + { + S: "foo2", + I: 1, + }, + }, + PtrStructList: []*SubStruct{ + { + S: "foo1", + I: 1, + }, + { + S: "foo2", + I: 1, + }, + }, + StringList: []string{ + "foo", + }, + OtherSub: SubStruct{ + S: "", + I: 1, + }, + Map: map[string]Item{ + "foo": ptr.To("bar"), + }, + StructMap: map[string]SubStruct{ + "foo": { + S: "string", + I: 1, + }, + }, + PtrStructMap: map[string]*SubStruct{ + "foo": { + S: "string", + I: 1, + }, + }, + AliasPtr: ptr.To("banana"), + }, + }, + { + name: "stringmap", + in: Defaulted{ + Map: map[string]Item{ + "foo": nil, + "bar": ptr.To("banana"), + }, + }, + out: Defaulted{ + StringDefault: "bar", + StringEmptyDefault: "", + StringEmpty: "", + StringPointer: ptr.To("default"), + Int64: &defaultInt64, + Int32: &defaultInt32, + IntDefault: 1, + IntEmptyDefault: 0, + IntEmpty: 0, + FloatDefault: 0.5, + FloatEmptyDefault: 0.0, + FloatEmpty: 0.0, + List: []Item{ + ptr.To("foo"), + ptr.To("bar"), + }, + Sub: &SubStruct{ + S: "foo", + I: 5, + }, + StructList: []SubStruct{ + { + S: "foo1", + I: 1, + }, + { + S: "foo2", + I: 1, + }, + }, + PtrStructList: []*SubStruct{ + { + S: "foo1", + I: 1, + }, + { + S: "foo2", + I: 1, + }, + }, + StringList: []string{ + "foo", + }, + OtherSub: SubStruct{ + S: "", + I: 1, + }, + Map: map[string]Item{ + "foo": ptr.To("apple"), + "bar": ptr.To("banana"), + }, + StructMap: map[string]SubStruct{ + "foo": { + S: "string", + I: 1, + }, + }, + PtrStructMap: map[string]*SubStruct{ + "foo": { + S: "string", + I: 1, + }, + }, + AliasPtr: ptr.To("banana"), + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + SetObjectDefaults_Defaulted(&tc.in) + if diff := cmp.Diff(tc.out, tc.in); len(diff) > 0 { + t.Errorf("Error: Expected and actual output are different \n %s\n", diff) + } + }) + } +} + +func Test_DefaultingFunction(t *testing.T) { + in := DefaultedWithFunction{} + SetObjectDefaults_DefaultedWithFunction(&in) + out := DefaultedWithFunction{ + S1: "default_function", + S2: "default_marker", + } + if diff := cmp.Diff(out, in); len(diff) > 0 { + t.Errorf("Error: Expected and actual output are different \n %s\n", diff) + } + +} + +func Test_DefaultingReference(t *testing.T) { + dv := DefaultedValueItem(SomeValue) + SomeDefault := SomeDefault + SomeValue := SomeValue + + ptrVar9 := string(SomeValue) + ptrVar8 := &ptrVar9 + ptrVar7 := (*B1)(&ptrVar8) + ptrVar6 := (*B2)(&ptrVar7) + ptrVar5 := &ptrVar6 + ptrVar4 := &ptrVar5 + ptrVar3 := &ptrVar4 + ptrVar2 := (*B3)(&ptrVar3) + ptrVar1 := &ptrVar2 + + var external2Str = external2.String(SomeValue) + + testcases := []struct { + name string + in DefaultedWithReference + out DefaultedWithReference + }{ + { + name: "default", + in: DefaultedWithReference{}, + out: DefaultedWithReference{ + AliasPointerInside: Item(&SomeDefault), + AliasOverride: Item(&SomeDefault), + AliasConvertDefaultPointer: &dv, + AliasPointerDefault: &dv, + PointerAliasDefault: Item(ptr.To("apple")), + AliasNonPointer: SomeValue, + AliasPointer: &SomeValue, + SymbolReference: SomeDefault, + SameNamePackageSymbolReference1: external.AConstant, + SameNamePackageSymbolReference2: externalexternal.AnotherConstant, + PointerConversion: (*B4)(&ptrVar1), + PointerConversionValue: (B4)(ptrVar1), + FullyQualifiedLocalSymbol: string(SomeValue), + ImportFromAliasCast: external3.StringPointer(&external2Str), + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + SetObjectDefaults_DefaultedWithReference(&tc.in) + if diff := cmp.Diff(tc.out, tc.in); len(diff) > 0 { + t.Errorf("Error: Expected and actual output are different \n %s\n", diff) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/type.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/type.go new file mode 100644 index 0000000000..0658ba217c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/type.go @@ -0,0 +1,265 @@ +/* +Copyright 2020 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 marker + +import ( + "k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty" + "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external3" +) + +type Defaulted struct { + empty.TypeMeta + + // +default="bar" + StringDefault string + + // Default is forced to empty string + // Specifying the default is a no-op + // +default="" + StringEmptyDefault string + + // Not specifying a default still defaults for non-omitempty + StringEmpty string + + // +default="default" + StringPointer *string + + // +default=64 + Int64 *int64 + + // +default=32 + Int32 *int32 + + // +default=1 + IntDefault int + + // +default=0 + IntEmptyDefault int + + // Default is forced to 0 + IntEmpty int + + // +default=0.5 + FloatDefault float64 + + // +default=0.0 + FloatEmptyDefault float64 + + FloatEmpty float64 + + // +default=["foo", "bar"] + List []Item + // +default={"s": "foo", "i": 5} + Sub *SubStruct + + //+default=[{"s": "foo1", "i": 1}, {"s": "foo2"}] + StructList []SubStruct + + //+default=[{"s": "foo1", "i": 1}, {"s": "foo2"}] + PtrStructList []*SubStruct + + //+default=["foo"] + StringList []string + + // Default is forced to empty struct + OtherSub SubStruct + + // +default={"foo": "bar"} + Map map[string]Item + + // +default={"foo": {"S": "string", "I": 1}} + StructMap map[string]SubStruct + + // +default={"foo": {"S": "string", "I": 1}} + PtrStructMap map[string]*SubStruct + + // A default specified here overrides the default for the Item type + // +default="banana" + AliasPtr Item +} + +type DefaultedOmitempty struct { + empty.TypeMeta `json:",omitempty"` + + // +default="bar" + StringDefault string `json:",omitempty"` + + // Default is forced to empty string + // Specifying the default is a no-op + // +default="" + StringEmptyDefault string `json:",omitempty"` + + // Not specifying a default still defaults for non-omitempty + StringEmpty string `json:",omitempty"` + + // +default="default" + StringPointer *string `json:",omitempty"` + + // +default=64 + Int64 *int64 `json:",omitempty"` + + // +default=32 + Int32 *int32 `json:",omitempty"` + + // +default=1 + IntDefault int `json:",omitempty"` + + // +default=0 + IntEmptyDefault int `json:",omitempty"` + + // Default is forced to 0 + IntEmpty int `json:",omitempty"` + + // +default=0.5 + FloatDefault float64 `json:",omitempty"` + + // +default=0.0 + FloatEmptyDefault float64 `json:",omitempty"` + + FloatEmpty float64 `json:",omitempty"` + + // +default=["foo", "bar"] + List []Item `json:",omitempty"` + // +default={"s": "foo", "i": 5} + Sub *SubStruct `json:",omitempty"` + + //+default=[{"s": "foo1", "i": 1}, {"s": "foo2"}] + StructList []SubStruct `json:",omitempty"` + + //+default=[{"s": "foo1", "i": 1}, {"s": "foo2"}] + PtrStructList []*SubStruct `json:",omitempty"` + + //+default=["foo"] + StringList []string `json:",omitempty"` + + // Default is forced to empty struct + OtherSub SubStruct `json:",omitempty"` + + // +default={"foo": "bar"} + Map map[string]Item `json:",omitempty"` + + // +default={"foo": {"S": "string", "I": 1}} + StructMap map[string]SubStruct `json:",omitempty"` + + // +default={"foo": {"S": "string", "I": 1}} + PtrStructMap map[string]*SubStruct `json:",omitempty"` + + // A default specified here overrides the default for the Item type + // +default="banana" + AliasPtr Item `json:",omitempty"` +} + +const SomeDefault = "ACoolConstant" + +// +default="apple" +type Item *string + +type ValueItem string + +// +default=ref(SomeValue) +type DefaultedValueItem ValueItem +type PointerValueItem *DefaultedValueItem + +type ItemDefaultWiped Item + +const SomeValue ValueItem = "Value" + +type SubStruct struct { + S string + // +default=1 + I int `json:"I,omitempty"` +} + +type DefaultedWithFunction struct { + empty.TypeMeta + // +default="default_marker" + S1 string `json:"S1,omitempty"` + // +default="default_marker" + S2 string `json:"S2,omitempty"` +} + +type DefaultedWithReference struct { + empty.TypeMeta + + // Shows that if we have an alias that is a pointer and have a default + // that is a value convertible to that pointer we can still use it + // +default=ref(SomeValue) + AliasConvertDefaultPointer PointerValueItem + + // Shows that default defined on a nested type is not respected through + // an alias + AliasWipedDefault ItemDefaultWiped + + // A default defined on a pointer-valued alias is respected + PointerAliasDefault Item + + // Can have alias that is a pointer to type of constant + // +default=ref(SomeDefault) + AliasPointerInside Item + + // Can override default specified on an alias + // +default=ref(SomeDefault) + AliasOverride Item + + // Type-level default is not respected unless a pointer + AliasNonPointerDefault DefaultedValueItem `json:",omitempty"` + + // Type-level default is not respected unless a pointer + AliasPointerDefault *DefaultedValueItem + + // Can have value typed alias + // +default=ref(SomeValue) + AliasNonPointer ValueItem `json:",omitempty"` + + // Can have a pointer to an alias whose default is a non-pointer value + // +default=ref(SomeValue) + AliasPointer *ValueItem `json:",omitempty"` + + // Basic ref usage example + // +default=ref(SomeDefault) + SymbolReference string `json:",omitempty"` + + // +default=ref(k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external.AConstant) + SameNamePackageSymbolReference1 string `json:",omitempty"` + + // +default=ref(k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/external.AnotherConstant) + SameNamePackageSymbolReference2 string `json:",omitempty"` + + // Should convert ValueItem -> string then up to B4 through addressOf and + // casting + // +default=ref(SomeValue) + PointerConversion *B4 + + // +default=ref(SomeValue) + PointerConversionValue B4 + + // +default=ref(k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker.SomeValue) + FullyQualifiedLocalSymbol string + + // Construction of external3.StringPointer requires importing external2 + // Test that generator can handle it + // +default=ref(SomeValue) + ImportFromAliasCast external3.StringPointer +} + +// Super complicated hierarchy of aliases which includes multiple pointers, +// and sibling types. +type B0 *string +type B1 B0 +type B2 *B1 +type B3 ****B2 +type B4 **B3 diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/zz_generated.defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/zz_generated.defaults.go new file mode 100644 index 0000000000..0add63d4cd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/zz_generated.defaults.go @@ -0,0 +1,325 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package marker + +import ( + json "encoding/json" + + runtime "k8s.io/apimachinery/pkg/runtime" + external "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external" + externalexternal "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external/external" + external2 "k8s.io/code-generator/cmd/defaulter-gen/output_tests/marker/external2" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&Defaulted{}, func(obj interface{}) { SetObjectDefaults_Defaulted(obj.(*Defaulted)) }) + scheme.AddTypeDefaultingFunc(&DefaultedOmitempty{}, func(obj interface{}) { SetObjectDefaults_DefaultedOmitempty(obj.(*DefaultedOmitempty)) }) + scheme.AddTypeDefaultingFunc(&DefaultedWithFunction{}, func(obj interface{}) { SetObjectDefaults_DefaultedWithFunction(obj.(*DefaultedWithFunction)) }) + scheme.AddTypeDefaultingFunc(&DefaultedWithReference{}, func(obj interface{}) { SetObjectDefaults_DefaultedWithReference(obj.(*DefaultedWithReference)) }) + return nil +} + +func SetObjectDefaults_Defaulted(in *Defaulted) { + if in.StringDefault == "" { + in.StringDefault = "bar" + } + if in.StringPointer == nil { + var ptrVar1 string = "default" + in.StringPointer = &ptrVar1 + } + if in.Int64 == nil { + var ptrVar1 int64 = 64 + in.Int64 = &ptrVar1 + } + if in.Int32 == nil { + var ptrVar1 int32 = 32 + in.Int32 = &ptrVar1 + } + if in.IntDefault == 0 { + in.IntDefault = 1 + } + if in.FloatDefault == 0 { + in.FloatDefault = 0.5 + } + if in.List == nil { + if err := json.Unmarshal([]byte(`["foo", "bar"]`), &in.List); err != nil { + panic(err) + } + } + for i := range in.List { + if in.List[i] == nil { + var ptrVar1 string = "apple" + in.List[i] = &ptrVar1 + } + } + if in.Sub == nil { + if err := json.Unmarshal([]byte(`{"s": "foo", "i": 5}`), &in.Sub); err != nil { + panic(err) + } + } + if in.Sub != nil { + if in.Sub.I == 0 { + in.Sub.I = 1 + } + } + if in.StructList == nil { + if err := json.Unmarshal([]byte(`[{"s": "foo1", "i": 1}, {"s": "foo2"}]`), &in.StructList); err != nil { + panic(err) + } + } + for i := range in.StructList { + a := &in.StructList[i] + if a.I == 0 { + a.I = 1 + } + } + if in.PtrStructList == nil { + if err := json.Unmarshal([]byte(`[{"s": "foo1", "i": 1}, {"s": "foo2"}]`), &in.PtrStructList); err != nil { + panic(err) + } + } + for i := range in.PtrStructList { + a := in.PtrStructList[i] + if a != nil { + if a.I == 0 { + a.I = 1 + } + } + } + if in.StringList == nil { + if err := json.Unmarshal([]byte(`["foo"]`), &in.StringList); err != nil { + panic(err) + } + } + if in.OtherSub.I == 0 { + in.OtherSub.I = 1 + } + if in.Map == nil { + if err := json.Unmarshal([]byte(`{"foo": "bar"}`), &in.Map); err != nil { + panic(err) + } + } + for i_Map := range in.Map { + if in.Map[i_Map] == nil { + var ptrVar1 string = "apple" + in.Map[i_Map] = &ptrVar1 + } + } + if in.StructMap == nil { + if err := json.Unmarshal([]byte(`{"foo": {"S": "string", "I": 1}}`), &in.StructMap); err != nil { + panic(err) + } + } + if in.PtrStructMap == nil { + if err := json.Unmarshal([]byte(`{"foo": {"S": "string", "I": 1}}`), &in.PtrStructMap); err != nil { + panic(err) + } + } + if in.AliasPtr == nil { + var ptrVar1 string = "banana" + in.AliasPtr = &ptrVar1 + } +} + +func SetObjectDefaults_DefaultedOmitempty(in *DefaultedOmitempty) { + if in.StringDefault == "" { + in.StringDefault = "bar" + } + if in.StringPointer == nil { + var ptrVar1 string = "default" + in.StringPointer = &ptrVar1 + } + if in.Int64 == nil { + var ptrVar1 int64 = 64 + in.Int64 = &ptrVar1 + } + if in.Int32 == nil { + var ptrVar1 int32 = 32 + in.Int32 = &ptrVar1 + } + if in.IntDefault == 0 { + in.IntDefault = 1 + } + if in.FloatDefault == 0 { + in.FloatDefault = 0.5 + } + if in.List == nil { + if err := json.Unmarshal([]byte(`["foo", "bar"]`), &in.List); err != nil { + panic(err) + } + } + for i := range in.List { + if in.List[i] == nil { + var ptrVar1 string = "apple" + in.List[i] = &ptrVar1 + } + } + if in.Sub == nil { + if err := json.Unmarshal([]byte(`{"s": "foo", "i": 5}`), &in.Sub); err != nil { + panic(err) + } + } + if in.Sub != nil { + if in.Sub.I == 0 { + in.Sub.I = 1 + } + } + if in.StructList == nil { + if err := json.Unmarshal([]byte(`[{"s": "foo1", "i": 1}, {"s": "foo2"}]`), &in.StructList); err != nil { + panic(err) + } + } + for i := range in.StructList { + a := &in.StructList[i] + if a.I == 0 { + a.I = 1 + } + } + if in.PtrStructList == nil { + if err := json.Unmarshal([]byte(`[{"s": "foo1", "i": 1}, {"s": "foo2"}]`), &in.PtrStructList); err != nil { + panic(err) + } + } + for i := range in.PtrStructList { + a := in.PtrStructList[i] + if a != nil { + if a.I == 0 { + a.I = 1 + } + } + } + if in.StringList == nil { + if err := json.Unmarshal([]byte(`["foo"]`), &in.StringList); err != nil { + panic(err) + } + } + if in.OtherSub.I == 0 { + in.OtherSub.I = 1 + } + if in.Map == nil { + if err := json.Unmarshal([]byte(`{"foo": "bar"}`), &in.Map); err != nil { + panic(err) + } + } + for i_Map := range in.Map { + if in.Map[i_Map] == nil { + var ptrVar1 string = "apple" + in.Map[i_Map] = &ptrVar1 + } + } + if in.StructMap == nil { + if err := json.Unmarshal([]byte(`{"foo": {"S": "string", "I": 1}}`), &in.StructMap); err != nil { + panic(err) + } + } + if in.PtrStructMap == nil { + if err := json.Unmarshal([]byte(`{"foo": {"S": "string", "I": 1}}`), &in.PtrStructMap); err != nil { + panic(err) + } + } + if in.AliasPtr == nil { + var ptrVar1 string = "banana" + in.AliasPtr = &ptrVar1 + } +} + +func SetObjectDefaults_DefaultedWithFunction(in *DefaultedWithFunction) { + SetDefaults_DefaultedWithFunction(in) + if in.S1 == "" { + in.S1 = "default_marker" + } + if in.S2 == "" { + in.S2 = "default_marker" + } +} + +func SetObjectDefaults_DefaultedWithReference(in *DefaultedWithReference) { + if in.AliasConvertDefaultPointer == nil { + ptrVar1 := DefaultedValueItem(SomeValue) + in.AliasConvertDefaultPointer = &ptrVar1 + } + if in.PointerAliasDefault == nil { + var ptrVar1 string = "apple" + in.PointerAliasDefault = &ptrVar1 + } + if in.AliasPointerInside == nil { + ptrVar1 := string(SomeDefault) + in.AliasPointerInside = &ptrVar1 + } + if in.AliasOverride == nil { + ptrVar1 := string(SomeDefault) + in.AliasOverride = &ptrVar1 + } + if in.AliasPointerDefault == nil { + ptrVar1 := DefaultedValueItem(SomeValue) + in.AliasPointerDefault = &ptrVar1 + } + if in.AliasNonPointer == "" { + in.AliasNonPointer = ValueItem(SomeValue) + } + if in.AliasPointer == nil { + ptrVar1 := ValueItem(SomeValue) + in.AliasPointer = &ptrVar1 + } + if in.SymbolReference == "" { + in.SymbolReference = string(SomeDefault) + } + if in.SameNamePackageSymbolReference1 == "" { + in.SameNamePackageSymbolReference1 = string(external.AConstant) + } + if in.SameNamePackageSymbolReference2 == "" { + in.SameNamePackageSymbolReference2 = string(externalexternal.AnotherConstant) + } + if in.PointerConversion == nil { + ptrVar9 := string(SomeValue) + ptrVar8 := &ptrVar9 + ptrVar7 := (*B1)(&ptrVar8) + ptrVar6 := (*B2)(&ptrVar7) + ptrVar5 := &ptrVar6 + ptrVar4 := &ptrVar5 + ptrVar3 := &ptrVar4 + ptrVar2 := (*B3)(&ptrVar3) + ptrVar1 := &ptrVar2 + in.PointerConversion = (*B4)(&ptrVar1) + } + if in.PointerConversionValue == nil { + ptrVar8 := string(SomeValue) + ptrVar7 := &ptrVar8 + ptrVar6 := (*B1)(&ptrVar7) + ptrVar5 := (*B2)(&ptrVar6) + ptrVar4 := &ptrVar5 + ptrVar3 := &ptrVar4 + ptrVar2 := &ptrVar3 + ptrVar1 := (*B3)(&ptrVar2) + in.PointerConversionValue = &ptrVar1 + } + if in.FullyQualifiedLocalSymbol == "" { + in.FullyQualifiedLocalSymbol = string(SomeValue) + } + if in.ImportFromAliasCast == nil { + ptrVar1 := external2.String(SomeValue) + in.ImportFromAliasCast = &ptrVar1 + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/defaults.go new file mode 100644 index 0000000000..e656f5037c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/defaults.go @@ -0,0 +1,33 @@ +/* +Copyright 2018 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 pointer + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +//nolint:unused +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_Tpointer(obj *Tpointer) { + if obj.BoolField == nil { + obj.BoolField = new(bool) + *obj.BoolField = true + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/doc.go new file mode 100644 index 0000000000..4e2e7d7e0f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2018 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. +*/ + +// +k8s:defaulter-gen=TypeMeta + +// This is a test package. +package pointer diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/fake_deepcopy_conversion.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/fake_deepcopy_conversion.go new file mode 100644 index 0000000000..c74e764bb8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/fake_deepcopy_conversion.go @@ -0,0 +1,82 @@ +/* +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 pointer + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tpointer) DeepCopyInto(out *Tpointer) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.BoolField != nil { + in, out := &in.BoolField, &out.BoolField + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tpointer. +func (in *Tpointer) DeepCopy() *Tpointer { + if in == nil { + return nil + } + out := new(Tpointer) + in.DeepCopyInto(out) + return out +} + +func (in *Tpointer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + out.TypeMeta = in.TypeMeta + in.NTP.DeepCopyInto(&out.NTP) + if in.Tp != nil { + in, out := &in.Tp, &out.Tp + *out = new(Tpointer) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} + +func (in *Ttest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +func (in *Tpointer) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *Ttest) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/type.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/type.go new file mode 100644 index 0000000000..5398a42aae --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/type.go @@ -0,0 +1,33 @@ +/* +Copyright 2018 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 pointer + +import ( + "k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty" +) + +type Tpointer struct { + empty.TypeMeta + BoolField *bool +} + +// Only test +type Ttest struct { + empty.TypeMeta + NTP Tpointer + Tp *Tpointer +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/zz_generated.defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/zz_generated.defaults.go new file mode 100644 index 0000000000..8bee913dbf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/pointer/zz_generated.defaults.go @@ -0,0 +1,46 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package pointer + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&Tpointer{}, func(obj interface{}) { SetObjectDefaults_Tpointer(obj.(*Tpointer)) }) + scheme.AddTypeDefaultingFunc(&Ttest{}, func(obj interface{}) { SetObjectDefaults_Ttest(obj.(*Ttest)) }) + return nil +} + +func SetObjectDefaults_Tpointer(in *Tpointer) { + SetDefaults_Tpointer(in) +} + +func SetObjectDefaults_Ttest(in *Ttest) { + SetObjectDefaults_Tpointer(&in.NTP) + if in.Tp != nil { + SetObjectDefaults_Tpointer(in.Tp) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/defaults.go new file mode 100644 index 0000000000..21a2ee25c3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/defaults.go @@ -0,0 +1,33 @@ +/* +Copyright 2018 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 slices + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +//nolint:unused +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_Ttest(obj *Ttest) { + if obj.BoolField == nil { + obj.BoolField = new(bool) + *obj.BoolField = true + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/doc.go new file mode 100644 index 0000000000..39e61fb390 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2018 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. +*/ + +// +k8s:defaulter-gen=TypeMeta + +// This is a test package. +package slices diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/fake_deepcopy_conversion.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/fake_deepcopy_conversion.go new file mode 100644 index 0000000000..ab07e3da7c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/fake_deepcopy_conversion.go @@ -0,0 +1,118 @@ +/* +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 slices + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ttest) DeepCopyInto(out *Ttest) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.BoolField != nil { + in, out := &in.BoolField, &out.BoolField + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ttest. +func (in *Ttest) DeepCopy() *Ttest { + if in == nil { + return nil + } + out := new(Ttest) + in.DeepCopyInto(out) + return out +} + +func (in *Ttest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TtestList) DeepCopyInto(out *TtestList) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Ttest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TtestList. +func (in *TtestList) DeepCopy() *TtestList { + if in == nil { + return nil + } + out := new(TtestList) + in.DeepCopyInto(out) + return out +} + +func (in *TtestList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TtestPointerList) DeepCopyInto(out *TtestPointerList) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*Ttest, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Ttest) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TtestPointerList. +func (in *TtestPointerList) DeepCopy() *TtestPointerList { + if in == nil { + return nil + } + out := new(TtestPointerList) + in.DeepCopyInto(out) + return out +} + +func (in *TtestPointerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +func (in *Ttest) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *TtestList) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *TtestPointerList) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/type.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/type.go new file mode 100644 index 0000000000..0b0a79f4f6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/type.go @@ -0,0 +1,37 @@ +/* +Copyright 2018 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 slices + +import ( + "k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty" +) + +// Only test +type Ttest struct { + empty.TypeMeta + BoolField *bool +} + +type TtestList struct { + empty.TypeMeta + Items []Ttest +} + +type TtestPointerList struct { + empty.TypeMeta + Items []*Ttest +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/zz_generated.defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/zz_generated.defaults.go new file mode 100644 index 0000000000..a7d6e2678f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/slices/zz_generated.defaults.go @@ -0,0 +1,56 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package slices + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&Ttest{}, func(obj interface{}) { SetObjectDefaults_Ttest(obj.(*Ttest)) }) + scheme.AddTypeDefaultingFunc(&TtestList{}, func(obj interface{}) { SetObjectDefaults_TtestList(obj.(*TtestList)) }) + scheme.AddTypeDefaultingFunc(&TtestPointerList{}, func(obj interface{}) { SetObjectDefaults_TtestPointerList(obj.(*TtestPointerList)) }) + return nil +} + +func SetObjectDefaults_Ttest(in *Ttest) { + SetDefaults_Ttest(in) +} + +func SetObjectDefaults_TtestList(in *TtestList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Ttest(a) + } +} + +func SetObjectDefaults_TtestPointerList(in *TtestPointerList) { + for i := range in.Items { + a := in.Items[i] + if a != nil { + SetObjectDefaults_Ttest(a) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/defaults.go new file mode 100644 index 0000000000..bcfdde1a32 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/defaults.go @@ -0,0 +1,33 @@ +/* +Copyright 2018 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 wholepkg + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +//nolint:unused +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_StructPrimitives(obj *StructPrimitives) { + if obj.BoolField == nil { + obj.BoolField = new(bool) + *obj.BoolField = true + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/doc.go new file mode 100644 index 0000000000..7e9a651f35 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2018 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. +*/ + +// +k8s:defaulter-gen=TypeMeta + +// This is a test package. +package wholepkg diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/fake_deepcopy_conversion.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/fake_deepcopy_conversion.go new file mode 100644 index 0000000000..e896bf7268 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/fake_deepcopy_conversion.go @@ -0,0 +1,315 @@ +/* +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 wholepkg + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructEverything) DeepCopyInto(out *StructEverything) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.BoolPtrField != nil { + in, out := &in.BoolPtrField, &out.BoolPtrField + *out = new(bool) + **out = **in + } + if in.IntPtrField != nil { + in, out := &in.IntPtrField, &out.IntPtrField + *out = new(int) + **out = **in + } + if in.StringPtrField != nil { + in, out := &in.StringPtrField, &out.StringPtrField + *out = new(string) + **out = **in + } + if in.FloatPtrField != nil { + in, out := &in.FloatPtrField, &out.FloatPtrField + *out = new(float64) + **out = **in + } + in.PointerStructField.DeepCopyInto(&out.PointerStructField) + if in.SliceBoolField != nil { + in, out := &in.SliceBoolField, &out.SliceBoolField + *out = make([]bool, len(*in)) + copy(*out, *in) + } + if in.SliceByteField != nil { + in, out := &in.SliceByteField, &out.SliceByteField + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.SliceIntField != nil { + in, out := &in.SliceIntField, &out.SliceIntField + *out = make([]int, len(*in)) + copy(*out, *in) + } + if in.SliceStringField != nil { + in, out := &in.SliceStringField, &out.SliceStringField + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SliceFloatField != nil { + in, out := &in.SliceFloatField, &out.SliceFloatField + *out = make([]float64, len(*in)) + copy(*out, *in) + } + in.SlicesStructField.DeepCopyInto(&out.SlicesStructField) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructEverything. +func (in *StructEverything) DeepCopy() *StructEverything { + if in == nil { + return nil + } + out := new(StructEverything) + in.DeepCopyInto(out) + return out +} + +func (in *StructEverything) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructPointer) DeepCopyInto(out *StructPointer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.PointerStructPrimitivesField.DeepCopyInto(&out.PointerStructPrimitivesField) + if in.PointerPointerStructPrimitivesField != nil { + in, out := &in.PointerPointerStructPrimitivesField, &out.PointerPointerStructPrimitivesField + *out = new(StructPrimitives) + (*in).DeepCopyInto(*out) + } + in.PointerStructPrimitivesAliasField.DeepCopyInto(&out.PointerStructPrimitivesAliasField) + in.PointerPointerStructPrimitivesAliasField.DeepCopyInto(&out.PointerPointerStructPrimitivesAliasField) + in.PointerStructStructPrimitives.DeepCopyInto(&out.PointerStructStructPrimitives) + if in.PointerPointerStructStructPrimitives != nil { + in, out := &in.PointerPointerStructStructPrimitives, &out.PointerPointerStructStructPrimitives + *out = new(StructStructPrimitives) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructPointer. +func (in *StructPointer) DeepCopy() *StructPointer { + if in == nil { + return nil + } + out := new(StructPointer) + in.DeepCopyInto(out) + return out +} + +func (in *StructPointer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructPrimitives) DeepCopyInto(out *StructPrimitives) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.BoolField != nil { + in, out := &in.BoolField, &out.BoolField + *out = new(bool) + **out = **in + } + if in.IntField != nil { + in, out := &in.IntField, &out.IntField + *out = new(int) + **out = **in + } + if in.StringField != nil { + in, out := &in.StringField, &out.StringField + *out = new(string) + **out = **in + } + if in.FloatField != nil { + in, out := &in.FloatField, &out.FloatField + *out = new(float64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructPrimitives. +func (in *StructPrimitives) DeepCopy() *StructPrimitives { + if in == nil { + return nil + } + out := new(StructPrimitives) + in.DeepCopyInto(out) + return out +} + +func (in *StructPrimitives) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructPrimitivesAlias) DeepCopyInto(out *StructPrimitivesAlias) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.BoolField != nil { + in, out := &in.BoolField, &out.BoolField + *out = new(bool) + **out = **in + } + if in.IntField != nil { + in, out := &in.IntField, &out.IntField + *out = new(int) + **out = **in + } + if in.StringField != nil { + in, out := &in.StringField, &out.StringField + *out = new(string) + **out = **in + } + if in.FloatField != nil { + in, out := &in.FloatField, &out.FloatField + *out = new(float64) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructPrimitivesAlias. +func (in *StructPrimitivesAlias) DeepCopy() *StructPrimitivesAlias { + if in == nil { + return nil + } + out := new(StructPrimitivesAlias) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructSlices) DeepCopyInto(out *StructSlices) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.SliceStructPrimitivesField != nil { + in, out := &in.SliceStructPrimitivesField, &out.SliceStructPrimitivesField + *out = make([]StructPrimitives, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SlicePointerStructPrimitivesField != nil { + in, out := &in.SlicePointerStructPrimitivesField, &out.SlicePointerStructPrimitivesField + *out = make([]*StructPrimitives, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(StructPrimitives) + (*in).DeepCopyInto(*out) + } + } + } + if in.SliceStructPrimitivesAliasField != nil { + in, out := &in.SliceStructPrimitivesAliasField, &out.SliceStructPrimitivesAliasField + *out = make([]StructPrimitivesAlias, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SlicePointerStructPrimitivesAliasField != nil { + in, out := &in.SlicePointerStructPrimitivesAliasField, &out.SlicePointerStructPrimitivesAliasField + *out = make([]*StructPrimitivesAlias, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(StructPrimitivesAlias) + (*in).DeepCopyInto(*out) + } + } + } + if in.SliceStructStructPrimitives != nil { + in, out := &in.SliceStructStructPrimitives, &out.SliceStructStructPrimitives + *out = make([]StructStructPrimitives, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SlicePointerStructStructPrimitives != nil { + in, out := &in.SlicePointerStructStructPrimitives, &out.SlicePointerStructStructPrimitives + *out = make([]*StructStructPrimitives, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(StructStructPrimitives) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructSlices. +func (in *StructSlices) DeepCopy() *StructSlices { + if in == nil { + return nil + } + out := new(StructSlices) + in.DeepCopyInto(out) + return out +} + +func (in *StructSlices) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StructStructPrimitives) DeepCopyInto(out *StructStructPrimitives) { + *out = *in + out.TypeMeta = in.TypeMeta + in.StructField.DeepCopyInto(&out.StructField) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StructStructPrimitives. +func (in *StructStructPrimitives) DeepCopy() *StructStructPrimitives { + if in == nil { + return nil + } + out := new(StructStructPrimitives) + in.DeepCopyInto(out) + return out +} + +func (in *StructStructPrimitives) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +func (in *StructEverything) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *StructPointer) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *StructPrimitives) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *StructSlices) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (in *StructStructPrimitives) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/type.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/type.go new file mode 100644 index 0000000000..80a83b7465 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/type.go @@ -0,0 +1,74 @@ +/* +Copyright 2018 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 wholepkg + +import ( + "k8s.io/code-generator/cmd/defaulter-gen/output_tests/empty" +) + +// Only primitives +type StructPrimitives struct { + empty.TypeMeta + BoolField *bool + IntField *int + StringField *string + FloatField *float64 +} +type StructPrimitivesAlias StructPrimitives + +type StructStructPrimitives struct { + empty.TypeMeta + StructField StructPrimitives +} + +// Pointer +type StructPointer struct { + empty.TypeMeta + PointerStructPrimitivesField StructPrimitives + PointerPointerStructPrimitivesField *StructPrimitives + PointerStructPrimitivesAliasField StructPrimitivesAlias + PointerPointerStructPrimitivesAliasField StructPrimitivesAlias + PointerStructStructPrimitives StructStructPrimitives + PointerPointerStructStructPrimitives *StructStructPrimitives +} + +// Slices +type StructSlices struct { + empty.TypeMeta + SliceStructPrimitivesField []StructPrimitives + SlicePointerStructPrimitivesField []*StructPrimitives + SliceStructPrimitivesAliasField []StructPrimitivesAlias + SlicePointerStructPrimitivesAliasField []*StructPrimitivesAlias + SliceStructStructPrimitives []StructStructPrimitives + SlicePointerStructStructPrimitives []*StructStructPrimitives +} + +// Everything +type StructEverything struct { + empty.TypeMeta + BoolPtrField *bool + IntPtrField *int + StringPtrField *string + FloatPtrField *float64 + PointerStructField StructPointer + SliceBoolField []bool + SliceByteField []byte + SliceIntField []int + SliceStringField []string + SliceFloatField []float64 + SlicesStructField StructSlices +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/zz_generated.defaults.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/zz_generated.defaults.go new file mode 100644 index 0000000000..26c7991bde --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/defaulter-gen/output_tests/wholepkg/zz_generated.defaults.go @@ -0,0 +1,85 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by defaulter-gen. DO NOT EDIT. + +package wholepkg + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&StructEverything{}, func(obj interface{}) { SetObjectDefaults_StructEverything(obj.(*StructEverything)) }) + scheme.AddTypeDefaultingFunc(&StructPointer{}, func(obj interface{}) { SetObjectDefaults_StructPointer(obj.(*StructPointer)) }) + scheme.AddTypeDefaultingFunc(&StructPrimitives{}, func(obj interface{}) { SetObjectDefaults_StructPrimitives(obj.(*StructPrimitives)) }) + scheme.AddTypeDefaultingFunc(&StructSlices{}, func(obj interface{}) { SetObjectDefaults_StructSlices(obj.(*StructSlices)) }) + scheme.AddTypeDefaultingFunc(&StructStructPrimitives{}, func(obj interface{}) { SetObjectDefaults_StructStructPrimitives(obj.(*StructStructPrimitives)) }) + return nil +} + +func SetObjectDefaults_StructEverything(in *StructEverything) { + SetObjectDefaults_StructPointer(&in.PointerStructField) + SetObjectDefaults_StructSlices(&in.SlicesStructField) +} + +func SetObjectDefaults_StructPointer(in *StructPointer) { + SetObjectDefaults_StructPrimitives(&in.PointerStructPrimitivesField) + if in.PointerPointerStructPrimitivesField != nil { + SetObjectDefaults_StructPrimitives(in.PointerPointerStructPrimitivesField) + } + SetObjectDefaults_StructStructPrimitives(&in.PointerStructStructPrimitives) + if in.PointerPointerStructStructPrimitives != nil { + SetObjectDefaults_StructStructPrimitives(in.PointerPointerStructStructPrimitives) + } +} + +func SetObjectDefaults_StructPrimitives(in *StructPrimitives) { + SetDefaults_StructPrimitives(in) +} + +func SetObjectDefaults_StructSlices(in *StructSlices) { + for i := range in.SliceStructPrimitivesField { + a := &in.SliceStructPrimitivesField[i] + SetObjectDefaults_StructPrimitives(a) + } + for i := range in.SlicePointerStructPrimitivesField { + a := in.SlicePointerStructPrimitivesField[i] + if a != nil { + SetObjectDefaults_StructPrimitives(a) + } + } + for i := range in.SliceStructStructPrimitives { + a := &in.SliceStructStructPrimitives[i] + SetObjectDefaults_StructStructPrimitives(a) + } + for i := range in.SlicePointerStructStructPrimitives { + a := in.SlicePointerStructStructPrimitives[i] + if a != nil { + SetObjectDefaults_StructStructPrimitives(a) + } + } +} + +func SetObjectDefaults_StructStructPrimitives(in *StructStructPrimitives) { + SetObjectDefaults_StructPrimitives(&in.StructField) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/.gitignore b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/.gitignore new file mode 100644 index 0000000000..0e9aa466bb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/.gitignore @@ -0,0 +1 @@ +go-to-protobuf diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/OWNERS b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/OWNERS new file mode 100644 index 0000000000..af7e2ec4c7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/OWNERS @@ -0,0 +1,6 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - smarterclayton +reviewers: + - smarterclayton diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/main.go new file mode 100644 index 0000000000..009973389b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/main.go @@ -0,0 +1,41 @@ +/* +Copyright 2015 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. +*/ + +// go-to-protobuf generates a Protobuf IDL from a Go struct, respecting any +// existing IDL tags on the Go struct. +package main + +import ( + goflag "flag" + + flag "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/go-to-protobuf/protobuf" + "k8s.io/klog/v2" +) + +var g = protobuf.New() + +func init() { + klog.InitFlags(nil) + g.BindFlags(flag.CommandLine) + goflag.Set("logtostderr", "true") + flag.CommandLine.AddGoFlagSet(goflag.CommandLine) +} + +func main() { + flag.Parse() + protobuf.Run(g) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd.go new file mode 100644 index 0000000000..067dfed0c1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd.go @@ -0,0 +1,465 @@ +/* +Copyright 2015 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. +*/ + +// go-to-protobuf generates a Protobuf IDL from a Go struct, respecting any +// existing IDL tags on the Go struct. +package protobuf + +import ( + "bytes" + "fmt" + "log" + "os/exec" + "path/filepath" + "sort" + "strings" + + flag "github.com/spf13/pflag" + + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/parser" + "k8s.io/gengo/v2/types" +) + +type Generator struct { + GoHeaderFile string + APIMachineryPackages string + Packages string + OutputDir string + ProtoImport []string + Conditional string + Clean bool + OnlyIDL bool + KeepGogoproto bool + DropGogoGo bool + SkipGeneratedRewrite bool + DropEmbeddedFields string +} + +func New() *Generator { + defaultSourceTree := "." + return &Generator{ + OutputDir: defaultSourceTree, + APIMachineryPackages: strings.Join([]string{ + `+k8s.io/apimachinery/pkg/util/intstr`, + `+k8s.io/apimachinery/pkg/api/resource`, + `+k8s.io/apimachinery/pkg/runtime/schema`, + `+k8s.io/apimachinery/pkg/runtime`, + `k8s.io/apimachinery/pkg/apis/meta/v1`, + `k8s.io/apimachinery/pkg/apis/meta/v1beta1`, + `k8s.io/apimachinery/pkg/apis/testapigroup/v1`, + }, ","), + Packages: "", + DropEmbeddedFields: "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta", + DropGogoGo: true, + } +} + +func (g *Generator) BindFlags(flag *flag.FlagSet) { + flag.StringVarP(&g.GoHeaderFile, "go-header-file", "h", "", "File containing boilerplate header text. The string YEAR will be replaced with the current 4-digit year.") + flag.StringVarP(&g.Packages, "packages", "p", g.Packages, "comma-separated list of directories to get input types from. Directories prefixed with '-' are not generated, directories prefixed with '+' only create types with explicit IDL instructions.") + flag.StringVar(&g.APIMachineryPackages, "apimachinery-packages", g.APIMachineryPackages, "comma-separated list of directories to get apimachinery input types from which are needed by any API. Directories prefixed with '-' are not generated, directories prefixed with '+' only create types with explicit IDL instructions.") + flag.StringVar(&g.OutputDir, "output-dir", g.OutputDir, "The base directory under which to generate results.") + flag.StringSliceVar(&g.ProtoImport, "proto-import", g.ProtoImport, "A search path for imported protobufs (may be repeated).") + flag.StringVar(&g.Conditional, "conditional", g.Conditional, "An optional Golang build tag condition to add to the generated Go code") + flag.BoolVar(&g.Clean, "clean", g.Clean, "If true, remove all generated files for the specified Packages.") + flag.BoolVar(&g.OnlyIDL, "only-idl", g.OnlyIDL, "If true, only generate the IDL for each package.") + flag.BoolVar(&g.KeepGogoproto, "keep-gogoproto", g.KeepGogoproto, "If true, the generated IDL will contain gogoprotobuf extensions which are normally removed") + flag.BoolVar(&g.SkipGeneratedRewrite, "skip-generated-rewrite", g.SkipGeneratedRewrite, "If true, skip fixing up the generated.pb.go file (debugging only).") + flag.BoolVar(&g.DropGogoGo, "drop-gogo-go", g.DropGogoGo, "Drop all references to gogo packages in generated code") + flag.StringVar(&g.DropEmbeddedFields, "drop-embedded-fields", g.DropEmbeddedFields, "Comma-delimited list of embedded Go types to omit from generated protobufs") +} + +// This roughly models gengo/v2.Execute. +func Run(g *Generator) { + // Roughly models gengo/v2.newBuilder. + + p := parser.NewWithOptions(parser.Options{BuildTags: []string{"proto"}}) + + var allInputs []string + if len(g.APIMachineryPackages) != 0 { + allInputs = append(allInputs, strings.Split(g.APIMachineryPackages, ",")...) + } + if len(g.Packages) != 0 { + allInputs = append(allInputs, strings.Split(g.Packages, ",")...) + } + if len(allInputs) == 0 { + log.Fatalf("Both apimachinery-packages and packages are empty. At least one package must be specified.") + } + + if g.DropGogoGo && g.SkipGeneratedRewrite { + log.Fatalf("--drop-gogo-go=true and --skip-generated-rewrite=true are mutually exclusive") + } + + // Build up a list of packages to load from all the inputs. Track the + // special modifiers for each. NOTE: This does not support pkg/... syntax. + type modifier struct { + allTypes bool + output bool + name string + } + inputModifiers := map[string]modifier{} + packages := make([]string, 0, len(allInputs)) + + for _, d := range allInputs { + modifier := modifier{allTypes: true, output: true} + + switch { + case strings.HasPrefix(d, "+"): + d = d[1:] + modifier.allTypes = false + case strings.HasPrefix(d, "-"): + d = d[1:] + modifier.output = false + } + name := protoSafePackage(d) + parts := strings.SplitN(d, "=", 2) + if len(parts) > 1 { + d = parts[0] + name = parts[1] + } + modifier.name = name + + packages = append(packages, d) + inputModifiers[d] = modifier + } + + // Load all the packages at once. + if err := p.LoadPackages(packages...); err != nil { + log.Fatalf("Unable to load packages: %v", err) + } + + c, err := generator.NewContext( + p, + namer.NameSystems{ + "public": namer.NewPublicNamer(3), + }, + "public", + ) + if err != nil { + log.Fatalf("Failed making a context: %v", err) + } + + c.FileTypes["protoidl"] = NewProtoFile() + + // Roughly models gengo/v2.Execute calling the + // tool-provided Targets() callback. + + boilerplate, err := gengo.GoBoilerplate(g.GoHeaderFile, "", "") + if err != nil { + log.Fatalf("Failed loading boilerplate (consider using the go-header-file flag): %v", err) + } + + omitTypes := map[types.Name]struct{}{} + for _, t := range strings.Split(g.DropEmbeddedFields, ",") { + name := types.Name{} + if i := strings.LastIndex(t, "."); i != -1 { + name.Package, name.Name = t[:i], t[i+1:] + } else { + name.Name = t + } + if len(name.Name) == 0 { + log.Fatalf("--drop-embedded-types requires names in the form of [GOPACKAGE.]TYPENAME: %v", t) + } + omitTypes[name] = struct{}{} + } + + protobufNames := NewProtobufNamer() + outputPackages := []generator.Target{} + nonOutputPackages := map[string]struct{}{} + + for _, input := range c.Inputs { + mod, found := inputModifiers[input] + if !found { + log.Fatalf("BUG: can't find input modifiers for %q", input) + } + pkg := c.Universe[input] + protopkg := newProtobufPackage(pkg.Path, pkg.Dir, mod.name, mod.allTypes, omitTypes) + header := append([]byte{}, boilerplate...) + header = append(header, protopkg.HeaderComment...) + protopkg.HeaderComment = header + protobufNames.Add(protopkg) + if mod.output { + outputPackages = append(outputPackages, protopkg) + } else { + nonOutputPackages[mod.name] = struct{}{} + } + } + c.Namers["proto"] = protobufNames + + for _, p := range outputPackages { + if err := p.(*protobufPackage).Clean(); err != nil { + log.Fatalf("Unable to clean package %s: %v", p.Name(), err) + } + } + + if g.Clean { + return + } + + // order package by imports, importees first + deps := deps(c, protobufNames.packages) + order, err := importOrder(deps) + if err != nil { + log.Fatalf("Failed to order packages by imports: %v", err) + } + topologicalPos := map[string]int{} + for i, p := range order { + topologicalPos[p] = i + } + sort.Sort(positionOrder{topologicalPos, protobufNames.packages}) + + var localOutputPackages []generator.Target + for _, p := range protobufNames.packages { + if _, ok := nonOutputPackages[p.Name()]; ok { + // if we're not outputting the package, don't include it in either package list + continue + } + localOutputPackages = append(localOutputPackages, p) + } + + if err := protobufNames.AssignTypesToPackages(c); err != nil { + log.Fatalf("Failed to identify Common types: %v", err) + } + + if err := c.ExecuteTargets(localOutputPackages); err != nil { + log.Fatalf("Failed executing local generator: %v", err) + } + + if g.OnlyIDL { + return + } + + if _, err := exec.LookPath("protoc"); err != nil { + log.Fatalf("Unable to find 'protoc': %v", err) + } + + searchArgs := []string{"-I", ".", "-I", g.OutputDir} + if len(g.ProtoImport) != 0 { + for _, s := range g.ProtoImport { + searchArgs = append(searchArgs, "-I", s) + } + } + // Despite docs saying that `--gogo_out=paths=source_relative:.` will + // output the .pb.go file to the same directory as the .proto file, it + // doesn't. Given example.com/foo/bar.proto (found in one of the -I paths + // above), the output becomes + // $output_base/example.com/foo/example.com/foo/bar.pb.go - basically + // useless. Users should set the output-dir to a single dir under which + // all the packages in question live (e.g. staging/src in kubernetes). + // Alternately, we could generate into a temp path and then move the + // resulting file back to the input dir, but that seems brittle in other + // ways. + args := searchArgs + args = append(args, fmt.Sprintf("--gogo_out=%s", g.OutputDir)) + + buf := &bytes.Buffer{} + if len(g.Conditional) > 0 { + fmt.Fprintf(buf, "// +build %s\n\n", g.Conditional) + } + buf.Write(boilerplate) + + for _, outputPackage := range outputPackages { + p := outputPackage.(*protobufPackage) + + path := filepath.Join(g.OutputDir, p.ImportPath()) + outputPath := filepath.Join(g.OutputDir, p.OutputPath()) + + // generate the gogoprotobuf protoc + cmd := exec.Command("protoc", append(args, path)...) + out, err := cmd.CombinedOutput() + if err != nil { + log.Println(strings.Join(cmd.Args, " ")) + log.Println(string(out)) + log.Fatalf("Unable to run protoc on %s: %v", p.Name(), err) + } + + if g.SkipGeneratedRewrite { + continue + } + + // alter the generated protobuf file to remove the generated types (but leave the serializers) and rewrite the + // package statement to match the desired package name + if err := RewriteGeneratedGogoProtobufFile(outputPath, p.ExtractGeneratedType, p.OptionalTypeName, buf.Bytes(), g.DropGogoGo); err != nil { + log.Fatalf("Unable to rewrite generated %s: %v", outputPath, err) + } + + outputPaths := []string{outputPath} + + // sort imports + cmd = exec.Command("goimports", append([]string{"-w"}, outputPaths...)...) + out, err = cmd.CombinedOutput() + if len(out) > 0 { + log.Print(string(out)) + } + if err != nil { + log.Println(strings.Join(cmd.Args, " ")) + log.Fatalf("Unable to rewrite imports for %s: %v", p.Name(), err) + } + + // format and simplify the generated file + cmd = exec.Command("gofmt", append([]string{"-s", "-w"}, outputPaths...)...) + out, err = cmd.CombinedOutput() + if len(out) > 0 { + log.Print(string(out)) + } + if err != nil { + log.Println(strings.Join(cmd.Args, " ")) + log.Fatalf("Unable to apply gofmt for %s: %v", p.Name(), err) + } + } + + if g.SkipGeneratedRewrite { + return + } + + if !g.KeepGogoproto { + // generate, but do so without gogoprotobuf extensions + for _, outputPackage := range outputPackages { + p := outputPackage.(*protobufPackage) + p.OmitGogo = true + } + if err := c.ExecuteTargets(localOutputPackages); err != nil { + log.Fatalf("Failed executing local generator: %v", err) + } + } + + for _, outputPackage := range outputPackages { + p := outputPackage.(*protobufPackage) + + if len(p.StructTags) == 0 { + continue + } + + pattern := filepath.Join(g.OutputDir, p.Path(), "*.go") + files, err := filepath.Glob(pattern) + if err != nil { + log.Fatalf("Can't glob pattern %q: %v", pattern, err) + } + + for _, s := range files { + if strings.HasSuffix(s, "_test.go") { + continue + } + if err := RewriteTypesWithProtobufStructTags(s, p.StructTags); err != nil { + log.Fatalf("Unable to rewrite with struct tags %s: %v", s, err) + } + } + } +} + +func deps(c *generator.Context, pkgs []*protobufPackage) map[string][]string { + ret := map[string][]string{} + for _, p := range pkgs { + pkg, ok := c.Universe[p.Path()] + if !ok { + log.Fatalf("Unrecognized package: %s", p.Path()) + } + + for _, d := range pkg.Imports { + ret[p.Path()] = append(ret[p.Path()], d.Path) + } + } + return ret +} + +// given a set of pkg->[]deps, return the order that ensures all deps are processed before the things that depend on them +func importOrder(deps map[string][]string) ([]string, error) { + // add all nodes and edges + var remainingNodes = map[string]struct{}{} + var graph = map[edge]struct{}{} + for to, froms := range deps { + remainingNodes[to] = struct{}{} + for _, from := range froms { + remainingNodes[from] = struct{}{} + graph[edge{from: from, to: to}] = struct{}{} + } + } + + // find initial nodes without any dependencies + sorted := findAndRemoveNodesWithoutDependencies(remainingNodes, graph) + for i := 0; i < len(sorted); i++ { + node := sorted[i] + removeEdgesFrom(node, graph) + sorted = append(sorted, findAndRemoveNodesWithoutDependencies(remainingNodes, graph)...) + } + if len(remainingNodes) > 0 { + return nil, fmt.Errorf("cycle: remaining nodes: %#v, remaining edges: %#v", remainingNodes, graph) + } + // for _, n := range sorted { + // fmt.Println("topological order", n) + // } + return sorted, nil +} + +// edge describes a from->to relationship in a graph +type edge struct { + from string + to string +} + +// findAndRemoveNodesWithoutDependencies finds nodes in the given set which are not pointed to by any edges in the graph, +// removes them from the set of nodes, and returns them in sorted order +func findAndRemoveNodesWithoutDependencies(nodes map[string]struct{}, graph map[edge]struct{}) []string { + roots := []string{} + // iterate over all nodes as potential "to" nodes + for node := range nodes { + incoming := false + // iterate over all remaining edges + for edge := range graph { + // if there's any edge to the node we care about, it's not a root + if edge.to == node { + incoming = true + break + } + } + // if there are no incoming edges, remove from the set of remaining nodes and add to our results + if !incoming { + delete(nodes, node) + roots = append(roots, node) + } + } + sort.Strings(roots) + return roots +} + +// removeEdgesFrom removes any edges from the graph where edge.from == node +func removeEdgesFrom(node string, graph map[edge]struct{}) { + for edge := range graph { + if edge.from == node { + delete(graph, edge) + } + } +} + +type positionOrder struct { + pos map[string]int + elements []*protobufPackage +} + +func (o positionOrder) Len() int { + return len(o.elements) +} + +func (o positionOrder) Less(i, j int) bool { + return o.pos[o.elements[i].Path()] < o.pos[o.elements[j].Path()] +} + +func (o positionOrder) Swap(i, j int) { + o.elements[i], o.elements[j] = o.elements[j], o.elements[i] +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd_test.go new file mode 100644 index 0000000000..c6b6717638 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/cmd_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2020 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 protobuf + +import ( + "reflect" + "testing" +) + +func TestImportOrder(t *testing.T) { + testcases := []struct { + Name string + Input map[string][]string + Expect []string + ExpectErr bool + }{ + { + Name: "empty", + Input: nil, + Expect: []string{}, + }, + { + Name: "simple", + Input: map[string][]string{"apps": {"core", "extensions", "meta"}, "extensions": {"core", "meta"}, "core": {"meta"}}, + Expect: []string{"meta", "core", "extensions", "apps"}, + }, + { + Name: "cycle", + Input: map[string][]string{"apps": {"core", "extensions", "meta"}, "extensions": {"core", "meta"}, "core": {"meta", "apps"}}, + ExpectErr: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + order, err := importOrder(tc.Input) + if err != nil { + if !tc.ExpectErr { + t.Fatalf("unexpected error: %v", err) + } + return + } + if tc.ExpectErr { + t.Fatalf("expected error, got none") + } + if !reflect.DeepEqual(order, tc.Expect) { + t.Fatalf("expected %v, got %v", tc.Expect, order) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/generator.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/generator.go new file mode 100644 index 0000000000..64634da849 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/generator.go @@ -0,0 +1,778 @@ +/* +Copyright 2015 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 protobuf + +import ( + "fmt" + "io" + "log" + "reflect" + "sort" + "strconv" + "strings" + + genutil "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +// genProtoIDL produces a .proto IDL. +type genProtoIDL struct { + // This base type is close enough to what we need, if we redefine some + // methods. + generator.GoGenerator + localPackage types.Name + localGoPackage types.Name + imports namer.ImportTracker + + generateAll bool + omitGogo bool + omitFieldTypes map[types.Name]struct{} +} + +func (g *genProtoIDL) PackageVars(c *generator.Context) []string { + if g.omitGogo { + return []string{ + fmt.Sprintf("option go_package = %q;", g.localGoPackage.Package), + } + } + return []string{ + "option (gogoproto.marshaler_all) = true;", + "option (gogoproto.stable_marshaler_all) = true;", + "option (gogoproto.sizer_all) = true;", + "option (gogoproto.goproto_stringer_all) = false;", + "option (gogoproto.stringer_all) = true;", + "option (gogoproto.unmarshaler_all) = true;", + "option (gogoproto.goproto_unrecognized_all) = false;", + "option (gogoproto.goproto_enum_prefix_all) = false;", + "option (gogoproto.goproto_getters_all) = false;", + fmt.Sprintf("option go_package = %q;", g.localGoPackage.Package), + } +} + +func (g *genProtoIDL) Filename() string { return g.OutputFilename + ".proto" } + +func (g *genProtoIDL) FileType() string { return "protoidl" } + +func (g *genProtoIDL) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + // The local namer returns the correct protobuf name for a proto type + // in the context of a package + "local": localNamer{g.localPackage}, + } +} + +// Filter ignores types that are identified as not exportable. +func (g *genProtoIDL) Filter(c *generator.Context, t *types.Type) bool { + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"protobuf"}, t.CommentLines) + if err != nil { + klog.Fatalf(`Error extracting tag "protobuf": %v`, err) + } + if tags["protobuf"] != nil { + if tags["protobuf"][0] == "false" { + // Type specified "false". + return false + } + if tags["protobuf"][0] == "true" { + // Type specified "true". + return true + } + klog.Fatalf(`Comment tag "protobuf" must be true or false, found: %q`, tags["protobuf"][0]) + } + if !g.generateAll { + // We're not generating everything. + return false + } + seen := map[*types.Type]bool{} + ok := isProtoable(seen, t) + return ok +} + +func isProtoable(seen map[*types.Type]bool, t *types.Type) bool { + if seen[t] { + // be optimistic in the case of type cycles. + return true + } + seen[t] = true + switch t.Kind { + case types.Builtin: + return true + case types.Alias: + return isProtoable(seen, t.Underlying) + case types.Slice, types.Pointer: + return isProtoable(seen, t.Elem) + case types.Map: + return isProtoable(seen, t.Key) && isProtoable(seen, t.Elem) + case types.Struct: + if len(t.Members) == 0 { + return true + } + for _, m := range t.Members { + if isProtoable(seen, m.Type) { + return true + } + } + return false + case types.Func, types.Chan: + return false + case types.DeclarationOf, types.Unknown, types.Unsupported: + return false + case types.Interface: + return false + default: + log.Printf("WARNING: type %q is not portable: %s", t.Kind, t.Name) + return false + } +} + +// isOptionalAlias should return true if the specified type has an underlying type +// (is an alias) of a map or slice and has the comment tag protobuf.nullable=true, +// indicating that the type should be nullable in protobuf. +func isOptionalAlias(t *types.Type) bool { + if t.Underlying == nil || (t.Underlying.Kind != types.Map && t.Underlying.Kind != types.Slice) { + return false + } + return extractBoolTagOrDie("protobuf.nullable", t.CommentLines) +} + +func (g *genProtoIDL) Imports(c *generator.Context) (imports []string) { + lines := []string{} + // TODO: this could be expressed more cleanly + for _, line := range g.imports.ImportLines() { + if g.omitGogo && line == "github.com/gogo/protobuf/gogoproto/gogo.proto" { + continue + } + lines = append(lines, line) + } + return lines +} + +// GenerateType makes the body of a file implementing a set for type t. +func (g *genProtoIDL) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + b := bodyGen{ + locator: &protobufLocator{ + namer: c.Namers["proto"].(ProtobufFromGoNamer), + tracker: g.imports, + universe: c.Universe, + + localGoPackage: g.localGoPackage.Package, + }, + localPackage: g.localPackage, + + omitGogo: g.omitGogo, + omitFieldTypes: g.omitFieldTypes, + + t: t, + } + switch t.Kind { + case types.Alias: + return b.doAlias(sw) + case types.Struct: + return b.doStruct(sw) + default: + return b.unknown() + } +} + +// ProtobufFromGoNamer finds the protobuf name of a type (and its package, and +// the package path) from its Go name. +type ProtobufFromGoNamer interface { + GoNameToProtoName(name types.Name) types.Name +} + +type ProtobufLocator interface { + ProtoTypeFor(t *types.Type) (*types.Type, error) + GoTypeForName(name types.Name) *types.Type + CastTypeName(name types.Name) string +} + +type protobufLocator struct { + namer ProtobufFromGoNamer + tracker namer.ImportTracker + universe types.Universe + + localGoPackage string +} + +// CastTypeName returns the cast type name of a Go type +// TODO: delegate to a new localgo namer? +func (p protobufLocator) CastTypeName(name types.Name) string { + if name.Package == p.localGoPackage { + return name.Name + } + return name.String() +} + +func (p protobufLocator) GoTypeForName(name types.Name) *types.Type { + if len(name.Package) == 0 { + name.Package = p.localGoPackage + } + return p.universe.Type(name) +} + +// ProtoTypeFor locates a Protobuf type for the provided Go type (if possible). +func (p protobufLocator) ProtoTypeFor(t *types.Type) (*types.Type, error) { + // we've already converted the type, or it's a map + if t.Kind == types.Protobuf || t.Kind == types.Map { + p.tracker.AddType(t) + return t, nil + } + // it's a fundamental type + if t, ok := isFundamentalProtoType(t); ok { + p.tracker.AddType(t) + return t, nil + } + // it's a message + if t.Kind == types.Struct || isOptionalAlias(t) { + t := &types.Type{ + Name: p.namer.GoNameToProtoName(t.Name), + Kind: types.Protobuf, + + CommentLines: t.CommentLines, + } + p.tracker.AddType(t) + return t, nil + } + return nil, errUnrecognizedType +} + +type bodyGen struct { + locator ProtobufLocator + localPackage types.Name + omitGogo bool + omitFieldTypes map[types.Name]struct{} + + t *types.Type +} + +func (b bodyGen) unknown() error { + return fmt.Errorf("not sure how to generate: %#v", b.t) +} + +func (b bodyGen) doAlias(sw *generator.SnippetWriter) error { + if !isOptionalAlias(b.t) { + return nil + } + + var kind string + switch b.t.Underlying.Kind { + case types.Map: + kind = "map" + default: + kind = "slice" + } + optional := &types.Type{ + Name: b.t.Name, + Kind: types.Struct, + + CommentLines: b.t.CommentLines, + SecondClosestCommentLines: b.t.SecondClosestCommentLines, + Members: []types.Member{ + { + Name: "Items", + CommentLines: []string{fmt.Sprintf("items, if empty, will result in an empty %s\n", kind)}, + Type: b.t.Underlying, + }, + }, + } + nested := b + nested.t = optional + return nested.doStruct(sw) +} + +func (b bodyGen) doStruct(sw *generator.SnippetWriter) error { + if len(b.t.Name.Name) == 0 { + return nil + } + if namer.IsPrivateGoName(b.t.Name.Name) { + return nil + } + + var alias *types.Type + var fields []protoField + options := []string{} + allOptions := gengo.ExtractCommentTags("+", b.t.CommentLines) + for k, v := range allOptions { + switch { + case strings.HasPrefix(k, "protobuf.options."): + key := strings.TrimPrefix(k, "protobuf.options.") + switch key { + case "marshal": + if v[0] == "false" { + if !b.omitGogo { + options = append(options, + "(gogoproto.marshaler) = false", + "(gogoproto.unmarshaler) = false", + "(gogoproto.sizer) = false", + ) + } + } + default: + if !b.omitGogo || !strings.HasPrefix(key, "(gogoproto.") { + if key == "(gogoproto.goproto_stringer)" && v[0] == "false" { + options = append(options, "(gogoproto.stringer) = false") + } + options = append(options, fmt.Sprintf("%s = %s", key, v[0])) + } + } + // protobuf.as allows a type to have the same message contents as another Go type + case k == "protobuf.as": + fields = nil + if alias = b.locator.GoTypeForName(types.Name{Name: v[0]}); alias == nil { + return fmt.Errorf("type %v references alias %q which does not exist", b.t, v[0]) + } + // protobuf.embed instructs the generator to use the named type in this package + // as an embedded message. + case k == "protobuf.embed": + fields = []protoField{ + { + Tag: 1, + Name: v[0], + Type: &types.Type{ + Name: types.Name{ + Name: v[0], + Package: b.localPackage.Package, + Path: b.localPackage.Path, + }, + }, + }, + } + } + } + if alias == nil { + alias = b.t + } + + // If we don't explicitly embed anything, generate fields by traversing fields. + if fields == nil { + memberFields, err := membersToFields(b.locator, alias, b.localPackage, b.omitFieldTypes) + if err != nil { + return fmt.Errorf("type %v cannot be converted to protobuf: %v", b.t, err) + } + fields = memberFields + } + + out := sw.Out() + genComment(out, b.t.CommentLines, "") + sw.Do(`message $.Name.Name$ { +`, b.t) + + if len(options) > 0 { + sort.Strings(options) + for _, s := range options { + fmt.Fprintf(out, " option %s;\n", s) + } + fmt.Fprintln(out) + } + + for i, field := range fields { + genComment(out, field.CommentLines, " ") + fmt.Fprintf(out, " ") + switch { + case field.Map: + case field.Repeated: + fmt.Fprintf(out, "repeated ") + case field.Required: + fmt.Fprintf(out, "required ") + default: + fmt.Fprintf(out, "optional ") + } + sw.Do(`$.Type|local$ $.Name$ = $.Tag$`, field) + if len(field.Extras) > 0 { + extras := []string{} + for k, v := range field.Extras { + if b.omitGogo && strings.HasPrefix(k, "(gogoproto.") { + continue + } + extras = append(extras, fmt.Sprintf("%s = %s", k, v)) + } + sort.Strings(extras) + if len(extras) > 0 { + fmt.Fprintf(out, " [") + fmt.Fprint(out, strings.Join(extras, ", ")) + fmt.Fprintf(out, "]") + } + } + fmt.Fprintf(out, ";\n") + if i != len(fields)-1 { + fmt.Fprintf(out, "\n") + } + } + fmt.Fprintf(out, "}\n\n") + return nil +} + +type protoField struct { + LocalPackage types.Name + + Tag int + Name string + Type *types.Type + Map bool + Repeated bool + Optional bool + Required bool + Nullable bool + Extras map[string]string + + CommentLines []string +} + +var ( + errUnrecognizedType = fmt.Errorf("did not recognize the provided type") +) + +func isFundamentalProtoType(t *types.Type) (*types.Type, bool) { + // TODO: when we enable proto3, also include other fundamental types in the google.protobuf package + // switch { + // case t.Kind == types.Struct && t.Name == types.Name{Package: "time", Name: "Time"}: + // return &types.Type{ + // Kind: types.Protobuf, + // Name: types.Name{Path: "google/protobuf/timestamp.proto", Package: "google.protobuf", Name: "Timestamp"}, + // }, true + // } + switch t.Kind { + case types.Slice: + if t.Elem.Name.Name == "byte" && len(t.Elem.Name.Package) == 0 { + return &types.Type{Name: types.Name{Name: "bytes"}, Kind: types.Protobuf}, true + } + case types.Builtin: + switch t.Name.Name { + case "string", "uint32", "int32", "uint64", "int64", "bool": + return &types.Type{Name: types.Name{Name: t.Name.Name}, Kind: types.Protobuf}, true + case "int": + return &types.Type{Name: types.Name{Name: "int64"}, Kind: types.Protobuf}, true + case "uint": + return &types.Type{Name: types.Name{Name: "uint64"}, Kind: types.Protobuf}, true + case "float64", "float": + return &types.Type{Name: types.Name{Name: "double"}, Kind: types.Protobuf}, true + case "float32": + return &types.Type{Name: types.Name{Name: "float"}, Kind: types.Protobuf}, true + case "uintptr": + return &types.Type{Name: types.Name{Name: "uint64"}, Kind: types.Protobuf}, true + } + // TODO: complex? + } + return t, false +} + +func memberTypeToProtobufField(locator ProtobufLocator, field *protoField, t *types.Type) error { + var err error + switch t.Kind { + case types.Protobuf: + field.Type, err = locator.ProtoTypeFor(t) + case types.Builtin: + field.Type, err = locator.ProtoTypeFor(t) + case types.Map: + valueField := &protoField{} + if err := memberTypeToProtobufField(locator, valueField, t.Elem); err != nil { + return err + } + keyField := &protoField{} + if err := memberTypeToProtobufField(locator, keyField, t.Key); err != nil { + return err + } + // All other protobuf types have kind types.Protobuf, so setting types.Map + // here would be very misleading. + field.Type = &types.Type{ + Kind: types.Protobuf, + Key: keyField.Type, + Elem: valueField.Type, + } + if !strings.HasPrefix(t.Name.Name, "map[") { + field.Extras["(gogoproto.casttype)"] = strconv.Quote(locator.CastTypeName(t.Name)) + } + if k, ok := keyField.Extras["(gogoproto.casttype)"]; ok { + field.Extras["(gogoproto.castkey)"] = k + } + if v, ok := valueField.Extras["(gogoproto.casttype)"]; ok { + field.Extras["(gogoproto.castvalue)"] = v + } + field.Map = true + case types.Pointer: + if err := memberTypeToProtobufField(locator, field, t.Elem); err != nil { + return err + } + field.Nullable = true + case types.Alias: + if isOptionalAlias(t) { + field.Type, err = locator.ProtoTypeFor(t) + field.Nullable = true + } else { + if err := memberTypeToProtobufField(locator, field, t.Underlying); err != nil { + log.Printf("failed to alias: %s %s: err %v", t.Name, t.Underlying.Name, err) + return err + } + // If this is not an alias to a slice, cast to the alias + if !field.Repeated { + if field.Extras == nil { + field.Extras = make(map[string]string) + } + field.Extras["(gogoproto.casttype)"] = strconv.Quote(locator.CastTypeName(t.Name)) + } + } + case types.Slice: + if t.Elem.Name.Name == "byte" && len(t.Elem.Name.Package) == 0 { + field.Type = &types.Type{Name: types.Name{Name: "bytes"}, Kind: types.Protobuf} + return nil + } + if err := memberTypeToProtobufField(locator, field, t.Elem); err != nil { + return err + } + field.Repeated = true + case types.Struct: + if len(t.Name.Name) == 0 { + return errUnrecognizedType + } + field.Type, err = locator.ProtoTypeFor(t) + field.Nullable = false + default: + return errUnrecognizedType + } + return err +} + +// protobufTagToField extracts information from an existing protobuf tag +func protobufTagToField(tag string, field *protoField, m types.Member, t *types.Type, localPackage types.Name) error { + if len(tag) == 0 || tag == "-" { + return nil + } + + // protobuf:"bytes,3,opt,name=Id,customtype=github.com/gogo/protobuf/test.Uuid" + parts := strings.Split(tag, ",") + if len(parts) < 3 { + return fmt.Errorf("member %q of %q malformed 'protobuf' tag, not enough segments", m.Name, t.Name) + } + protoTag, err := strconv.Atoi(parts[1]) + if err != nil { + return fmt.Errorf("member %q of %q malformed 'protobuf' tag, field ID is %q which is not an integer: %w", m.Name, t.Name, parts[1], err) + } + field.Tag = protoTag + + // In general there is doesn't make sense to parse the protobuf tags to get the type, + // as all auto-generated once will have wire type "bytes", "varint" or "fixed64". + // However, sometimes we explicitly set them to have a custom serialization, e.g.: + // type Time struct { + // time.Time `protobuf:"Timestamp,1,req,name=time"` + // } + // to force the generator to use a given type (that we manually wrote serialization & + // deserialization methods for). + switch parts[0] { + case "varint", "fixed32", "fixed64", "bytes", "group": + default: + var name types.Name + if last := strings.LastIndex(parts[0], "."); last != -1 { + prefix := parts[0][:last] + name = types.Name{ + Name: parts[0][last+1:], + Package: prefix, + Path: strings.ReplaceAll(prefix, ".", "/"), + } + } else { + name = types.Name{ + Name: parts[0], + Package: localPackage.Package, + Path: localPackage.Path, + } + } + field.Type = &types.Type{ + Name: name, + Kind: types.Protobuf, + } + } + + protoExtra := make(map[string]string) + for i, extra := range parts[3:] { + parts := strings.SplitN(extra, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("member %q of %q malformed 'protobuf' tag, tag %d should be key=value, got %q", m.Name, t.Name, i+4, extra) + } + switch parts[0] { + case "name": + protoExtra[parts[0]] = parts[1] + case "casttype", "castkey", "castvalue": + parts[0] = fmt.Sprintf("(gogoproto.%s)", parts[0]) + protoExtra[parts[0]] = strconv.Quote(parts[1]) + } + } + + field.Extras = protoExtra + if name, ok := protoExtra["name"]; ok { + field.Name = name + delete(protoExtra, "name") + } + + return nil +} + +func membersToFields(locator ProtobufLocator, t *types.Type, localPackage types.Name, omitFieldTypes map[types.Name]struct{}) ([]protoField, error) { + fields := []protoField{} + + for _, m := range t.Members { + if namer.IsPrivateGoName(m.Name) { + // skip private fields + continue + } + if _, ok := omitFieldTypes[types.Name{Name: m.Type.Name.Name, Package: m.Type.Name.Package}]; ok { + continue + } + tags := reflect.StructTag(m.Tags) + field := protoField{ + LocalPackage: localPackage, + + Tag: -1, + Extras: make(map[string]string), + } + + protobufTag := tags.Get("protobuf") + if protobufTag == "-" { + continue + } + + if err := protobufTagToField(protobufTag, &field, m, t, localPackage); err != nil { + return nil, err + } + + // extract information from JSON field tag + if tag, _ := tags.Lookup("json"); len(tag) > 0 { + parts := strings.Split(tag, ",") + if len(field.Name) == 0 && len(parts[0]) != 0 { + field.Name = parts[0] + } + if field.Tag == -1 && field.Name == "-" { + continue + } + } + + if field.Type == nil { + if err := memberTypeToProtobufField(locator, &field, m.Type); err != nil { + return nil, fmt.Errorf("unable to embed type %q as field %q in %q: %v", m.Type, field.Name, t.Name, err) + } + } + if len(field.Name) == 0 { + field.Name = namer.IL(m.Name) + } + + if field.Map && field.Repeated { + // maps cannot be repeated + field.Repeated = false + field.Nullable = true + } + + if !field.Nullable { + field.Extras["(gogoproto.nullable)"] = "false" + } + if (field.Type.Name.Name == "bytes" && field.Type.Name.Package == "") || (field.Repeated && field.Type.Name.Package == "" && namer.IsPrivateGoName(field.Type.Name.Name)) { + delete(field.Extras, "(gogoproto.nullable)") + } + if field.Name != m.Name { + field.Extras["(gogoproto.customname)"] = strconv.Quote(m.Name) + } + field.CommentLines = m.CommentLines + fields = append(fields, field) + } + + // assign tags + highest := 0 + byTag := make(map[int]*protoField) + // fields are in Go struct order, which we preserve + for i := range fields { + field := &fields[i] + tag := field.Tag + if tag != -1 { + if existing, ok := byTag[tag]; ok { + return nil, fmt.Errorf("field %q and %q both have tag %d", field.Name, existing.Name, tag) + } + byTag[tag] = field + } + if tag > highest { + highest = tag + } + } + // starting from the highest observed tag, assign new field tags + for i := range fields { + field := &fields[i] + if field.Tag != -1 { + continue + } + highest++ + field.Tag = highest + byTag[field.Tag] = field + } + return fields, nil +} + +func genComment(out io.Writer, lines []string, indent string) { + for { + l := len(lines) + if l == 0 || len(lines[l-1]) != 0 { + break + } + lines = lines[:l-1] + } + for _, c := range lines { + if len(c) == 0 { + fmt.Fprintf(out, "%s//\n", indent) // avoid trailing whitespace + continue + } + fmt.Fprintf(out, "%s// %s\n", indent, c) + } +} + +func formatProtoFile(source []byte) ([]byte, error) { + // TODO; Is there any protobuf formatter? + return source, nil +} + +func assembleProtoFile(w io.Writer, f *generator.File) { + w.Write(f.Header) + + fmt.Fprint(w, "syntax = \"proto2\";\n\n") + + if len(f.PackageName) > 0 { + fmt.Fprintf(w, "package %s;\n\n", f.PackageName) + } + + if len(f.Imports) > 0 { + imports := []string{} + for i := range f.Imports { + imports = append(imports, i) + } + sort.Strings(imports) + for _, s := range imports { + fmt.Fprintf(w, "import %q;\n", s) + } + fmt.Fprint(w, "\n") + } + + if f.Vars.Len() > 0 { + fmt.Fprintf(w, "%s\n", f.Vars.String()) + } + + w.Write(f.Body.Bytes()) +} + +func NewProtoFile() *generator.DefaultFileType { + return &generator.DefaultFileType{ + Format: formatProtoFile, + Assemble: assembleProtoFile, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/import_tracker.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/import_tracker.go new file mode 100644 index 0000000000..0031c9bd83 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/import_tracker.go @@ -0,0 +1,50 @@ +/* +Copyright 2015 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 protobuf + +import ( + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +type ImportTracker struct { + namer.DefaultImportTracker +} + +func NewImportTracker(local types.Name, typesToAdd ...*types.Type) *ImportTracker { + tracker := namer.NewDefaultImportTracker(local) + tracker.IsInvalidType = func(t *types.Type) bool { return t.Kind != types.Protobuf } + tracker.LocalName = func(name types.Name) string { return name.Package } + tracker.PrintImport = func(path, name string) string { return path } + + tracker.AddTypes(typesToAdd...) + return &ImportTracker{ + DefaultImportTracker: tracker, + } +} + +// AddNullable ensures that support for the nullable Gogo-protobuf extension is added. +func (tracker *ImportTracker) AddNullable() { + tracker.AddType(&types.Type{ + Kind: types.Protobuf, + Name: types.Name{ + Name: "nullable", + Package: "gogoproto", + Path: "github.com/gogo/protobuf/gogoproto/gogo.proto", + }, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/namer.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/namer.go new file mode 100644 index 0000000000..2ad0a9537e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/namer.go @@ -0,0 +1,206 @@ +/* +Copyright 2015 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 protobuf + +import ( + "fmt" + "reflect" + "strings" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +type localNamer struct { + localPackage types.Name +} + +func (n localNamer) Name(t *types.Type) string { + if t.Key != nil && t.Elem != nil { + return fmt.Sprintf("map<%s, %s>", n.Name(t.Key), n.Name(t.Elem)) + } + if len(n.localPackage.Package) != 0 && n.localPackage.Package == t.Name.Package { + return t.Name.Name + } + // For non-local and non-fundamental types, use an absolute reference + // see https://protobuf.com/docs/language-spec#type-references + if strings.Contains(t.Name.Package, ".") { + return fmt.Sprintf(".%s", t.Name) + } + return t.Name.String() +} + +type protobufNamer struct { + packages []*protobufPackage + // The key here is a Go import-path. + packagesByPath map[string]*protobufPackage +} + +func NewProtobufNamer() *protobufNamer { + return &protobufNamer{ + packagesByPath: make(map[string]*protobufPackage), + } +} + +func (n *protobufNamer) Name(t *types.Type) string { + if t.Kind == types.Map { + return fmt.Sprintf("map<%s, %s>", n.Name(t.Key), n.Name(t.Elem)) + } + return t.Name.String() +} + +func (n *protobufNamer) Add(p *protobufPackage) { + if _, ok := n.packagesByPath[p.Path()]; !ok { + n.packagesByPath[p.Path()] = p + n.packages = append(n.packages, p) + } +} + +func (n *protobufNamer) GoNameToProtoName(name types.Name) types.Name { + if p, ok := n.packagesByPath[name.Package]; ok { + return types.Name{ + Name: name.Name, + Package: p.Name(), + Path: p.ImportPath(), + } + } + for _, p := range n.packages { + if _, ok := p.FilterTypes[name]; ok { + return types.Name{ + Name: name.Name, + Package: p.Name(), + Path: p.ImportPath(), + } + } + } + return types.Name{Name: name.Name} +} + +func protoSafePackage(name string) string { + pkg := strings.ReplaceAll(name, "/", ".") + return strings.ReplaceAll(pkg, "-", "_") +} + +type typeNameSet map[types.Name]*protobufPackage + +// assignGoTypeToProtoPackage looks for Go and Protobuf types that are referenced by a type in +// a package. It will not recurse into protobuf types. +func assignGoTypeToProtoPackage(p *protobufPackage, t *types.Type, local, global typeNameSet, optional map[types.Name]struct{}) { + newT, isProto := isFundamentalProtoType(t) + if isProto { + t = newT + } + if otherP, ok := global[t.Name]; ok { + if _, ok := local[t.Name]; !ok { + p.Imports.AddType(&types.Type{ + Kind: types.Protobuf, + Name: otherP.ProtoTypeName(), + }) + } + return + } + if t.Name.Package == p.Path() { + // Associate types only to their own package + global[t.Name] = p + } + if _, ok := local[t.Name]; ok { + return + } + // don't recurse into existing proto types + if isProto { + p.Imports.AddType(t) + return + } + + local[t.Name] = p + for _, m := range t.Members { + if namer.IsPrivateGoName(m.Name) { + continue + } + field := &protoField{} + tag := reflect.StructTag(m.Tags).Get("protobuf") + if tag == "-" { + continue + } + if err := protobufTagToField(tag, field, m, t, p.ProtoTypeName()); err == nil && field.Type != nil { + assignGoTypeToProtoPackage(p, field.Type, local, global, optional) + continue + } + assignGoTypeToProtoPackage(p, m.Type, local, global, optional) + } + // TODO: should methods be walked? + if t.Elem != nil { + assignGoTypeToProtoPackage(p, t.Elem, local, global, optional) + } + if t.Key != nil { + assignGoTypeToProtoPackage(p, t.Key, local, global, optional) + } + if t.Underlying != nil { + if t.Kind == types.Alias && isOptionalAlias(t) { + optional[t.Name] = struct{}{} + } + assignGoTypeToProtoPackage(p, t.Underlying, local, global, optional) + } +} + +// isTypeApplicableToProtobuf checks to see if a type is relevant for protobuf processing. +// Currently, it filters out functions and private types. +func isTypeApplicableToProtobuf(t *types.Type) bool { + // skip functions -- we don't care about them for protobuf + if t.Kind == types.Func || (t.Kind == types.DeclarationOf && t.Underlying.Kind == types.Func) { + return false + } + // skip private types + if namer.IsPrivateGoName(t.Name.Name) { + return false + } + + return true +} + +func (n *protobufNamer) AssignTypesToPackages(c *generator.Context) error { + global := make(typeNameSet) + for _, p := range n.packages { + local := make(typeNameSet) + optional := make(map[types.Name]struct{}) + p.Imports = NewImportTracker(p.ProtoTypeName()) + for _, t := range c.Order { + if t.Name.Package != p.Path() { + continue + } + if !isTypeApplicableToProtobuf(t) { + // skip types that we don't care about, like functions + continue + } + assignGoTypeToProtoPackage(p, t, local, global, optional) + } + p.FilterTypes = make(map[types.Name]struct{}) + p.LocalNames = make(map[string]struct{}) + p.OptionalTypeNames = make(map[string]struct{}) + for k, v := range local { + if v == p { + p.FilterTypes[k] = struct{}{} + p.LocalNames[k.Name] = struct{}{} + if _, ok := optional[k]; ok { + p.OptionalTypeNames[k.Name] = struct{}{} + } + } + } + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/namer_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/namer_test.go new file mode 100644 index 0000000000..0ee71f80be --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/namer_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2016 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 protobuf + +import "testing" + +func TestProtoSafePackage(t *testing.T) { + tests := []struct { + pkg string + expected string + }{ + { + pkg: "foo", + expected: "foo", + }, + { + pkg: "foo/bar", + expected: "foo.bar", + }, + { + pkg: "foo/bar/baz", + expected: "foo.bar.baz", + }, + { + pkg: "foo/bar-baz/x/y-z/q", + expected: "foo.bar_baz.x.y_z.q", + }, + } + + for _, test := range tests { + actual := protoSafePackage(test.pkg) + if e, a := test.expected, actual; e != a { + t.Errorf("%s: expected %s, got %s", test.pkg, e, a) + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/package.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/package.go new file mode 100644 index 0000000000..b31a7c4dd7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/package.go @@ -0,0 +1,205 @@ +/* +Copyright 2015 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 protobuf + +import ( + "fmt" + "go/ast" + "log" + "os" + "path/filepath" + "reflect" + "strings" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" +) + +func newProtobufPackage(packagePath, packageDir, packageName string, generateAll bool, omitFieldTypes map[types.Name]struct{}) *protobufPackage { + pkg := &protobufPackage{ + SimpleTarget: generator.SimpleTarget{ + // The protobuf package name (foo.bar.baz) + PkgName: packageName, + PkgPath: packagePath, + PkgDir: packageDir, + HeaderComment: []byte("// This file was autogenerated by go-to-protobuf. Do not edit it manually!\n\n"), + PkgDocComment: []byte(fmt.Sprintf("// Package %s is an autogenerated protobuf IDL.\n", packageName)), + }, + GenerateAll: generateAll, + OmitFieldTypes: omitFieldTypes, + } + pkg.FilterFunc = pkg.filterFunc + pkg.GeneratorsFunc = pkg.generatorsFunc + return pkg +} + +// protobufPackage contains the protobuf implementation of Package. +type protobufPackage struct { + generator.SimpleTarget + + // If true, generate protobuf serializations for all public types. + // If false, only generate protobuf serializations for structs that + // request serialization. + GenerateAll bool + + // A list of types to filter to; if not specified all types will be included. + FilterTypes map[types.Name]struct{} + + // If true, omit any gogoprotobuf extensions not defined as types. + OmitGogo bool + + // A list of field types that will be excluded from the output struct + OmitFieldTypes map[types.Name]struct{} + + // A list of names that this package exports + LocalNames map[string]struct{} + + // A list of type names in this package that will need marshaller rewriting + // to remove synthetic protobuf fields. + OptionalTypeNames map[string]struct{} + + // A list of struct tags to generate onto named struct fields + StructTags map[string]map[string]string + + // An import tracker for this package + Imports *ImportTracker +} + +func (p *protobufPackage) Clean() error { + for _, s := range []string{p.ImportPath(), p.OutputPath()} { + if err := os.Remove(filepath.Join(p.Dir(), filepath.Base(s))); err != nil && !os.IsNotExist(err) { + return err + } + } + return nil +} + +func (p *protobufPackage) ProtoTypeName() types.Name { + return types.Name{ + Name: p.Path(), // the go path "foo/bar/baz" + Package: p.Name(), // the protobuf package "foo.bar.baz" + Path: p.ImportPath(), // the path of the import to get the proto + } +} + +func (p *protobufPackage) filterFunc(c *generator.Context, t *types.Type) bool { + switch t.Kind { + case types.Func, types.Chan: + return false + case types.Struct: + if t.Name.Name == "struct{}" { + return false + } + case types.Builtin: + return false + case types.Alias: + if !isOptionalAlias(t) { + return false + } + case types.Slice, types.Array, types.Map: + return false + case types.Pointer: + return false + } + if _, ok := isFundamentalProtoType(t); ok { + return false + } + _, ok := p.FilterTypes[t.Name] + return ok +} + +func (p *protobufPackage) HasGoType(name string) bool { + _, ok := p.LocalNames[name] + return ok +} + +func (p *protobufPackage) OptionalTypeName(name string) bool { + _, ok := p.OptionalTypeNames[name] + return ok +} + +func (p *protobufPackage) ExtractGeneratedType(t *ast.TypeSpec) bool { + if !p.HasGoType(t.Name.Name) { + return false + } + + switch s := t.Type.(type) { + case *ast.StructType: + for i, f := range s.Fields.List { + if len(f.Tag.Value) == 0 { + continue + } + tag := strings.Trim(f.Tag.Value, "`") + protobufTag := reflect.StructTag(tag).Get("protobuf") + if len(protobufTag) == 0 { + continue + } + if len(f.Names) > 1 { + log.Printf("WARNING: struct %s field %d %s: defined multiple names but single protobuf tag", t.Name.Name, i, f.Names[0].Name) + // TODO hard error? + } + if p.StructTags == nil { + p.StructTags = make(map[string]map[string]string) + } + m := p.StructTags[t.Name.Name] + if m == nil { + m = make(map[string]string) + p.StructTags[t.Name.Name] = m + } + m[f.Names[0].Name] = tag + } + default: + log.Printf("WARNING: unexpected Go AST type definition: %#v", t) + } + + return true +} + +func (p *protobufPackage) generatorsFunc(c *generator.Context) []generator.Generator { + generators := []generator.Generator{} + + p.Imports.AddNullable() + + generators = append(generators, &genProtoIDL{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "generated", // the extension is added later + }, + localPackage: types.Name{Package: p.Name(), Path: p.Path()}, + localGoPackage: types.Name{Package: p.Path(), Name: p.GoPackageName()}, + imports: p.Imports, + generateAll: p.GenerateAll, + omitGogo: p.OmitGogo, + omitFieldTypes: p.OmitFieldTypes, + }) + return generators +} + +func (p *protobufPackage) GoPackageName() string { + return filepath.Base(p.Path()) +} + +func (p *protobufPackage) ImportPath() string { + return filepath.Join(p.Path(), "generated.proto") +} + +func (p *protobufPackage) OutputPath() string { + return filepath.Join(p.Path(), "generated.pb.go") +} + +var ( + _ = generator.Target(&protobufPackage{}) +) diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser.go new file mode 100644 index 0000000000..3753c27c9a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser.go @@ -0,0 +1,615 @@ +/* +Copyright 2015 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 protobuf + +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/printer" + "go/token" + "os" + "reflect" + "strings" + + customreflect "k8s.io/code-generator/third_party/forked/golang/reflect" +) + +func rewriteFile(name string, header []byte, rewriteFn func(*token.FileSet, *ast.File) error) error { + fset := token.NewFileSet() + src, err := os.ReadFile(name) + if err != nil { + return err + } + file, err := parser.ParseFile(fset, name, src, parser.DeclarationErrors|parser.ParseComments) + if err != nil { + return err + } + + if err := rewriteFn(fset, file); err != nil { + return err + } + + b := &bytes.Buffer{} + b.Write(header) + if err := printer.Fprint(b, fset, file); err != nil { + return err + } + + body, err := format.Source(b.Bytes()) + if err != nil { + return err + } + + f, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer f.Close() + if _, err := f.Write(body); err != nil { + return err + } + return f.Close() +} + +// ExtractFunc extracts information from the provided TypeSpec and returns true if the type should be +// removed from the destination file. +type ExtractFunc func(*ast.TypeSpec) bool + +// OptionalFunc returns true if the provided local name is a type that has protobuf.nullable=true +// and should have its marshal functions adjusted to remove the 'Items' accessor. +type OptionalFunc func(name string) bool + +func RewriteGeneratedGogoProtobufFile(file string, extractFn ExtractFunc, optionalFn OptionalFunc, header []byte, dropGogo bool) error { + return rewriteFile(file, header, func(fset *token.FileSet, file *ast.File) error { + cmap := ast.NewCommentMap(fset, file, file.Comments) + + // transform methods that point to optional maps or slices + for _, d := range file.Decls { + rewriteOptionalMethods(d, optionalFn) + } + if dropGogo { + // transform references to gogo sort util + var oldSortImport string + var usedSort bool + for _, d := range file.Decls { + oldSortImport, usedSort = rewriteGogoSortImport(d) + if usedSort { + break + } + } + if usedSort { + for _, d := range file.Decls { + rewriteGogoSort(d, oldSortImport, "sort") + } + } + } + + // remove types that are already declared + decls := []ast.Decl{} + for _, d := range file.Decls { + if dropExistingTypeDeclarations(d, extractFn) { + continue + } + if dropEmptyImportDeclarations(d) { + continue + } + // remove all but required functions + if dropGogo && dropUnusedGo(d) { + continue + } + decls = append(decls, d) + } + file.Decls = decls + + // remove unmapped comments + file.Comments = cmap.Filter(file).Comments() + return nil + }) +} + +// rewriteGogoSortImport rewrites an import of "github.com/gogo/protobuf/sortkeys" to "sort", +// and returns the original package alias and true if the rewrite occurred. +// Returns "", false if the decl is not an import decl, or does not contain an import of "github.com/gogo/protobuf/sortkeys", +func rewriteGogoSortImport(decl ast.Decl) (string, bool) { + t, ok := decl.(*ast.GenDecl) + if !ok { + return "", false + } + if t.Tok != token.IMPORT { + return "", false + } + for _, s := range t.Specs { + if spec, ok := s.(*ast.ImportSpec); ok { + if spec.Path != nil && spec.Path.Value == `"github.com/gogo/protobuf/sortkeys"` { + // switch gogo sort to stdlib sort + spec.Path.Value = `"sort"` + oldName := "sortkeys" + if spec.Name != nil { + oldName = spec.Name.Name + } + spec.Name = nil + return oldName, true + } + } + } + return "", false +} + +// rewriteGogoSort walks the AST, replacing use of the oldSortImport package with newSortImport +func rewriteGogoSort(decl ast.Decl, oldSortImport, newSortImport string) { + t, ok := decl.(*ast.FuncDecl) + if !ok { + return + } + ast.Walk(replacePackageVisitor{oldPackage: oldSortImport, newPackage: newSortImport}, t.Body) +} + +// keepFuncs is an allowlist of top-level func decls we should keep +var keepFuncs = map[string]bool{ + // generated helpers + "sovGenerated": true, + "sozGenerated": true, + "skipGenerated": true, + "encodeVarintGenerated": true, + "valueToStringGenerated": true, + + // unmarshal + "Reset": true, + "Unmarshal": true, + + // marshal + "Size": true, + "Marshal": true, + "MarshalTo": true, + "MarshalToSizedBuffer": true, + + // other widely used methods + "String": true, +} + +// keepVars is an allowlist of top-level var decls we should keep +var keepVars = map[string]bool{ + "ErrInvalidLengthGenerated": true, + "ErrIntOverflowGenerated": true, + "ErrUnexpectedEndOfGroupGenerated": true, +} + +// dropUnusedGo returns true if the top-level decl should be dropped. +// Has the following behavior for different decl types: +// * import: decl is rewritten to drop gogo package imports. Returns true if all imports in the decl were gogo imports, false if non-gogo imports remain. +// * var: decl is rewritten to drop vars not in the keepVars allowlist. Returns true if all vars in the decl were removed, false if allowlisted vars remain. +// * const: returns true +// * type: returns true +// * func: returns true if the func is not in the keepFuncs allowlist and should be dropped. +// * other: returns false +func dropUnusedGo(decl ast.Decl) bool { + switch t := decl.(type) { + case *ast.GenDecl: + switch t.Tok { + case token.IMPORT: + specs := []ast.Spec{} + for _, s := range t.Specs { + if spec, ok := s.(*ast.ImportSpec); ok { + if spec.Path == nil || !strings.HasPrefix(spec.Path.Value, `"github.com/gogo/protobuf/`) { + specs = append(specs, spec) + } + } + } + if len(specs) == 0 { + return true + } + t.Specs = specs + return false + case token.CONST: + // drop all const declarations + return true + case token.VAR: + specs := []ast.Spec{} + for _, s := range t.Specs { + if spec, ok := s.(*ast.ValueSpec); ok { + if keepVars[spec.Names[0].Name] { + specs = append(specs, spec) + } + } + } + if len(specs) == 0 { + return true + } + t.Specs = specs + return false + case token.TYPE: + // drop all type declarations + return true + } + case *ast.FuncDecl: + name := "" + if t.Name != nil { + name = t.Name.Name + } + return !keepFuncs[name] + default: + return false + } + return false +} + +// rewriteOptionalMethods makes specific mutations to marshaller methods that belong to types identified +// as being "optional" (they may be nil on the wire). This allows protobuf to serialize a map or slice and +// properly discriminate between empty and nil (which is not possible in protobuf). +// TODO: move into upstream gogo-protobuf once https://github.com/gogo/protobuf/issues/181 +// has agreement +func rewriteOptionalMethods(decl ast.Decl, isOptional OptionalFunc) { + if t, ok := decl.(*ast.FuncDecl); ok { + ident, ptr, ok := receiver(t) + if !ok { + return + } + + // correct initialization of the form `m.Field = &OptionalType{}` to + // `m.Field = OptionalType{}` + if t.Name.Name == "Unmarshal" { + ast.Walk(optionalAssignmentVisitor{fn: isOptional}, t.Body) + } + + if !isOptional(ident.Name) { + return + } + + switch t.Name.Name { + case "Unmarshal": + ast.Walk(&optionalItemsVisitor{}, t.Body) + case "MarshalTo", "Size", "String", "MarshalToSizedBuffer": + ast.Walk(&optionalItemsVisitor{}, t.Body) + fallthrough + case "Marshal": + // if the method has a pointer receiver, set it back to a normal receiver + if ptr { + t.Recv.List[0].Type = ident + } + } + } +} + +type optionalAssignmentVisitor struct { + fn OptionalFunc +} + +// Visit walks the provided node, transforming field initializations of the form +// m.Field = &OptionalType{} -> m.Field = OptionalType{} +func (v optionalAssignmentVisitor) Visit(n ast.Node) ast.Visitor { + if t, ok := n.(*ast.AssignStmt); ok { + if len(t.Lhs) == 1 && len(t.Rhs) == 1 { + if !isFieldSelector(t.Lhs[0], "m", "") { + return nil + } + unary, ok := t.Rhs[0].(*ast.UnaryExpr) + if !ok || unary.Op != token.AND { + return nil + } + composite, ok := unary.X.(*ast.CompositeLit) + if !ok || composite.Type == nil || len(composite.Elts) != 0 { + return nil + } + if ident, ok := composite.Type.(*ast.Ident); ok && v.fn(ident.Name) { + t.Rhs[0] = composite + } + } + return nil + } + return v +} + +type optionalItemsVisitor struct{} + +// Visit walks the provided node, looking for specific patterns to transform that match +// the effective outcome of turning struct{ map[x]y || []x } into map[x]y or []x. +func (v *optionalItemsVisitor) Visit(n ast.Node) ast.Visitor { + switch t := n.(type) { + case *ast.RangeStmt: + if isFieldSelector(t.X, "m", "Items") { + t.X = &ast.Ident{Name: "m"} + } + case *ast.AssignStmt: + if len(t.Lhs) == 1 && len(t.Rhs) == 1 { + switch lhs := t.Lhs[0].(type) { + case *ast.IndexExpr: + if isFieldSelector(lhs.X, "m", "Items") { + lhs.X = &ast.StarExpr{X: &ast.Ident{Name: "m"}} + } + default: + if isFieldSelector(t.Lhs[0], "m", "Items") { + t.Lhs[0] = &ast.StarExpr{X: &ast.Ident{Name: "m"}} + } + } + if rhs, ok := t.Rhs[0].(*ast.CallExpr); ok { + if ident, ok := rhs.Fun.(*ast.Ident); ok && ident.Name == "append" { + ast.Walk(v, rhs) + if len(rhs.Args) > 0 { + if arg, ok := rhs.Args[0].(*ast.Ident); ok { + if arg.Name == "m" { + rhs.Args[0] = &ast.StarExpr{X: &ast.Ident{Name: "m"}} + } + } + } + return nil + } + } + } + case *ast.IfStmt: + if cond, ok := t.Cond.(*ast.BinaryExpr); ok { + if cond.Op == token.EQL { + if isFieldSelector(cond.X, "m", "Items") && isIdent(cond.Y, "nil") { + cond.X = &ast.StarExpr{X: &ast.Ident{Name: "m"}} + } + } + } + if t.Init != nil { + // Find form: + // if err := m[len(m.Items)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + // return err + // } + if s, ok := t.Init.(*ast.AssignStmt); ok { + if call, ok := s.Rhs[0].(*ast.CallExpr); ok { + if sel, ok := call.Fun.(*ast.SelectorExpr); ok { + if x, ok := sel.X.(*ast.IndexExpr); ok { + // m[] -> (*m)[] + if sel2, ok := x.X.(*ast.SelectorExpr); ok { + if ident, ok := sel2.X.(*ast.Ident); ok && ident.Name == "m" { + x.X = &ast.StarExpr{X: &ast.Ident{Name: "m"}} + } + } + // len(m.Items) -> len(*m) + if bin, ok := x.Index.(*ast.BinaryExpr); ok { + if call2, ok := bin.X.(*ast.CallExpr); ok && len(call2.Args) == 1 { + if isFieldSelector(call2.Args[0], "m", "Items") { + call2.Args[0] = &ast.StarExpr{X: &ast.Ident{Name: "m"}} + } + } + } + } + } + } + } + } + case *ast.IndexExpr: + if isFieldSelector(t.X, "m", "Items") { + t.X = &ast.Ident{Name: "m"} + return nil + } + case *ast.CallExpr: + changed := false + for i := range t.Args { + if isFieldSelector(t.Args[i], "m", "Items") { + t.Args[i] = &ast.Ident{Name: "m"} + changed = true + } + } + if changed { + return nil + } + } + return v +} + +func isFieldSelector(n ast.Expr, name, field string) bool { + s, ok := n.(*ast.SelectorExpr) + if !ok || s.Sel == nil || (field != "" && s.Sel.Name != field) { + return false + } + return isIdent(s.X, name) +} + +func isIdent(n ast.Expr, value string) bool { + ident, ok := n.(*ast.Ident) + return ok && ident.Name == value +} + +func receiver(f *ast.FuncDecl) (ident *ast.Ident, pointer bool, ok bool) { + if f.Recv == nil || len(f.Recv.List) != 1 { + return nil, false, false + } + switch t := f.Recv.List[0].Type.(type) { + case *ast.StarExpr: + identity, ok := t.X.(*ast.Ident) + if !ok { + return nil, false, false + } + return identity, true, true + case *ast.Ident: + return t, false, true + } + return nil, false, false +} + +// dropExistingTypeDeclarations removes any type declaration for which extractFn returns true. The function +// returns true if the entire declaration should be dropped. +func dropExistingTypeDeclarations(decl ast.Decl, extractFn ExtractFunc) bool { + if t, ok := decl.(*ast.GenDecl); ok { + if t.Tok != token.TYPE { + return false + } + specs := []ast.Spec{} + for _, s := range t.Specs { + if spec, ok := s.(*ast.TypeSpec); ok { + if extractFn(spec) { + continue + } + specs = append(specs, spec) + } + } + if len(specs) == 0 { + return true + } + t.Specs = specs + } + return false +} + +// dropEmptyImportDeclarations strips any generated but no-op imports from the generated code +// to prevent generation from being able to define side-effects. The function returns true +// if the entire declaration should be dropped. +func dropEmptyImportDeclarations(decl ast.Decl) bool { + if t, ok := decl.(*ast.GenDecl); ok { + if t.Tok != token.IMPORT { + return false + } + specs := []ast.Spec{} + for _, s := range t.Specs { + if spec, ok := s.(*ast.ImportSpec); ok { + if spec.Name != nil && spec.Name.Name == "_" { + continue + } + specs = append(specs, spec) + } + } + if len(specs) == 0 { + return true + } + t.Specs = specs + } + return false +} + +func RewriteTypesWithProtobufStructTags(name string, structTags map[string]map[string]string) error { + return rewriteFile(name, []byte{}, func(fset *token.FileSet, file *ast.File) error { + allErrs := []error{} + + // set any new struct tags + for _, d := range file.Decls { + if errs := updateStructTags(d, structTags, []string{"protobuf"}); len(errs) > 0 { + allErrs = append(allErrs, errs...) + } + } + + if len(allErrs) > 0 { + var s string + for _, err := range allErrs { + s += err.Error() + "\n" + } + return errors.New(s) + } + return nil + }) +} + +func getFieldName(expr ast.Expr, structname string) (name string, err error) { + for { + switch t := expr.(type) { + case *ast.Ident: + return t.Name, nil + case *ast.SelectorExpr: + return t.Sel.Name, nil + case *ast.StarExpr: + expr = t.X + default: + return "", fmt.Errorf("unable to get name for tag from struct %q, field %#v", structname, t) + } + } +} + +func updateStructTags(decl ast.Decl, structTags map[string]map[string]string, toCopy []string) []error { + var errs []error + t, ok := decl.(*ast.GenDecl) + if !ok { + return nil + } + if t.Tok != token.TYPE { + return nil + } + + for _, s := range t.Specs { + spec, ok := s.(*ast.TypeSpec) + if !ok { + continue + } + typeName := spec.Name.Name + fieldTags, ok := structTags[typeName] + if !ok { + continue + } + st, ok := spec.Type.(*ast.StructType) + if !ok { + continue + } + + for i := range st.Fields.List { + f := st.Fields.List[i] + var name string + var err error + if len(f.Names) == 0 { + name, err = getFieldName(f.Type, spec.Name.Name) + if err != nil { + errs = append(errs, err) + continue + } + } else { + name = f.Names[0].Name + } + value, ok := fieldTags[name] + if !ok { + continue + } + var tags customreflect.StructTags + if f.Tag != nil { + oldTags, err := customreflect.ParseStructTags(strings.Trim(f.Tag.Value, "`")) + if err != nil { + errs = append(errs, fmt.Errorf("unable to read struct tag from struct %q, field %q: %v", spec.Name.Name, name, err)) + continue + } + tags = oldTags + } + for _, name := range toCopy { + // don't overwrite existing tags + if tags.Has(name) { + continue + } + // append new tags + if v := reflect.StructTag(value).Get(name); len(v) > 0 { + tags = append(tags, customreflect.StructTag{Name: name, Value: v}) + } + } + if len(tags) == 0 { + continue + } + if f.Tag == nil { + f.Tag = &ast.BasicLit{} + } + f.Tag.Value = tags.String() + } + } + return errs +} + +type replacePackageVisitor struct { + oldPackage string + newPackage string +} + +// Visit walks the provided node, transforming references to the old package to the new package. +func (v replacePackageVisitor) Visit(n ast.Node) ast.Visitor { + if e, ok := n.(*ast.SelectorExpr); ok { + if i, ok := e.X.(*ast.Ident); ok && i.Name == v.oldPackage { + i.Name = v.newPackage + } + return nil + } + return v +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser_test.go new file mode 100644 index 0000000000..db765a30c2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/parser_test.go @@ -0,0 +1,119 @@ +/* +Copyright 2020 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 protobuf + +import ( + "go/ast" + "testing" +) + +/* + struct fields in go AST: + + type Struct struct { + // fields with a direct field Name as + A X // regular fields + B *X // pointer fields + C // embedded type field + + // qualified embedded type fields use an in the AST + v1.TypeMeta // X=v1, Sel=TypeMeta + + // fields without a direct name, but + // a in the go-AST + *D // type field embedded as pointer + *v1.ListMeta // qualified type field embedded as pointer + // with pointing to + } +*/ + +func TestProtoParser(t *testing.T) { + ident := ast.NewIdent("FieldName") + tests := []struct { + expr ast.Expr + err bool + }{ + // valid struct field expressions + { + expr: ident, + err: false, + }, + { + expr: &ast.SelectorExpr{ + Sel: ident, + }, + err: false, + }, + { + expr: &ast.StarExpr{ + X: ident, + }, + err: false, + }, + { + expr: &ast.StarExpr{ + X: &ast.StarExpr{ + X: ident, + }, + }, + err: false, + }, + { + expr: &ast.StarExpr{ + X: &ast.SelectorExpr{ + Sel: ident, + }, + }, + err: false, + }, + + // something else should provide an error + { + expr: &ast.KeyValueExpr{ + Key: ident, + Colon: 0, + Value: ident, + }, + err: true, + }, + { + expr: &ast.StarExpr{ + X: &ast.KeyValueExpr{ + Key: ident, + Colon: 0, + Value: ident, + }, + }, + err: true, + }, + } + + for _, test := range tests { + actual, err := getFieldName(test.expr, "Struct") + if !test.err { + if err != nil { + t.Errorf("%s: unexpected error %s", test.expr, err) + } else if actual != ident.Name { + t.Errorf("%s: expected %s, got %s", test.expr, ident.Name, actual) + } + } else { + if err == nil { + t.Errorf("%s: expected error did not occur, got %s instead", test.expr, actual) + } + } + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/tags.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/tags.go new file mode 100644 index 0000000000..44ca07d1a8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protobuf/tags.go @@ -0,0 +1,33 @@ +/* +Copyright 2016 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 protobuf + +import ( + "k8s.io/gengo/v2" + "k8s.io/klog/v2" +) + +// extractBoolTagOrDie gets the comment-tags for the key and asserts that, if +// it exists, the value is boolean. If the tag did not exist, it returns +// false. +func extractBoolTagOrDie(key string, lines []string) bool { + val, err := gengo.ExtractSingleBoolCommentTag("+", key, false, lines) + if err != nil { + klog.Fatal(err) + } + return val +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protoc-gen-gogo/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protoc-gen-gogo/main.go new file mode 100644 index 0000000000..a08512bce3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/go-to-protobuf/protoc-gen-gogo/main.go @@ -0,0 +1,51 @@ +/* +Copyright 2015 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 main defines the protoc-gen-gogo binary we use to generate our proto go files, +// as well as takes dependencies on the correct gogo/protobuf packages for godeps. +package main + +import ( + "strings" + + "github.com/gogo/protobuf/vanity/command" + + // dependencies that are required for our packages + _ "github.com/gogo/protobuf/gogoproto" + _ "github.com/gogo/protobuf/proto" + _ "github.com/gogo/protobuf/sortkeys" +) + +func main() { + // read input + request := command.Read() + + // if we're given paths as inputs, generate .pb.go files based on those paths + for _, file := range request.FileToGenerate { + if strings.Contains(file, "/") { + if request.Parameter != nil { + *request.Parameter += ",paths=source_relative" + } else { + param := "paths=source_relative" + request.Parameter = ¶m + } + break + } + } + + // generate + command.Write(command.Generate(request)) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/args/args.go new file mode 100644 index 0000000000..a3b6dd9079 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/args/args.go @@ -0,0 +1,90 @@ +/* +Copyright 2017 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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/pkg/apidefinitions" +) + +// Args is used by the gengo framework to pass args specific to this generator. +type Args struct { + OutputDir string // must be a directory path + OutputPkg string // must be a Go import-path + GoHeaderFile string + VersionedClientSetPackage string // must be a Go import-path + InternalClientSetPackage string // must be a Go import-path + ListersPackage string // must be a Go import-path + SingleDirectory bool + + // PluralExceptions define a list of pluralizer exceptions in Type:PluralType format. + // The default list is "Endpoints:Endpoints" + PluralExceptions []string + + apidefinitions.LintArgs +} + +// New returns default arguments for the generator. +func New() *Args { + return &Args{ + SingleDirectory: false, + } +} + +// AddFlags add the generator flags to the flag set. +func (args *Args) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&args.OutputDir, "output-dir", "", + "the base directory under which to generate results") + fs.StringVar(&args.OutputPkg, "output-pkg", args.OutputPkg, + "the Go import-path of the generated results") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + fs.StringVar(&args.InternalClientSetPackage, "internal-clientset-package", args.InternalClientSetPackage, + "the Go import-path of the internal clientset to use") + fs.StringVar(&args.VersionedClientSetPackage, "versioned-clientset-package", args.VersionedClientSetPackage, + "the Go import-path of the versioned clientset to use") + fs.StringVar(&args.ListersPackage, "listers-package", args.ListersPackage, + "the Go import-path of the listers to use") + fs.BoolVar(&args.SingleDirectory, "single-directory", args.SingleDirectory, + "if true, omit the intermediate \"internalversion\" and \"externalversions\" subdirectories") + fs.StringSliceVar(&args.PluralExceptions, "plural-exceptions", args.PluralExceptions, + "list of comma separated plural exception definitions in Type:PluralizedType format") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputDir) == 0 { + return fmt.Errorf("--output-dir must be specified") + } + if len(args.OutputPkg) == 0 { + return fmt.Errorf("--output-pkg must be specified") + } + if len(args.VersionedClientSetPackage) == 0 { + return fmt.Errorf("--versioned-clientset-package must be specified") + } + if len(args.ListersPackage) == 0 { + return fmt.Errorf("--listers-package must be specified") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/factory.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/factory.go new file mode 100644 index 0000000000..b8157d36b0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/factory.go @@ -0,0 +1,419 @@ +/* +Copyright 2016 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 generators + +import ( + "io" + "path" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/klog/v2" +) + +// factoryGenerator produces a file of listers for a given GroupVersion and +// type. +type factoryGenerator struct { + generator.GoGenerator + outputPackage string + imports namer.ImportTracker + groupVersions map[string]clientgentypes.GroupVersions + gvGoNames map[string]string + clientSetPackage string + internalInterfacesPackage string + filtered bool +} + +var _ generator.Generator = &factoryGenerator{} + +func (g *factoryGenerator) Filter(c *generator.Context, t *types.Type) bool { + if !g.filtered { + g.filtered = true + return true + } + return false +} + +func (g *factoryGenerator) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *factoryGenerator) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + return +} + +func (g *factoryGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "{{", "}}") + + klog.V(5).Infof("processing type %v", t) + + gvInterfaces := make(map[string]*types.Type) + gvNewFuncs := make(map[string]*types.Type) + for groupPkgName := range g.groupVersions { + gvInterfaces[groupPkgName] = c.Universe.Type(types.Name{Package: path.Join(g.outputPackage, groupPkgName), Name: "Interface"}) + gvNewFuncs[groupPkgName] = c.Universe.Function(types.Name{Package: path.Join(g.outputPackage, groupPkgName), Name: "New"}) + } + m := map[string]interface{}{ + "cacheDoneChecker": c.Universe.Type(cacheDoneChecker), + "cacheInformerName": c.Universe.Type(cacheInformerName), + "cacheSharedIndexInformer": c.Universe.Type(cacheSharedIndexInformer), + "cacheSyncResult": c.Universe.Type(cacheSyncResult), + "cacheTransformFunc": c.Universe.Type(cacheTransformFunc), + "cacheWaitFor": c.Universe.Function(cacheWaitForFunc), + "contextContext": c.Universe.Type(contextContext), + "contextCause": c.Universe.Function(contextCauseFunc), + "fmtErrorf": c.Universe.Function(fmtErrorfFunc), + "groupVersions": g.groupVersions, + "gvInterfaces": gvInterfaces, + "gvNewFuncs": gvNewFuncs, + "gvGoNames": g.gvGoNames, + "interfacesNewInformerFunc": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "NewInformerFunc"}), + "interfacesTweakListOptionsFunc": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "TweakListOptionsFunc"}), + "informerFactoryInterface": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "SharedInformerFactory"}), + "clientSetInterface": c.Universe.Type(types.Name{Package: g.clientSetPackage, Name: "Interface"}), + "reflectType": c.Universe.Type(reflectType), + "runtimeObject": c.Universe.Type(runtimeObject), + "schemaGroupVersionResource": c.Universe.Type(schemaGroupVersionResource), + "stringsBuilder": c.Universe.Type(stringsBuilder), + "syncMutex": c.Universe.Type(syncMutex), + "timeDuration": c.Universe.Type(timeDuration), + "namespaceAll": c.Universe.Type(metav1NamespaceAll), + "object": c.Universe.Type(metav1Object), + "waitContextForChannel": c.Universe.Function(waitContextForChannelFunc), + } + + sw.Do(sharedInformerFactoryStruct, m) + sw.Do(sharedInformerFactoryInterface, m) + + return sw.Error() +} + +var sharedInformerFactoryStruct = ` +// SharedInformerOption defines the functional option type for SharedInformerFactory. +type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory + +type sharedInformerFactory struct { + client {{.clientSetInterface|raw}} + namespace string + tweakListOptions {{.interfacesTweakListOptionsFunc|raw}} + lock {{.syncMutex|raw}} + defaultResync {{.timeDuration|raw}} + customResync map[{{.reflectType|raw}}]{{.timeDuration|raw}} + transform {{.cacheTransformFunc|raw}} + informerName *{{.cacheInformerName|raw}} + + informers map[{{.reflectType|raw}}]{{.cacheSharedIndexInformer|raw}} + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[{{.reflectType|raw}}]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool +} + +// WithCustomResyncConfig sets a custom resync period for the specified informer types. +func WithCustomResyncConfig(resyncConfig map[{{.object|raw}}]{{.timeDuration|raw}}) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + for k, v := range resyncConfig { + factory.customResync[reflect.TypeOf(k)] = v + } + return factory + } +} + +// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. +func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.tweakListOptions = tweakListOptions + return factory + } +} + +// WithNamespace limits the SharedInformerFactory to the specified namespace. +func WithNamespace(namespace string) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.namespace = namespace + return factory + } +} + +// WithTransform sets a transform on all informers. +func WithTransform(transform {{.cacheTransformFunc|raw}}) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.transform = transform + return factory + } +} + +// WithInformerName sets the InformerName for informer identity used in metrics. +// The InformerName must be created via cache.NewInformerName() at startup, +// which validates global uniqueness. Each informer type will register its +// GVR under this name. +func WithInformerName(informerName *{{.cacheInformerName|raw}}) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.informerName = informerName + return factory + } +} + +func (f *sharedInformerFactory) InformerName() *{{.cacheInformerName|raw}} { + return f.informerName +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. +func NewSharedInformerFactory(client {{.clientSetInterface|raw}}, defaultResync {{.timeDuration|raw}}) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +// +// Deprecated: Please use NewSharedInformerFactoryWithOptions instead +func NewFilteredSharedInformerFactory(client {{.clientSetInterface|raw}}, defaultResync {{.timeDuration|raw}}, namespace string, tweakListOptions {{.interfacesTweakListOptionsFunc|raw}}) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) +} + +// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. +func NewSharedInformerFactoryWithOptions(client {{.clientSetInterface|raw}}, defaultResync {{.timeDuration|raw}}, options ...SharedInformerOption) SharedInformerFactory { + factory := &sharedInformerFactory{ + client: client, + namespace: v1.NamespaceAll, + defaultResync: defaultResync, + informers: make(map[{{.reflectType|raw}}]{{.cacheSharedIndexInformer|raw}}), + startedInformers: make(map[{{.reflectType|raw}}]bool), + customResync: make(map[{{.reflectType|raw}}]{{.timeDuration|raw}}), + } + + // Apply all options + for _, opt := range options { + factory = opt(factory) + } + + return factory +} + +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.StartWithContext({{.waitContextForChannel|raw}}(stopCh)) +} + +func (f *sharedInformerFactory) StartWithContext(ctx {{.contextContext|raw}}) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.shuttingDown { + return + } + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + f.wg.Go(func() { + informer.RunWithContext(ctx) + }) + f.startedInformers[informerType] = true + } + } +} + +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() + f.informerName.Release() +} + +func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + result := f.WaitForCacheSyncWithContext(wait.ContextForChannel(stopCh)) + return result.Synced +} + +func (f *sharedInformerFactory) WaitForCacheSyncWithContext(ctx context.Context) {{.cacheSyncResult|raw}} { + informers := func() map[{{.reflectType|raw}}]{{.cacheSharedIndexInformer|raw}} { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[{{.reflectType|raw}}]{{.cacheSharedIndexInformer|raw}}{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + // Wait for informers to sync, without polling. + cacheSyncs := make([]{{.cacheDoneChecker|raw}}, 0, len(informers)) + for _, informer := range informers { + cacheSyncs = append(cacheSyncs, informer.HasSyncedChecker()) + } + {{.cacheWaitFor|raw}}(ctx, "" /* no logging */, cacheSyncs...) + + res := {{.cacheSyncResult|raw}} { + Synced: make(map[{{.reflectType|raw}}]bool, len(informers)), + } + failed := false + for informType, informer := range informers { + hasSynced := informer.HasSynced() + if !hasSynced { + failed = true + } + res.Synced[informType] = hasSynced + } + if failed { + // context.Cause is more informative than ctx.Err(). + // This must be non-nil, otherwise WaitFor wouldn't have stopped + // prematurely. + res.Err = {{.contextCause|raw}}(ctx) + } + + return res +} + +// InformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj {{.runtimeObject|raw}}, newFunc {{.interfacesNewInformerFunc|raw}}) {{.cacheSharedIndexInformer|raw}} { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + + resyncPeriod, exists := f.customResync[informerType] + if !exists { + resyncPeriod = f.defaultResync + } + + informer = newFunc(f.client, resyncPeriod) + if f.transform != nil { + informer.SetTransform(f.transform) + } + f.informers[informerType] = informer + + return informer +} +` + +var sharedInformerFactoryInterface = ` +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.Shutdown() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// handle, err := typeInformer.Informer().AddEventHandler(...) +// if err != nil { +// return fmt.Errorf("register event handler: %v", err) +// } +// defer typeInformer.Informer().RemoveEventHandler(handle) // Avoids leaking goroutines. +// factory.StartWithContext(ctx) // Start processing these informers. +// synced := factory.WaitForCacheSyncWithContext(ctx) +// if err := synced.AsError(); err != nil { +// return err +// } +// for v := range synced { +// // Only if desired log some information similar to this. +// fmt.Fprintf(os.Stdout, "cache synced: %s", v) +// } +// +// // Also make sure that all of the initial cache events have been delivered. +// if !WaitFor(ctx, "event handler sync", handle.HasSyncedChecker()) { +// // Must have failed because of context. +// return fmt.Errorf("sync event handler: %w", context.Cause(ctx)) +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.StartWithContext(ctx) +type SharedInformerFactory interface { + {{.informerFactoryInterface|raw}} + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + // + // Contextual logging: StartWithContext should be used instead of Start in code which supports contextual logging. + Start(stopCh <-chan struct{}) + + // StartWithContext initializes all requested informers. They are handled in goroutines + // which run until the context gets canceled. + // Warning: StartWithContext does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + StartWithContext(ctx context.Context) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. + // + // Contextual logging: WaitForCacheSyncWithContext should be used instead of WaitForCacheSync in code which supports contextual logging. It also returns a more useful result. + WaitForCacheSync(stopCh <-chan struct{}) map[{{.reflectType|raw}}]bool + + // WaitForCacheSyncWithContext blocks until all started informers' caches were synced + // or the context gets canceled. + WaitForCacheSyncWithContext(ctx {{.contextContext|raw}}) {{.cacheSyncResult|raw}} + + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource {{.schemaGroupVersionResource|raw}}) (GenericInformer, error) + + // InformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj {{.runtimeObject|raw}}, newFunc {{.interfacesNewInformerFunc|raw}}) {{.cacheSharedIndexInformer|raw}} + + {{$gvInterfaces := .gvInterfaces}} + {{$gvGoNames := .gvGoNames}} + {{range $groupName, $group := .groupVersions}}{{index $gvGoNames $groupName}}() {{index $gvInterfaces $groupName|raw}} + {{end}} +} + +{{$gvNewFuncs := .gvNewFuncs}} +{{$gvGoNames := .gvGoNames}} +{{range $groupPkgName, $group := .groupVersions}} +func (f *sharedInformerFactory) {{index $gvGoNames $groupPkgName}}() {{index $gvInterfaces $groupPkgName|raw}} { + return {{index $gvNewFuncs $groupPkgName|raw}}(f, f.namespace, f.tweakListOptions) +} +{{end}} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/factoryinterface.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/factoryinterface.go new file mode 100644 index 0000000000..e59b8bcc5f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/factoryinterface.go @@ -0,0 +1,111 @@ +/* +Copyright 2016 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 generators + +import ( + "io" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/klog/v2" +) + +// factoryInterfaceGenerator produces a file of interfaces used to break a dependency cycle for +// informer registration +type factoryInterfaceGenerator struct { + generator.GoGenerator + outputPackage string + imports namer.ImportTracker + clientSetPackage string + filtered bool +} + +var _ generator.Generator = &factoryInterfaceGenerator{} + +func (g *factoryInterfaceGenerator) Filter(c *generator.Context, t *types.Type) bool { + if !g.filtered { + g.filtered = true + return true + } + return false +} + +func (g *factoryInterfaceGenerator) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *factoryInterfaceGenerator) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + return +} + +func (g *factoryInterfaceGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "{{", "}}") + + klog.V(5).Infof("processing type %v", t) + + m := map[string]interface{}{ + "cacheIndexers": c.Universe.Type(cacheIndexers), + "cacheInformerName": c.Universe.Type(cacheInformerName), + "cacheSharedIndexInformer": c.Universe.Type(cacheSharedIndexInformer), + "clientSetPackage": c.Universe.Type(types.Name{Package: g.clientSetPackage, Name: "Interface"}), + "runtimeObject": c.Universe.Type(runtimeObject), + "timeDuration": c.Universe.Type(timeDuration), + "v1ListOptions": c.Universe.Type(v1ListOptions), + } + + sw.Do(externalSharedInformerFactoryInterface, m) + + return sw.Error() +} + +var externalSharedInformerFactoryInterface = ` +// NewInformerFunc takes {{.clientSetPackage|raw}} and {{.timeDuration|raw}} to return a SharedIndexInformer. +type NewInformerFunc func({{.clientSetPackage|raw}}, {{.timeDuration|raw}}) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj {{.runtimeObject|raw}}, newFunc NewInformerFunc) {{.cacheSharedIndexInformer|raw}} + InformerName() *{{.cacheInformerName|raw}} +} + +// TweakListOptionsFunc is a function that transforms a {{.v1ListOptions|raw}}. +type TweakListOptionsFunc func(*{{.v1ListOptions|raw}}) + +// InformerOptions holds the options for creating an informer. +type InformerOptions struct { + // ResyncPeriod is the resync period for this informer. + // If not set, defaults to 0 (no resync). + ResyncPeriod {{.timeDuration|raw}} + + // Indexers are the indexers for this informer. + Indexers {{.cacheIndexers|raw}} + + // InformerName is used to uniquely identify this informer for metrics. + // If not set, metrics will not be published for this informer. + // Use cache.NewInformerName() to create an InformerName at startup. + InformerName *{{.cacheInformerName|raw}} + + // TweakListOptions is an optional function to modify the list options. + TweakListOptions TweakListOptionsFunc +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/generic.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/generic.go new file mode 100644 index 0000000000..e863b1d2fd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/generic.go @@ -0,0 +1,184 @@ +/* +Copyright 2016 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 generators + +import ( + "io" + "sort" + "strings" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + codegennamer "k8s.io/code-generator/pkg/namer" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +// genericGenerator generates the generic informer. +type genericGenerator struct { + generator.GoGenerator + outputPackage string + imports namer.ImportTracker + groupVersions map[string]clientgentypes.GroupVersions + groupGoNames map[string]string + pluralExceptions map[string]string + typesForGroupVersion map[clientgentypes.GroupVersion][]*types.Type + filtered bool +} + +var _ generator.Generator = &genericGenerator{} + +func (g *genericGenerator) Filter(c *generator.Context, t *types.Type) bool { + if !g.filtered { + g.filtered = true + return true + } + return false +} + +func (g *genericGenerator) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + "allLowercasePlural": namer.NewAllLowercasePluralNamer(g.pluralExceptions), + "publicPlural": namer.NewPublicPluralNamer(g.pluralExceptions), + "resource": codegennamer.NewTagOverrideNamer("resourceName", namer.NewAllLowercasePluralNamer(g.pluralExceptions)), + } +} + +func (g *genericGenerator) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + return +} + +type group struct { + GroupGoName string + Name string + Versions []*version +} + +type groupSort []group + +func (g groupSort) Len() int { return len(g) } +func (g groupSort) Less(i, j int) bool { + return strings.ToLower(g[i].Name) < strings.ToLower(g[j].Name) +} +func (g groupSort) Swap(i, j int) { g[i], g[j] = g[j], g[i] } + +type version struct { + Name string + GoName string + Resources []*types.Type +} + +type versionSort []*version + +func (v versionSort) Len() int { return len(v) } +func (v versionSort) Less(i, j int) bool { + return strings.ToLower(v[i].Name) < strings.ToLower(v[j].Name) +} +func (v versionSort) Swap(i, j int) { v[i], v[j] = v[j], v[i] } + +func (g *genericGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "{{", "}}") + + groups := []group{} + schemeGVs := make(map[*version]*types.Type) + + orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)} + for groupPackageName, groupVersions := range g.groupVersions { + group := group{ + GroupGoName: g.groupGoNames[groupPackageName], + Name: groupVersions.Group.NonEmpty(), + Versions: []*version{}, + } + for _, v := range groupVersions.Versions { + gv := clientgentypes.GroupVersion{Group: groupVersions.Group, Version: v.Version} + version := &version{ + Name: v.Version.NonEmpty(), + GoName: namer.IC(v.Version.NonEmpty()), + Resources: orderer.OrderTypes(g.typesForGroupVersion[gv]), + } + func() { + schemeGVs[version] = c.Universe.Variable(types.Name{Package: g.typesForGroupVersion[gv][0].Name.Package, Name: "SchemeGroupVersion"}) + }() + group.Versions = append(group.Versions, version) + } + sort.Sort(versionSort(group.Versions)) + groups = append(groups, group) + } + sort.Sort(groupSort(groups)) + + m := map[string]interface{}{ + "cacheGenericLister": c.Universe.Type(cacheGenericLister), + "cacheNewGenericLister": c.Universe.Function(cacheNewGenericLister), + "cacheSharedIndexInformer": c.Universe.Type(cacheSharedIndexInformer), + "fmtErrorf": c.Universe.Type(fmtErrorfFunc), + "groups": groups, + "schemeGVs": schemeGVs, + "schemaGroupResource": c.Universe.Type(schemaGroupResource), + "schemaGroupVersionResource": c.Universe.Type(schemaGroupVersionResource), + } + + sw.Do(genericInformer, m) + sw.Do(forResource, m) + + return sw.Error() +} + +var genericInformer = ` +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() {{.cacheSharedIndexInformer|raw}} + Lister() {{.cacheGenericLister|raw}} +} + +type genericInformer struct { + informer {{.cacheSharedIndexInformer|raw}} + resource {{.schemaGroupResource|raw}} +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() {{.cacheSharedIndexInformer|raw}} { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() {{.cacheGenericLister|raw}} { + return {{.cacheNewGenericLister|raw}}(f.Informer().GetIndexer(), f.resource) +} +` + +var forResource = ` +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource {{.schemaGroupVersionResource|raw}}) (GenericInformer, error) { + switch resource { + {{range $group := .groups -}}{{$GroupGoName := .GroupGoName -}} + {{range $version := .Versions -}} + // Group={{$group.Name}}, Version={{.Name}} + {{range .Resources -}} + case {{index $.schemeGVs $version|raw}}.WithResource("{{.|resource}}"): + return &genericInformer{resource: resource.GroupResource(), informer: f.{{$GroupGoName}}().{{$version.GoName}}().{{.|publicPlural}}().Informer()}, nil + {{end}} + {{end}} + {{end -}} + } + + return nil, {{.fmtErrorf|raw}}("no informer found for %v", resource) +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/groupinterface.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/groupinterface.go new file mode 100644 index 0000000000..5342e25d97 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/groupinterface.go @@ -0,0 +1,118 @@ +/* +Copyright 2016 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 generators + +import ( + "io" + "path" + "strings" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +// groupInterfaceGenerator generates the per-group interface file. +type groupInterfaceGenerator struct { + generator.GoGenerator + outputPackage string + imports namer.ImportTracker + groupVersions clientgentypes.GroupVersions + filtered bool + internalInterfacesPackage string +} + +var _ generator.Generator = &groupInterfaceGenerator{} + +func (g *groupInterfaceGenerator) Filter(c *generator.Context, t *types.Type) bool { + if !g.filtered { + g.filtered = true + return true + } + return false +} + +func (g *groupInterfaceGenerator) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *groupInterfaceGenerator) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + return +} + +type versionData struct { + Name string + Interface *types.Type + New *types.Type +} + +func (g *groupInterfaceGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + versions := make([]versionData, 0, len(g.groupVersions.Versions)) + for _, version := range g.groupVersions.Versions { + gv := clientgentypes.GroupVersion{Group: g.groupVersions.Group, Version: version.Version} + versionPackage := path.Join(g.outputPackage, strings.ToLower(gv.Version.NonEmpty())) + iface := c.Universe.Type(types.Name{Package: versionPackage, Name: "Interface"}) + versions = append(versions, versionData{ + Name: namer.IC(version.Version.NonEmpty()), + Interface: iface, + New: c.Universe.Function(types.Name{Package: versionPackage, Name: "New"}), + }) + } + m := map[string]interface{}{ + "interfacesTweakListOptionsFunc": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "TweakListOptionsFunc"}), + "interfacesSharedInformerFactory": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "SharedInformerFactory"}), + "versions": versions, + } + + sw.Do(groupTemplate, m) + + return sw.Error() +} + +var groupTemplate = ` +// Interface provides access to each of this group's versions. +type Interface interface { + $range .versions -$ + // $.Name$ provides access to shared informers for resources in $.Name$. + $.Name$() $.Interface|raw$ + $end$ +} + +type group struct { + factory $.interfacesSharedInformerFactory|raw$ + namespace string + tweakListOptions $.interfacesTweakListOptionsFunc|raw$ +} + +// New returns a new Interface. +func New(f $.interfacesSharedInformerFactory|raw$, namespace string, tweakListOptions $.interfacesTweakListOptionsFunc|raw$) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +$range .versions$ +// $.Name$ returns a new $.Interface|raw$. +func (g *group) $.Name$() $.Interface|raw$ { + return $.New|raw$(g.factory, g.namespace, g.tweakListOptions) +} +$end$ +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/informer.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/informer.go new file mode 100644 index 0000000000..4a95162d33 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/informer.go @@ -0,0 +1,331 @@ +/* +Copyright 2016 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 generators + +import ( + "fmt" + "io" + "strings" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/code-generator/cmd/client-gen/generators/util" + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + + "k8s.io/klog/v2" +) + +// informerGenerator produces a file of listers for a given GroupVersion and +// type. +type informerGenerator struct { + generator.GoGenerator + outputPackage string + groupPkgName string + groupVersion clientgentypes.GroupVersion + groupGoName string + typeToGenerate *types.Type + imports namer.ImportTracker + clientSetPackage string + listersPackage string + internalInterfacesPackage string +} + +var _ generator.Generator = &informerGenerator{} + +func (g *informerGenerator) Filter(c *generator.Context, t *types.Type) bool { + return t == g.typeToGenerate +} + +func (g *informerGenerator) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *informerGenerator) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + return +} + +func (g *informerGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + klog.V(5).Infof("processing type %v", t) + + listerPackage := fmt.Sprintf("%s/%s/%s", g.listersPackage, g.groupPkgName, strings.ToLower(g.groupVersion.Version.NonEmpty())) + clientSetInterface := c.Universe.Type(types.Name{Package: g.clientSetPackage, Name: "Interface"}) + informerFor := "InformerFor" + + tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + if err != nil { + return err + } + + m := map[string]interface{}{ + "apiScheme": c.Universe.Type(apiScheme), + "cacheDeletedObject": c.Universe.Type(cacheDeletedObject), + "cacheIndexers": c.Universe.Type(cacheIndexers), + "cacheListWatch": c.Universe.Type(cacheListWatch), + "cacheMetaNamespaceIndexFunc": c.Universe.Function(cacheMetaNamespaceIndexFunc), + "cacheNamespaceIndex": c.Universe.Variable(cacheNamespaceIndex), + "cacheNewSharedIndexInformer": c.Universe.Function(cacheNewSharedIndexInformer), + "cacheNewTypedSharedIndexInformer": c.Universe.Function(cacheNewTypedSharedIndexInformer), + "cacheNewSharedIndexInformerWithOptions": c.Universe.Function(cacheNewSharedIndexInformerWithOptions), + "cacheSharedIndexInformer": c.Universe.Type(cacheSharedIndexInformer), + "cacheTypedFilteringResourceEventHandler": c.Universe.Type(cacheTypedFilteringResourceEventHandler), + "cacheTypedResourceEventHandlerDetailedFuncs": c.Universe.Type(cacheTypedResourceEventHandlerDetailedFuncs), + "cacheTypedResourceEventHandlerFuncs": c.Universe.Type(cacheTypedResourceEventHandlerFuncs), + "cacheTypedIndexers": c.Universe.Type(cacheTypedIndexers), + "cacheTypedIndexersToIndexers": c.Universe.Type(cacheTypedIndexersToIndexers), + "cacheTypedSharedIndexInformer": c.Universe.Type(cacheTypedSharedIndexInformer), + "cacheSharedIndexInformerOptions": c.Universe.Type(cacheSharedIndexInformerOptions), + "cacheToListWatcherWithWatchListSemantics": c.Universe.Function(cacheToListWatcherWithWatchListSemanticsFunc), + "cacheInformerName": c.Universe.Type(cacheInformerName), + "clientSetInterface": clientSetInterface, + "contextContext": c.Universe.Type(contextContext), + "contextBackground": c.Universe.Function(contextBackgroundFunc), + "group": namer.IC(g.groupGoName), + "groupName": g.groupVersion.Group.String(), + "informerFor": informerFor, + "interfacesInformerOptions": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "InformerOptions"}), + "interfacesTweakListOptionsFunc": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "TweakListOptionsFunc"}), + "interfacesSharedInformerFactory": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "SharedInformerFactory"}), + "listOptions": c.Universe.Type(listOptions), + "lister": c.Universe.Type(types.Name{Package: listerPackage, Name: t.Name.Name + "Lister"}), + "namespaceAll": c.Universe.Type(metav1NamespaceAll), + "namespaced": !tags.NonNamespaced, + "newLister": c.Universe.Function(types.Name{Package: listerPackage, Name: "New" + t.Name.Name + "Lister"}), + "resourceName": strings.ToLower(t.Name.Name) + "s", + "runtimeObject": c.Universe.Type(runtimeObject), + "schemaGroupVersionResource": c.Universe.Type(schemaGroupVersionResource), + "timeDuration": c.Universe.Type(timeDuration), + "type": t, + "v1ListOptions": c.Universe.Type(v1ListOptions), + "version": namer.IC(g.groupVersion.Version.String()), + "versionName": g.groupVersion.Version.String(), + "watchInterface": c.Universe.Type(watchInterface), + } + + sw.Do(typeInformerInterface, m) + sw.Do(typeInformerStruct, m) + sw.Do(typeInformerPublicConstructor, m) + sw.Do(typeFilteredInformerPublicConstructor, m) + sw.Do(typeInformerPublicConstructorWithOptions, m) + sw.Do(typeInformerConstructor, m) + sw.Do(typeInformerInformer, m) + sw.Do(typeInformerLister, m) + sw.Do(typeInformerToTypedInformer, m) + sw.Do(typeInformerToIndexInformer, m) + + return sw.Error() +} + +var typeInformerInterface = ` +// $.type|public$Informer provides access to a shared informer and lister for +// $.type|publicPlural$. Prefer using the type-safe variant (see [Typed$.type|public$Informer]). +type $.type|public$Informer interface { + Informer() $.cacheSharedIndexInformer|raw$ + Lister() $.lister|raw$ +} + +// Typed$.type|public$Informer provides access to a shared informer and lister for +// $.type|publicPlural$, including the type-safe TypedInformer variant. +// It is a superset of $.type|public$Informer. +type Typed$.type|public$Informer interface { + Informer() $.cacheSharedIndexInformer|raw$ + TypedInformer() $.type|public$IndexInformer + Lister() $.lister|raw$ +} + +// $.type|public$IndexInformer is a wrapper around the underlying [$.cacheSharedIndexInformer|raw$] +// with type-safe variants of several methods. +type $.type|public$IndexInformer $.cacheTypedSharedIndexInformer|raw$[*$.type|raw$] + +// $.type|public$HandlerFuncs is a specialization of [$.cacheTypedResourceEventHandlerFuncs|raw$] for $.type|public$. +type $.type|public$HandlerFuncs = $.cacheTypedResourceEventHandlerFuncs|raw$[*$.type|raw$] + +// $.type|public$DetailedHandlerFuncs is a specialization of [$.cacheTypedResourceEventHandlerDetailedFuncs|raw$] for $.type|public$. +type $.type|public$DetailedHandlerFuncs = $.cacheTypedResourceEventHandlerDetailedFuncs|raw$[*$.type|raw$] + +// $.type|public$FilteringHandler is a specialization of [$.cacheTypedFilteringResourceEventHandler|raw$] for $.type|public$. +type $.type|public$FilteringHandler = $.cacheTypedFilteringResourceEventHandler|raw$[*$.type|raw$] + +// $.type|public$Indexers is a specialization of [$.cacheTypedIndexers|raw$] for $.type|public$. +type $.type|public$Indexers = $.cacheTypedIndexers|raw$[*$.type|raw$] + +// Deleted$.type|public$ is a specialization of [$.cacheDeletedObject|raw$] for $.type|public$. +type Deleted$.type|public$ = $.cacheDeletedObject|raw$[*$.type|raw$] +` + +var typeInformerStruct = ` +type $.type|private$Informer struct { + factory $.interfacesSharedInformerFactory|raw$ + tweakListOptions $.interfacesTweakListOptionsFunc|raw$ + $if .namespaced$namespace string$end$ +} +` + +var typeInformerPublicConstructor = ` +// New$.type|public$Informer constructs a new informer for $.type|public$ type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +// If you really need an independent one, prefer using the type-safe variant (see [NewTyped$.type|public$Informer]). +func New$.type|public$Informer(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, resyncPeriod $.timeDuration|raw$, indexers $.cacheIndexers|raw$) $.cacheSharedIndexInformer|raw$ { + return New$.type|public$InformerWithOptions(client$if .namespaced$, namespace$end$, $.interfacesInformerOptions|raw${ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewTyped$.type|public$Informer constructs a new informer for $.type|public$ type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTyped$.type|public$Informer(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, resyncPeriod $.timeDuration|raw$, indexers $.type|public$Indexers) $.type|public$IndexInformer { + return NewTyped$.type|public$InformerWithOptions(client$if .namespaced$, namespace$end$, $.interfacesInformerOptions|raw${ResyncPeriod: resyncPeriod, Indexers: $.cacheTypedIndexersToIndexers|raw$(indexers)}) +} +` + +var typeFilteredInformerPublicConstructor = ` +// NewFiltered$.type|public$Informer constructs a new informer for $.type|public$ type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +// If you really need an independent one, prefer using the type-safe variant (see [NewTypedFiltered$.type|public$Informer]). +func NewFiltered$.type|public$Informer(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, resyncPeriod $.timeDuration|raw$, indexers $.cacheIndexers|raw$, tweakListOptions $.interfacesTweakListOptionsFunc|raw$) $.cacheSharedIndexInformer|raw$ { + return NewTyped$.type|public$InformerWithOptions(client$if .namespaced$, namespace$end$, $.interfacesInformerOptions|raw${ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewTypedFiltered$.type|public$Informer constructs a new informer for $.type|public$ type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTypedFiltered$.type|public$Informer(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, resyncPeriod $.timeDuration|raw$, indexers $.type|public$Indexers, tweakListOptions $.interfacesTweakListOptionsFunc|raw$) $.type|public$IndexInformer { + return NewTyped$.type|public$InformerWithOptions(client$if .namespaced$, namespace$end$, $.interfacesInformerOptions|raw${ResyncPeriod: resyncPeriod, Indexers: $.cacheTypedIndexersToIndexers|raw$(indexers), TweakListOptions: tweakListOptions}) +} +` + +var typeInformerPublicConstructorWithOptions = ` +// New$.type|public$InformerWithOptions constructs a new informer for $.type|public$ type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +// If you really need an independent one, prefer using the type-safe variant (see [NewTyped$.type|public$InformerWithOptions]). +func New$.type|public$InformerWithOptions(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, options $.interfacesInformerOptions|raw$) $.cacheSharedIndexInformer|raw$ { + return NewTyped$.type|public$InformerWithOptions(client$if .namespaced$, namespace$end$, options) +} + +// NewTyped$.type|public$InformerWithOptions constructs a new informer for $.type|public$ type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTyped$.type|public$InformerWithOptions(client $.clientSetInterface|raw$$if .namespaced$, namespace string$end$, options $.interfacesInformerOptions|raw$) $.type|public$IndexInformer { + gvr := $.schemaGroupVersionResource|raw${Group: "$.groupName$", Version: "$.versionName$", Resource: "$.resourceName$"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return $.cacheNewTypedSharedIndexInformer|raw$[*$.type|raw$]($.cacheNewSharedIndexInformerWithOptions|raw$( + $.cacheToListWatcherWithWatchListSemantics|raw$(&$.cacheListWatch|raw${ + ListFunc: func(opts $.v1ListOptions|raw$) ($.runtimeObject|raw$, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).List($.contextBackground|raw$(), opts) + }, + WatchFunc: func(opts $.v1ListOptions|raw$) ($.watchInterface|raw$, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).Watch($.contextBackground|raw$(), opts) + }, + ListWithContextFunc: func(ctx $.contextContext|raw$, opts $.v1ListOptions|raw$) ($.runtimeObject|raw$, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).List(ctx, opts) + }, + WatchFuncWithContext: func(ctx $.contextContext|raw$, opts $.v1ListOptions|raw$) ($.watchInterface|raw$, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.$.group$$.version$().$.type|publicPlural$($if .namespaced$namespace$end$).Watch(ctx, opts) + }, + }, client), + &$.type|raw${}, + $.cacheSharedIndexInformerOptions|raw${ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + )) +} +` + +var typeInformerConstructor = ` +func (f *$.type|private$Informer) defaultInformer(client $.clientSetInterface|raw$, resyncPeriod $.timeDuration|raw$) $.cacheSharedIndexInformer|raw$ { + return NewTyped$.type|public$InformerWithOptions(client$if .namespaced$, f.namespace$end$, $.interfacesInformerOptions|raw${ResyncPeriod: resyncPeriod, Indexers: $.cacheIndexers|raw${$.cacheNamespaceIndex|raw$: $.cacheMetaNamespaceIndexFunc|raw$}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} +` + +var typeInformerInformer = ` +func (f *$.type|private$Informer) Informer() $.cacheSharedIndexInformer|raw$ { + return f.TypedInformer() +} + +func (f *$.type|private$Informer) TypedInformer() $.type|public$IndexInformer { + return $.cacheNewTypedSharedIndexInformer|raw$[*$.type|raw$](f.factory.$.informerFor$(&$.type|raw${}, f.defaultInformer)) +} +` + +var typeInformerLister = ` +func (f *$.type|private$Informer) Lister() $.lister|raw$ { + return $.newLister|raw$(f.Informer().GetIndexer()) +} +` + +var typeInformerToTypedInformer = ` +// ToTyped$.type|public$Informer converts an untyped informer into a Typed$.type|public$Informer. +// +// WARNING: this conversion is only safe if the informer handles objects of type +// *$.type|public$. If that is not the case, calling type-safe methods of the returned +// Typed$.type|public$Informer leads to runtime panics. A safer alternative is to pass +// around a Typed$.type|public$Informer instances that was obtained from a +// SharedInformerFactory. +func ToTyped$.type|public$Informer(informer $.type|public$Informer) Typed$.type|public$Informer { + if informer, ok := informer.(Typed$.type|public$Informer); ok { + return informer + } + return &$.type|private$TypedInformerAdapter{informer} +} + +type $.type|private$TypedInformerAdapter struct { + $.type|public$Informer +} + +func (a *$.type|private$TypedInformerAdapter) TypedInformer() $.type|public$IndexInformer { + return $.cacheNewTypedSharedIndexInformer|raw$[*$.type|raw$](a.Informer()) +} +` + +var typeInformerToIndexInformer = ` +// To$.type|public$IndexInformer converts an untyped informer into a $.type|public$IndexInformer. +// +// WARNING: this conversion is only safe if the informer handles objects of type +// *$.type|public$. If that is not the case, calling type-safe methods of the returned +// $.type|public$IndexInformer leads to runtime panics. A safer alternative is to pass +// around a $.type|public$IndexInformer instances that was obtained from a +// SharedInformerFactory. +func To$.type|public$IndexInformer(informer $.cacheSharedIndexInformer|raw$) $.type|public$IndexInformer { + if informer, ok := informer.($.type|public$IndexInformer); ok { + return informer + } + return $.cacheNewTypedSharedIndexInformer|raw$[*$.type|raw$](informer) +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/targets.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/targets.go new file mode 100644 index 0000000000..0f916d6bf1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/targets.go @@ -0,0 +1,395 @@ +/* +Copyright 2016 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 generators + +import ( + "fmt" + "path" + "path/filepath" + "strings" + + "k8s.io/code-generator/cmd/client-gen/generators/util" + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/code-generator/cmd/informer-gen/args" + "k8s.io/code-generator/pkg/apidefinitions" + genutil "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +// NameSystems returns the name system used by the generators in this package. +func NameSystems(pluralExceptions map[string]string) namer.NameSystems { + return namer.NameSystems{ + "public": namer.NewPublicNamer(0), + "private": namer.NewPrivateNamer(0), + "raw": namer.NewRawNamer("", nil), + "publicPlural": namer.NewPublicPluralNamer(pluralExceptions), + "allLowercasePlural": namer.NewAllLowercasePluralNamer(pluralExceptions), + "lowercaseSingular": &lowercaseSingularNamer{}, + } +} + +// lowercaseSingularNamer implements Namer +type lowercaseSingularNamer struct{} + +// Name returns t's name in all lowercase. +func (n *lowercaseSingularNamer) Name(t *types.Type) string { + return strings.ToLower(t.Name.Name) +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +// objectMetaForPackage returns the type of ObjectMeta used by package p. +func objectMetaForPackage(p *types.Package) (*types.Type, bool, error) { + generatingForPackage := false + for _, t := range p.Types { + if !util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient { + continue + } + generatingForPackage = true + for _, member := range t.Members { + if member.Name == "ObjectMeta" { + return member.Type, isInternal(member), nil + } + } + } + if generatingForPackage { + return nil, false, fmt.Errorf("unable to find ObjectMeta for any types in package %s", p.Path) + } + return nil, false, nil +} + +// isInternal returns true if the tags for a member do not contain a json tag +func isInternal(m types.Member) bool { + return !strings.Contains(m.Tags, "json") +} + +const subdirForInternalInterfaces = "internalinterfaces" + +// GetTargets makes the client target definition. +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, "", gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + internalVersionOutputDir := args.OutputDir + internalVersionOutputPkg := args.OutputPkg + externalVersionOutputDir := args.OutputDir + externalVersionOutputPkg := args.OutputPkg + if !args.SingleDirectory { + internalVersionOutputDir = filepath.Join(internalVersionOutputDir, "internalversion") + internalVersionOutputPkg = path.Join(internalVersionOutputPkg, "internalversion") + externalVersionOutputDir = filepath.Join(externalVersionOutputDir, "externalversions") + externalVersionOutputPkg = path.Join(externalVersionOutputPkg, "externalversions") + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + var targetList []generator.Target + typesForGroupVersion := make(map[clientgentypes.GroupVersion][]*types.Type) + + externalGroupVersions := make(map[string]clientgentypes.GroupVersions) + internalGroupVersions := make(map[string]clientgentypes.GroupVersions) + groupGoNames := make(map[string]string) + for _, inputPkg := range context.Inputs { + p := context.Universe.Package(inputPkg) + + info, err := apidefinitions.Identify(p, apidefinitions.Informer, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + continue + } + + objectMeta, internal, err := objectMetaForPackage(p) + if err != nil { + klog.Fatal(err) + } + if objectMeta == nil { + // no types in this package had genclient + continue + } + + var gv clientgentypes.GroupVersion + var targetGroupVersions map[string]clientgentypes.GroupVersions + + if internal { + lastSlash := strings.LastIndex(p.Path, "/") + if lastSlash == -1 { + klog.Fatalf("error constructing internal group version for package %q", p.Path) + } + gv.Group = clientgentypes.Group(p.Path[lastSlash+1:]) + targetGroupVersions = internalGroupVersions + } else { + parts := strings.Split(p.Path, "/") + gv.Group = clientgentypes.Group(parts[len(parts)-2]) + gv.Version = clientgentypes.Version(parts[len(parts)-1]) + targetGroupVersions = externalGroupVersions + } + groupPackageName := gv.Group.NonEmpty() + gvPackage := path.Clean(p.Path) + + // If there's a comment of the form "// +groupName=somegroup" or + // "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the + // group when generating. + override, ok, err := apidefinitions.GroupNameForPackage(p.Comments) + if err != nil { + klog.Fatalf("error resolving group name: %v", err) + } + if ok { + gv.Group = clientgentypes.Group(override) + } + + // If there's a comment of the form "// +groupGoName=SomeUniqueShortName", use that as + // the Go group identifier in CamelCase. It defaults + groupGoNames[groupPackageName] = namer.IC(strings.Split(gv.Group.NonEmpty(), ".")[0]) + goName, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{"groupGoName"}, p.Comments) + if err != nil { + klog.Fatalf("error extracting groupGoName tags: %v", err) + } + if goName["groupGoName"] != nil { + groupGoNames[groupPackageName] = namer.IC(goName["groupGoName"][0]) + } + + var typesToGenerate []*types.Type + for _, t := range p.Types { + tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + if !tags.GenerateClient || tags.NoVerbs || !tags.HasVerb("list") || !tags.HasVerb("watch") { + continue + } + + typesToGenerate = append(typesToGenerate, t) + + if _, ok := typesForGroupVersion[gv]; !ok { + typesForGroupVersion[gv] = []*types.Type{} + } + typesForGroupVersion[gv] = append(typesForGroupVersion[gv], t) + } + if len(typesToGenerate) == 0 { + continue + } + + groupVersionsEntry, ok := targetGroupVersions[groupPackageName] + if !ok { + groupVersionsEntry = clientgentypes.GroupVersions{ + PackageName: groupPackageName, + Group: gv.Group, + } + } + groupVersionsEntry.Versions = append(groupVersionsEntry.Versions, clientgentypes.PackageVersion{Version: gv.Version, Package: gvPackage}) + targetGroupVersions[groupPackageName] = groupVersionsEntry + + orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)} + typesToGenerate = orderer.OrderTypes(typesToGenerate) + + if internal { + targetList = append(targetList, + versionTarget( + internalVersionOutputDir, internalVersionOutputPkg, + groupPackageName, gv, groupGoNames[groupPackageName], + boilerplate, typesToGenerate, + args.InternalClientSetPackage, args.ListersPackage)) + } else { + targetList = append(targetList, + versionTarget( + externalVersionOutputDir, externalVersionOutputPkg, + groupPackageName, gv, groupGoNames[groupPackageName], + boilerplate, typesToGenerate, + args.VersionedClientSetPackage, args.ListersPackage)) + } + } + + if len(externalGroupVersions) != 0 { + targetList = append(targetList, + factoryInterfaceTarget( + externalVersionOutputDir, externalVersionOutputPkg, + boilerplate, args.VersionedClientSetPackage)) + targetList = append(targetList, + factoryTarget( + externalVersionOutputDir, externalVersionOutputPkg, + boilerplate, groupGoNames, genutil.PluralExceptionListToMapOrDie(args.PluralExceptions), + externalGroupVersions, args.VersionedClientSetPackage, typesForGroupVersion)) + for _, gvs := range externalGroupVersions { + targetList = append(targetList, + groupTarget(externalVersionOutputDir, externalVersionOutputPkg, gvs, boilerplate)) + } + } + + if len(internalGroupVersions) != 0 { + targetList = append(targetList, + factoryInterfaceTarget(internalVersionOutputDir, internalVersionOutputPkg, boilerplate, args.InternalClientSetPackage)) + targetList = append(targetList, + factoryTarget( + internalVersionOutputDir, internalVersionOutputPkg, + boilerplate, groupGoNames, genutil.PluralExceptionListToMapOrDie(args.PluralExceptions), + internalGroupVersions, args.InternalClientSetPackage, typesForGroupVersion)) + for _, gvs := range internalGroupVersions { + targetList = append(targetList, + groupTarget(internalVersionOutputDir, internalVersionOutputPkg, gvs, boilerplate)) + } + } + + return targetList +} + +func factoryTarget(outputDirBase, outputPkgBase string, boilerplate []byte, groupGoNames, pluralExceptions map[string]string, groupVersions map[string]clientgentypes.GroupVersions, clientSetPackage string, + typesForGroupVersion map[clientgentypes.GroupVersion][]*types.Type) generator.Target { + return &generator.SimpleTarget{ + PkgName: path.Base(outputDirBase), + PkgPath: outputPkgBase, + PkgDir: outputDirBase, + HeaderComment: boilerplate, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = append(generators, &factoryGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "factory.go", + }, + outputPackage: outputPkgBase, + imports: generator.NewImportTrackerForPackage(outputPkgBase), + groupVersions: groupVersions, + clientSetPackage: clientSetPackage, + internalInterfacesPackage: path.Join(outputPkgBase, subdirForInternalInterfaces), + gvGoNames: groupGoNames, + }) + + generators = append(generators, &genericGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "generic.go", + }, + outputPackage: outputPkgBase, + imports: generator.NewImportTrackerForPackage(outputPkgBase), + groupVersions: groupVersions, + pluralExceptions: pluralExceptions, + typesForGroupVersion: typesForGroupVersion, + groupGoNames: groupGoNames, + }) + + return generators + }, + } +} + +func factoryInterfaceTarget(outputDirBase, outputPkgBase string, boilerplate []byte, clientSetPackage string) generator.Target { + outputDir := filepath.Join(outputDirBase, subdirForInternalInterfaces) + outputPkg := path.Join(outputPkgBase, subdirForInternalInterfaces) + + return &generator.SimpleTarget{ + PkgName: path.Base(outputDir), + PkgPath: outputPkg, + PkgDir: outputDir, + HeaderComment: boilerplate, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = append(generators, &factoryInterfaceGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "factory_interfaces.go", + }, + outputPackage: outputPkg, + imports: generator.NewImportTrackerForPackage(outputPkg), + clientSetPackage: clientSetPackage, + }) + + return generators + }, + } +} + +func groupTarget(outputDirBase, outputPackageBase string, groupVersions clientgentypes.GroupVersions, boilerplate []byte) generator.Target { + outputDir := filepath.Join(outputDirBase, groupVersions.PackageName) + outputPkg := path.Join(outputPackageBase, groupVersions.PackageName) + groupPkgName := strings.Split(string(groupVersions.PackageName), ".")[0] + + return &generator.SimpleTarget{ + PkgName: groupPkgName, + PkgPath: outputPkg, + PkgDir: outputDir, + HeaderComment: boilerplate, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = append(generators, &groupInterfaceGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "interface.go", + }, + outputPackage: outputPkg, + groupVersions: groupVersions, + imports: generator.NewImportTrackerForPackage(outputPkg), + internalInterfacesPackage: path.Join(outputPackageBase, subdirForInternalInterfaces), + }) + return generators + }, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + return tags.GenerateClient && tags.HasVerb("list") && tags.HasVerb("watch") + }, + } +} + +func versionTarget(outputDirBase, outputPkgBase string, groupPkgName string, gv clientgentypes.GroupVersion, groupGoName string, boilerplate []byte, typesToGenerate []*types.Type, clientSetPackage, listersPackage string) generator.Target { + subdir := []string{groupPkgName, strings.ToLower(gv.Version.NonEmpty())} + outputDir := filepath.Join(outputDirBase, filepath.Join(subdir...)) + outputPkg := path.Join(outputPkgBase, path.Join(subdir...)) + + return &generator.SimpleTarget{ + PkgName: strings.ToLower(gv.Version.NonEmpty()), + PkgPath: outputPkg, + PkgDir: outputDir, + HeaderComment: boilerplate, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = append(generators, &versionInterfaceGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "interface.go", + }, + outputPackage: outputPkg, + imports: generator.NewImportTrackerForPackage(outputPkg), + types: typesToGenerate, + internalInterfacesPackage: path.Join(outputPkgBase, subdirForInternalInterfaces), + }) + + for _, t := range typesToGenerate { + generators = append(generators, &informerGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: strings.ToLower(t.Name.Name) + ".go", + }, + outputPackage: outputPkg, + groupPkgName: groupPkgName, + groupVersion: gv, + groupGoName: groupGoName, + typeToGenerate: t, + imports: generator.NewImportTrackerForPackage(outputPkg), + clientSetPackage: clientSetPackage, + listersPackage: listersPackage, + internalInterfacesPackage: path.Join(outputPkgBase, subdirForInternalInterfaces), + }) + } + return generators + }, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + return tags.GenerateClient && tags.HasVerb("list") && tags.HasVerb("watch") + }, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/types.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/types.go new file mode 100644 index 0000000000..b0e7302fad --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/types.go @@ -0,0 +1,64 @@ +/* +Copyright 2016 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 generators + +import "k8s.io/gengo/v2/types" + +var ( + apiScheme = types.Name{Package: "k8s.io/kubernetes/pkg/api/legacyscheme", Name: "Scheme"} + cacheDeletedObject = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "DeletedObject"} + cacheDoneChecker = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "DoneChecker"} + cacheGenericLister = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "GenericLister"} + cacheIndexers = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "Indexers"} + cacheInformerName = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "InformerName"} + cacheListWatch = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "ListWatch"} + cacheMetaNamespaceIndexFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "MetaNamespaceIndexFunc"} + cacheNamespaceIndex = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NamespaceIndex"} + cacheNewGenericLister = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewGenericLister"} + cacheNewSharedIndexInformer = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewSharedIndexInformer"} + cacheNewSharedIndexInformerWithOptions = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewSharedIndexInformerWithOptions"} + cacheNewTypedSharedIndexInformer = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewTypedSharedIndexInformer"} + cacheSharedIndexInformer = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "SharedIndexInformer"} + cacheTypedFilteringResourceEventHandler = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "TypedFilteringResourceEventHandler"} + cacheTypedResourceEventHandlerDetailedFuncs = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "TypedResourceEventHandlerDetailedFuncs"} + cacheTypedResourceEventHandlerFuncs = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "TypedResourceEventHandlerFuncs"} + cacheTypedIndexers = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "TypedIndexers"} + cacheTypedIndexersToIndexers = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "TypedIndexersToIndexers"} + cacheTypedSharedIndexInformer = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "TypedSharedIndexInformer"} + cacheSharedIndexInformerOptions = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "SharedIndexInformerOptions"} + cacheSyncResult = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "SyncResult"} + cacheTransformFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "TransformFunc"} + cacheToListWatcherWithWatchListSemanticsFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "ToListWatcherWithWatchListSemantics"} + cacheWaitForFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "WaitFor"} + contextBackgroundFunc = types.Name{Package: "context", Name: "Background"} + contextCauseFunc = types.Name{Package: "context", Name: "Cause"} + contextContext = types.Name{Package: "context", Name: "Context"} + fmtErrorfFunc = types.Name{Package: "fmt", Name: "Errorf"} + listOptions = types.Name{Package: "k8s.io/kubernetes/pkg/apis/core", Name: "ListOptions"} + reflectType = types.Name{Package: "reflect", Name: "Type"} + runtimeObject = types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "Object"} + schemaGroupResource = types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupResource"} + schemaGroupVersionResource = types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersionResource"} + stringsBuilder = types.Name{Package: "strings", Name: "Builder"} + syncMutex = types.Name{Package: "sync", Name: "Mutex"} + timeDuration = types.Name{Package: "time", Name: "Duration"} + v1ListOptions = types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListOptions"} + metav1NamespaceAll = types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "NamespaceAll"} + metav1Object = types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "Object"} + waitContextForChannelFunc = types.Name{Package: "k8s.io/apimachinery/pkg/util/wait", Name: "ContextForChannel"} + watchInterface = types.Name{Package: "k8s.io/apimachinery/pkg/watch", Name: "Interface"} +) diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/versioninterface.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/versioninterface.go new file mode 100644 index 0000000000..d130ba5b3a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/generators/versioninterface.go @@ -0,0 +1,109 @@ +/* +Copyright 2016 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 generators + +import ( + "io" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/code-generator/cmd/client-gen/generators/util" +) + +// versionInterfaceGenerator generates the per-version interface file. +type versionInterfaceGenerator struct { + generator.GoGenerator + outputPackage string + imports namer.ImportTracker + types []*types.Type + filtered bool + internalInterfacesPackage string +} + +var _ generator.Generator = &versionInterfaceGenerator{} + +func (g *versionInterfaceGenerator) Filter(c *generator.Context, t *types.Type) bool { + if !g.filtered { + g.filtered = true + return true + } + return false +} + +func (g *versionInterfaceGenerator) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *versionInterfaceGenerator) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + return +} + +func (g *versionInterfaceGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + m := map[string]interface{}{ + "interfacesTweakListOptionsFunc": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "TweakListOptionsFunc"}), + "interfacesSharedInformerFactory": c.Universe.Type(types.Name{Package: g.internalInterfacesPackage, Name: "SharedInformerFactory"}), + "types": g.types, + } + + sw.Do(versionTemplate, m) + for _, typeDef := range g.types { + tags, err := util.ParseClientGenTags(append(typeDef.SecondClosestCommentLines, typeDef.CommentLines...)) + if err != nil { + return err + } + m["namespaced"] = !tags.NonNamespaced + m["type"] = typeDef + sw.Do(versionFuncTemplate, m) + } + + return sw.Error() +} + +var versionTemplate = ` +// Interface provides access to all the informers in this group version. +type Interface interface { + $range .types -$ + // $.|publicPlural$ returns a $.|public$Informer. + $.|publicPlural$() Typed$.|public$Informer + $end$ +} + +type version struct { + factory $.interfacesSharedInformerFactory|raw$ + namespace string + tweakListOptions $.interfacesTweakListOptionsFunc|raw$ +} + +// New returns a new Interface. +func New(f $.interfacesSharedInformerFactory|raw$, namespace string, tweakListOptions $.interfacesTweakListOptionsFunc|raw$) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} +` + +var versionFuncTemplate = ` +// $.type|publicPlural$ returns a Typed$.type|public$Informer. +func (v *version) $.type|publicPlural$() Typed$.type|public$Informer { + return &$.type|private$Informer{factory: v.factory$if .namespaced$, namespace: v.namespace$end$, tweakListOptions: v.tweakListOptions} +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/main.go new file mode 100644 index 0000000000..b0fc48517b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/informer-gen/main.go @@ -0,0 +1,59 @@ +/* +Copyright 2016 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 main + +import ( + "flag" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/informer-gen/args" + "k8s.io/code-generator/cmd/informer-gen/generators" + "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + args := args.New() + + args.AddFlags(pflag.CommandLine) + flag.Set("logtostderr", "true") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return generators.GetTargets(context, args) + } + + // Run it. + if err := gengo.Execute( + generators.NameSystems(util.PluralExceptionListToMapOrDie(args.PluralExceptions)), + generators.DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/args/args.go new file mode 100644 index 0000000000..899ea6c664 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/args/args.go @@ -0,0 +1,70 @@ +/* +Copyright 2017 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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/pkg/apidefinitions" +) + +// Args is used by the gengo framework to pass args specific to this generator. +type Args struct { + OutputDir string // must be a directory path + OutputPkg string // must be a Go import-path + GoHeaderFile string + + // PluralExceptions specify list of exceptions used when pluralizing certain types. + // For example 'Endpoints:Endpoints', otherwise the pluralizer will generate 'Endpointes'. + PluralExceptions []string + + apidefinitions.LintArgs +} + +// New returns default arguments for the generator. +func New() *Args { + return &Args{} +} + +// AddFlags add the generator flags to the flag set. +func (args *Args) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&args.OutputDir, "output-dir", "", + "the base directory under which to generate results") + fs.StringVar(&args.OutputPkg, "output-pkg", "", + "the base Go import-path under which to generate results") + fs.StringSliceVar(&args.PluralExceptions, "plural-exceptions", args.PluralExceptions, + "list of comma separated plural exception definitions in Type:PluralizedType format") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputDir) == 0 { + return fmt.Errorf("--output-dir must be specified") + } + if len(args.OutputPkg) == 0 { + return fmt.Errorf("--output-pkg must be specified") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/generators/expansion.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/generators/expansion.go new file mode 100644 index 0000000000..4755f2ed1e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/generators/expansion.go @@ -0,0 +1,73 @@ +/* +Copyright 2016 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 generators + +import ( + "io" + "os" + "path/filepath" + "strings" + + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" + + "k8s.io/code-generator/cmd/client-gen/generators/util" +) + +// expansionGenerator produces a file for a expansion interfaces. +type expansionGenerator struct { + generator.GoGenerator + outputPath string + types []*types.Type +} + +// We only want to call GenerateType() once per group. +func (g *expansionGenerator) Filter(c *generator.Context, t *types.Type) bool { + return t == g.types[0] +} + +func (g *expansionGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + for _, t := range g.types { + tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + manualFile := filepath.Join(g.outputPath, strings.ToLower(t.Name.Name+"_expansion.go")) + if _, err := os.Stat(manualFile); err == nil { + klog.V(4).Infof("file %q exists, not generating", manualFile) + } else if os.IsNotExist(err) { + sw.Do(expansionInterfaceTemplate, t) + if !tags.NonNamespaced { + sw.Do(namespacedExpansionInterfaceTemplate, t) + } + } else { + return err + } + } + return sw.Error() +} + +var expansionInterfaceTemplate = ` +// $.|public$ListerExpansion allows custom methods to be added to +// $.|public$Lister. +type $.|public$ListerExpansion interface {} +` + +var namespacedExpansionInterfaceTemplate = ` +// $.|public$NamespaceListerExpansion allows custom methods to be added to +// $.|public$NamespaceLister. +type $.|public$NamespaceListerExpansion interface {} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/generators/lister.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/generators/lister.go new file mode 100644 index 0000000000..8f9623bcb2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/generators/lister.go @@ -0,0 +1,351 @@ +/* +Copyright 2016 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 generators + +import ( + "fmt" + "io" + "path" + "path/filepath" + "strings" + + "k8s.io/code-generator/cmd/client-gen/generators/util" + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/code-generator/cmd/lister-gen/args" + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +// NameSystems returns the name system used by the generators in this package. +func NameSystems(pluralExceptions map[string]string) namer.NameSystems { + return namer.NameSystems{ + "public": namer.NewPublicNamer(0), + "private": namer.NewPrivateNamer(0), + "raw": namer.NewRawNamer("", nil), + "publicPlural": namer.NewPublicPluralNamer(pluralExceptions), + "allLowercasePlural": namer.NewAllLowercasePluralNamer(pluralExceptions), + "lowercaseSingular": &lowercaseSingularNamer{}, + } +} + +// lowercaseSingularNamer implements Namer +type lowercaseSingularNamer struct{} + +// Name returns t's name in all lowercase. +func (n *lowercaseSingularNamer) Name(t *types.Type) string { + return strings.ToLower(t.Name.Name) +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +// GetTargets makes the client target definition. +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, "", gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + var targetList []generator.Target + for _, inputPkg := range context.Inputs { + p := context.Universe.Package(inputPkg) + + info, err := apidefinitions.Identify(p, apidefinitions.Lister, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + continue + } + + objectMeta, internal, err := objectMetaForPackage(p) + if err != nil { + klog.Fatal(err) + } + if objectMeta == nil { + // no types in this package had genclient + continue + } + + var gv clientgentypes.GroupVersion + var internalGVPkg string + + if internal { + lastSlash := strings.LastIndex(p.Path, "/") + if lastSlash == -1 { + klog.Fatalf("error constructing internal group version for package %q", p.Path) + } + gv.Group = clientgentypes.Group(p.Path[lastSlash+1:]) + internalGVPkg = p.Path + } else { + parts := strings.Split(p.Path, "/") + gv.Group = clientgentypes.Group(parts[len(parts)-2]) + gv.Version = clientgentypes.Version(parts[len(parts)-1]) + + internalGVPkg = strings.Join(parts[0:len(parts)-1], "/") + } + groupPackageName := strings.ToLower(gv.Group.NonEmpty()) + + // If there's a comment of the form "// +groupName=somegroup" or + // "// +groupName=somegroup.foo.bar.io", use the first field (somegroup) as the name of the + // group when generating. + override, ok, err := apidefinitions.GroupNameForPackage(p.Comments) + if err != nil { + klog.Fatalf("error resolving group name: %v", err) + } + if ok { + gv.Group = clientgentypes.Group(strings.SplitN(override, ".", 2)[0]) + } + + var typesToGenerate []*types.Type + for _, t := range p.Types { + tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + if !tags.GenerateClient || !tags.HasVerb("list") || !tags.HasVerb("get") { + continue + } + typesToGenerate = append(typesToGenerate, t) + } + if len(typesToGenerate) == 0 { + continue + } + orderer := namer.Orderer{Namer: namer.NewPrivateNamer(0)} + typesToGenerate = orderer.OrderTypes(typesToGenerate) + + subdir := []string{groupPackageName, strings.ToLower(gv.Version.NonEmpty())} + outputDir := filepath.Join(args.OutputDir, filepath.Join(subdir...)) + outputPkg := path.Join(args.OutputPkg, path.Join(subdir...)) + targetList = append(targetList, &generator.SimpleTarget{ + PkgName: strings.ToLower(gv.Version.NonEmpty()), + PkgPath: outputPkg, + PkgDir: outputDir, + HeaderComment: boilerplate, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + tags := util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + return tags.GenerateClient && tags.HasVerb("list") && tags.HasVerb("get") + }, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = append(generators, &expansionGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: "expansion_generated.go", + }, + outputPath: outputDir, + types: typesToGenerate, + }) + + for _, t := range typesToGenerate { + generators = append(generators, &listerGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: strings.ToLower(t.Name.Name) + ".go", + }, + outputPackage: outputPkg, + groupVersion: gv, + internalGVPkg: internalGVPkg, + typeToGenerate: t, + imports: generator.NewImportTrackerForPackage(outputPkg), + objectMeta: objectMeta, + }) + } + return generators + }, + }) + } + + return targetList +} + +// objectMetaForPackage returns the type of ObjectMeta used by package p. +func objectMetaForPackage(p *types.Package) (*types.Type, bool, error) { + generatingForPackage := false + for _, t := range p.Types { + // filter out types which don't have genclient. + if !util.MustParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)).GenerateClient { + continue + } + generatingForPackage = true + for _, member := range t.Members { + if member.Name == "ObjectMeta" { + return member.Type, isInternal(member), nil + } + } + } + if generatingForPackage { + return nil, false, fmt.Errorf("unable to find ObjectMeta for any types in package %s", p.Path) + } + return nil, false, nil +} + +// isInternal returns true if the tags for a member do not contain a json tag +func isInternal(m types.Member) bool { + return !strings.Contains(m.Tags, "json") +} + +// listerGenerator produces a file of listers for a given GroupVersion and +// type. +type listerGenerator struct { + generator.GoGenerator + outputPackage string + groupVersion clientgentypes.GroupVersion + internalGVPkg string + typeToGenerate *types.Type + imports namer.ImportTracker + objectMeta *types.Type +} + +var _ generator.Generator = &listerGenerator{} + +func (g *listerGenerator) Filter(c *generator.Context, t *types.Type) bool { + return t == g.typeToGenerate +} + +func (g *listerGenerator) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *listerGenerator) Imports(c *generator.Context) (imports []string) { + imports = append(imports, g.imports.ImportLines()...) + return +} + +func (g *listerGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + + klog.V(5).Infof("processing type %v", t) + m := map[string]interface{}{ + "Resource": c.Universe.Function(types.Name{Package: t.Name.Package, Name: "Resource"}), + "labelsSelector": c.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/labels", Name: "Selector"}), + "listersResourceIndexer": c.Universe.Function(types.Name{Package: "k8s.io/client-go/listers", Name: "ResourceIndexer"}), + "listersNew": c.Universe.Function(types.Name{Package: "k8s.io/client-go/listers", Name: "New"}), + "listersNewNamespaced": c.Universe.Function(types.Name{Package: "k8s.io/client-go/listers", Name: "NewNamespaced"}), + "cacheIndexer": c.Universe.Type(types.Name{Package: "k8s.io/client-go/tools/cache", Name: "Indexer"}), + "type": t, + "objectMeta": g.objectMeta, + } + + tags, err := util.ParseClientGenTags(append(t.SecondClosestCommentLines, t.CommentLines...)) + if err != nil { + return err + } + + if tags.NonNamespaced { + sw.Do(typeListerInterfaceNonNamespaced, m) + } else { + sw.Do(typeListerInterface, m) + } + + sw.Do(typeListerStruct, m) + sw.Do(typeListerConstructor, m) + + if tags.NonNamespaced { + return sw.Error() + } + + sw.Do(typeListerNamespaceLister, m) + sw.Do(namespaceListerInterface, m) + sw.Do(namespaceListerStruct, m) + + return sw.Error() +} + +var typeListerInterface = ` +// $.type|public$Lister helps list $.type|publicPlural$. +// All objects returned here must be treated as read-only. +type $.type|public$Lister interface { + // List lists all $.type|publicPlural$ in the indexer. + // Objects returned here must be treated as read-only. + List(selector $.labelsSelector|raw$) (ret []*$.type|raw$, err error) + // $.type|publicPlural$ returns an object that can list and get $.type|publicPlural$. + $.type|publicPlural$(namespace string) $.type|public$NamespaceLister + $.type|public$ListerExpansion +} +` + +var typeListerInterfaceNonNamespaced = ` +// $.type|public$Lister helps list $.type|publicPlural$. +// All objects returned here must be treated as read-only. +type $.type|public$Lister interface { + // List lists all $.type|publicPlural$ in the indexer. + // Objects returned here must be treated as read-only. + List(selector $.labelsSelector|raw$) (ret []*$.type|raw$, err error) + // Get retrieves the $.type|public$ from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*$.type|raw$, error) + $.type|public$ListerExpansion +} +` + +// This embeds a typed resource indexer instead of aliasing, so that the struct +// is available as a receiver for methods specific to the generated type +// (from the corresponding expansion interface). +var typeListerStruct = ` +// $.type|private$Lister implements the $.type|public$Lister interface. +type $.type|private$Lister struct { + $.listersResourceIndexer|raw$[*$.type|raw$] +} +` + +var typeListerConstructor = ` +// New$.type|public$Lister returns a new $.type|public$Lister. +func New$.type|public$Lister(indexer $.cacheIndexer|raw$) $.type|public$Lister { + return &$.type|private$Lister{$.listersNew|raw$[*$.type|raw$](indexer, $.Resource|raw$("$.type|lowercaseSingular$"))} +} +` + +var typeListerNamespaceLister = ` +// $.type|publicPlural$ returns an object that can list and get $.type|publicPlural$. +func (s *$.type|private$Lister) $.type|publicPlural$(namespace string) $.type|public$NamespaceLister { + return $.type|private$NamespaceLister{$.listersNewNamespaced|raw$[*$.type|raw$](s.ResourceIndexer, namespace)} +} +` + +var namespaceListerInterface = ` +// $.type|public$NamespaceLister helps list and get $.type|publicPlural$. +// All objects returned here must be treated as read-only. +type $.type|public$NamespaceLister interface { + // List lists all $.type|publicPlural$ in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector $.labelsSelector|raw$) (ret []*$.type|raw$, err error) + // Get retrieves the $.type|public$ from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*$.type|raw$, error) + $.type|public$NamespaceListerExpansion +} +` + +// This embeds a typed namespaced resource indexer instead of aliasing, so that the struct +// is available as a receiver for methods specific to the generated type +// (from the corresponding expansion interface). +var namespaceListerStruct = ` +// $.type|private$NamespaceLister implements the $.type|public$NamespaceLister +// interface. +type $.type|private$NamespaceLister struct { + $.listersResourceIndexer|raw$[*$.type|raw$] +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/main.go new file mode 100644 index 0000000000..8dde18bab9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/lister-gen/main.go @@ -0,0 +1,59 @@ +/* +Copyright 2016 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 main + +import ( + "flag" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/lister-gen/args" + "k8s.io/code-generator/cmd/lister-gen/generators" + "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + args := args.New() + + args.AddFlags(pflag.CommandLine) + flag.Set("logtostderr", "true") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return generators.GetTargets(context, args) + } + + // Run it. + if err := gengo.Execute( + generators.NameSystems(util.PluralExceptionListToMapOrDie(args.PluralExceptions)), + generators.DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args/args.go new file mode 100644 index 0000000000..b4448303cc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args/args.go @@ -0,0 +1,57 @@ +/* +Copyright 2020 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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/pkg/apidefinitions" +) + +type Args struct { + OutputFile string + GoHeaderFile string + + apidefinitions.LintArgs +} + +// New returns default arguments for the generator. +func New() *Args { + return &Args{} +} + +// AddFlags add the generator flags to the flag set. +func (args *Args) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&args.OutputFile, "output-file", "generated.prerelease_lifecycle.go", + "the name of the file to be generated") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputFile) == 0 { + return fmt.Errorf("--output-file must be specified") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/main.go new file mode 100644 index 0000000000..717b6b0761 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/main.go @@ -0,0 +1,76 @@ +/* +Copyright 2020 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. +*/ + +// prerelease-lifecycle-gen is a tool for auto-generating api-status.csv files. +// +// Given a list of input directories, it will create a zz_api_status.go file for all beta APIs which indicates the kinds, +// the release it was introduced, the release it will be deprecated, and the release it will be removed. +// +// Generation is governed by comment tags in the source. Any package may +// request Status generation by including a comment in the file-comments of +// one file, of the form: +// +// // +k8s:prerelease-lifecycle-gen=true +// +// // +k8s:prerelease-lifecycle-gen:introduced=1.19 +// // +k8s:prerelease-lifecycle-gen:deprecated=1.22 +// // +k8s:prerelease-lifecycle-gen:removed=1.25 +// // +k8s:prerelease-lifecycle-gen:replacement=wardle.example.com,v1,Flunder +// +// Note that registration is a whole-package option, and is not available for +// individual types. +package main + +import ( + "flag" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args" + statusgenerators "k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + args := args.New() + + args.AddFlags(pflag.CommandLine) + flag.Set("logtostderr", "true") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return statusgenerators.GetTargets(context, args) + } + + // Run it. + if err := gengo.Execute( + statusgenerators.NameSystems(), + statusgenerators.DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go new file mode 100644 index 0000000000..13f834dbf2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status.go @@ -0,0 +1,486 @@ +/* +Copyright 2020 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 prereleaselifecyclegenerators + +import ( + "fmt" + "io" + "path" + "regexp" + "strconv" + "strings" + + "k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args" + "k8s.io/code-generator/pkg/apidefinitions" + genutil "k8s.io/code-generator/pkg/util" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + + "k8s.io/klog/v2" +) + +// This is the comment tag that carries parameters for API status generation. Because the cadence is fixed, we can predict +// with near certainty when this lifecycle happens as the API is introduced. +const ( + tagEnabledName = "k8s:prerelease-lifecycle-gen" + introducedTagName = tagEnabledName + ":introduced" + deprecatedTagName = tagEnabledName + ":deprecated" + removedTagName = tagEnabledName + ":removed" + + replacementTagName = tagEnabledName + ":replacement" +) + +// enabledTagValue holds parameters from a tagName tag. +type tagValue struct { + value string +} + +func extractEnabledTypeTag(t *types.Type) *tagValue { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + return extractTag(tagEnabledName, comments) +} + +func tagExists(tagName string, t *types.Type) bool { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + rawTag := extractTag(tagName, comments) + return rawTag != nil +} + +func extractKubeVersionTag(tagName string, t *types.Type) (*tagValue, int, int, error) { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + rawTag := extractTag(tagName, comments) + if rawTag == nil || len(rawTag.value) == 0 { + return nil, -1, -1, fmt.Errorf("%v missing %v=Version tag", t, tagName) + } + + splitValue := strings.Split(rawTag.value, ".") + if len(splitValue) != 2 || len(splitValue[0]) == 0 || len(splitValue[1]) == 0 { + return nil, -1, -1, fmt.Errorf("%v format must match %v=xx.yy tag", t, tagName) + } + major, err := strconv.ParseInt(splitValue[0], 10, 32) + if err != nil { + return nil, -1, -1, fmt.Errorf("%v format must match %v=xx.yy : %w", t, tagName, err) + } + minor, err := strconv.ParseInt(splitValue[1], 10, 32) + if err != nil { + return nil, -1, -1, fmt.Errorf("%v format must match %v=xx.yy : %w", t, tagName, err) + } + + return rawTag, int(major), int(minor), nil +} + +func extractIntroducedTag(t *types.Type) (*tagValue, int, int, error) { + return extractKubeVersionTag(introducedTagName, t) +} + +func extractDeprecatedTag(t *types.Type) (*tagValue, int, int, error) { + return extractKubeVersionTag(deprecatedTagName, t) +} + +func extractRemovedTag(t *types.Type) (*tagValue, int, int, error) { + return extractKubeVersionTag(removedTagName, t) +} + +func extractReplacementTag(t *types.Type) (group, version, kind string, hasReplacement bool, err error) { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{replacementTagName}, comments) + if err != nil { + return "", "", "", false, fmt.Errorf("failed to parse comments: %w", err) + } + tagVals := tags[replacementTagName] + if len(tagVals) == 0 { + // No match for the tag. + return "", "", "", false, nil + } + // If there are multiple values, abort. + if len(tagVals) > 1 { + return "", "", "", false, fmt.Errorf("found %d %s tags: %q", len(tagVals), replacementTagName, tagVals) + } + tagValue := tagVals[0] + parts := strings.Split(tagValue, ",") + if len(parts) != 3 { + return "", "", "", false, fmt.Errorf(`%s value must be ",,", got %q`, replacementTagName, tagValue) + } + group, version, kind = parts[0], parts[1], parts[2] + if len(version) == 0 || len(kind) == 0 { + return "", "", "", false, fmt.Errorf(`%s value must be ",,", got %q`, replacementTagName, tagValue) + } + // sanity check the group + if strings.ToLower(group) != group { + return "", "", "", false, fmt.Errorf(`replacement group must be all lower-case, got %q`, group) + } + // sanity check the version + if !strings.HasPrefix(version, "v") || strings.ToLower(version) != version { + return "", "", "", false, fmt.Errorf(`replacement version must start with "v" and be all lower-case, got %q`, version) + } + // sanity check the kind + if strings.ToUpper(kind[:1]) != kind[:1] { + return "", "", "", false, fmt.Errorf(`replacement kind must start with uppercase-letter, got %q`, kind) + } + return group, version, kind, true, nil +} + +func extractTag(tagName string, comments []string) *tagValue { + tags, err := genutil.ExtractCommentTagsWithoutArguments("+", []string{tagName}, comments) + if err != nil { + klog.Fatalf("Error extracting %s tags: %v", tagName, err) + } + if tags[tagName] == nil { + // No match for the tag. + return nil + } + // If there are multiple values, abort. + if len(tags[tagName]) > 1 { + klog.Fatalf("Found %d %s tags: %q", len(tags[tagName]), tagName, tags[tagName]) + } + + // If we got here we are returning something. + tag := &tagValue{} + + // Get the primary value. + parts := strings.Split(tags[tagName][0], ",") + if len(parts) >= 1 { + tag.value = parts[0] + } + + // Parse extra arguments. + parts = parts[1:] + for i := range parts { + kv := strings.SplitN(parts[i], "=", 2) + k := kv[0] + if k != "" { + klog.Fatalf("Unsupported %s param: %q", tagName, parts[i]) + } + } + return tag +} + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{ + "public": namer.NewPublicNamer(1), + "raw": namer.NewRawNamer("", nil), + } +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +// GetTargets makes the target definition. +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, gengo.StdBuildTag, gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + targetList := []generator.Target{} + + for _, i := range context.Inputs { + klog.V(5).Infof("considering pkg %q", i) + pkg := context.Universe[i] + + info, err := apidefinitions.Identify(pkg, apidefinitions.PrereleaseLifecycle, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + klog.V(5).Infof(" not enabled") + continue + } + klog.V(3).Infof("generating package %q", pkg.Path) + + targetList = append(targetList, + &generator.SimpleTarget{ + PkgName: strings.Split(path.Base(pkg.Path), ".")[0], + PkgPath: pkg.Path, + PkgDir: pkg.Dir, // output pkg is the same as the input + HeaderComment: boilerplate, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + return t.Name.Package == pkg.Path + }, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + return []generator.Generator{ + NewPrereleaseLifecycleGen(args.OutputFile, pkg.Path), + } + }, + }) + } + return targetList +} + +// genDeepCopy produces a file with autogenerated deep-copy functions. +type genPreleaseLifecycle struct { + generator.GoGenerator + targetPackage string + imports namer.ImportTracker + typesForInit []*types.Type +} + +// NewPrereleaseLifecycleGen creates a generator for the prerelease-lifecycle-generator +func NewPrereleaseLifecycleGen(outputFilename, targetPackage string) generator.Generator { + return &genPreleaseLifecycle{ + GoGenerator: generator.GoGenerator{ + OutputFilename: outputFilename, + }, + targetPackage: targetPackage, + imports: generator.NewImportTracker(), + typesForInit: make([]*types.Type, 0), + } +} + +func (g *genPreleaseLifecycle) Namers(c *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "public": namer.NewPublicNamer(1), + "intrapackage": namer.NewPublicNamer(0), + "raw": namer.NewRawNamer("", nil), + } +} + +func (g *genPreleaseLifecycle) Filter(c *generator.Context, t *types.Type) bool { + // Filter out types not being processed or not copyable within the package. + if !isAPIType(t) { + klog.V(2).Infof("Type %v is not a valid target for status", t) + return false + } + g.typesForInit = append(g.typesForInit, t) + return true +} + +// versionMethod returns the signature of an () method, nil or an error +// if the type is wrong. Introduced() allows more efficient deep copy +// implementations to be defined by the type's author. The correct signature +// +// func (t *T) () string +func versionMethod(methodName string, t *types.Type) (*types.Signature, error) { + f, found := t.Methods[methodName] + if !found { + return nil, nil + } + if len(f.Signature.Parameters) != 0 { + return nil, fmt.Errorf("type %v: invalid %v signature, expected no parameters", t, methodName) + } + if len(f.Signature.Results) != 2 { + return nil, fmt.Errorf("type %v: invalid %v signature, expected exactly two result types", t, methodName) + } + + ptrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Kind == types.Pointer && f.Signature.Receiver.Elem.Name == t.Name + nonPtrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Name == t.Name + + if !ptrRcvr && !nonPtrRcvr { + // this should never happen + return nil, fmt.Errorf("type %v: invalid %v signature, expected a receiver of type %s or *%s", t, methodName, t.Name.Name, t.Name.Name) + } + + return f.Signature, nil +} + +// versionedMethodOrDie returns the signature of a () method, nil or calls klog.Fatalf +// if the type is wrong. +func versionedMethodOrDie(methodName string, t *types.Type) *types.Signature { + ret, err := versionMethod(methodName, t) + if err != nil { + klog.Fatal(err) + } + return ret +} + +// isAPIType indicates whether or not a type could be used to serve an API. That means, "does it have TypeMeta". +// This doesn't mean the type is served, but we will handle all TypeMeta types. +func isAPIType(t *types.Type) bool { + // Filter out private types. + if namer.IsPrivateGoName(t.Name.Name) { + return false + } + + if t.Kind != types.Struct { + return false + } + + for _, currMember := range t.Members { + if currMember.Embedded && currMember.Name == "TypeMeta" { + return true + } + } + + if t.Kind == types.Alias { + return isAPIType(t.Underlying) + } + + return false +} + +func (g *genPreleaseLifecycle) isOtherPackage(pkg string) bool { + if pkg == g.targetPackage { + return false + } + if strings.HasSuffix(pkg, "\""+g.targetPackage+"\"") { + return false + } + return true +} + +func (g *genPreleaseLifecycle) Imports(c *generator.Context) (imports []string) { + importLines := []string{} + for _, singleImport := range g.imports.ImportLines() { + if g.isOtherPackage(singleImport) { + importLines = append(importLines, singleImport) + } + } + return importLines +} + +var ( + isGAVersionRegex = regexp.MustCompile(`^v\d+$`) +) + +func (g *genPreleaseLifecycle) argsFromType(c *generator.Context, t *types.Type) (generator.Args, error) { + a := generator.Args{ + "type": t, + } + _, introducedMajor, introducedMinor, err := extractIntroducedTag(t) + if err != nil { + return nil, err + } + + // Take version from package last segment. + // Use heuristic to determine whether the package is GA or prerelease. + // If the package is GA, the version matches the format vN where N is a number. + version := path.Base(t.Name.Package) + isGAVersion := isGAVersionRegex.MatchString(version) + + a = a. + With("introducedMajor", introducedMajor). + With("introducedMinor", introducedMinor) + + // compute based on our policy + hasDeprecated := tagExists(deprecatedTagName, t) + hasRemoved := tagExists(removedTagName, t) + + deprecatedMajor := introducedMajor + deprecatedMinor := introducedMinor + 3 + // if someone intentionally override the deprecation release + if hasDeprecated { + _, deprecatedMajor, deprecatedMinor, err = extractDeprecatedTag(t) + if err != nil { + return nil, err + } + } + + if !isGAVersion || hasDeprecated { + a = a. + With("deprecatedMajor", deprecatedMajor). + With("deprecatedMinor", deprecatedMinor) + } + + // compute based on our policy + removedMajor := deprecatedMajor + removedMinor := deprecatedMinor + 3 + // if someone intentionally override the removed release + if hasRemoved { + _, removedMajor, removedMinor, err = extractRemovedTag(t) + if err != nil { + return nil, err + } + } + + if !isGAVersion || hasRemoved { + a = a. + With("removedMajor", removedMajor). + With("removedMinor", removedMinor) + } + + replacementGroup, replacementVersion, replacementKind, hasReplacement, err := extractReplacementTag(t) + if err != nil { + return nil, err + } + if hasReplacement { + gvkType := c.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersionKind"}) + g.imports.AddType(gvkType) + a = a. + With("replacementGroup", replacementGroup). + With("replacementVersion", replacementVersion). + With("replacementKind", replacementKind). + With("GroupVersionKind", gvkType) + } + + return a, nil +} + +func (g *genPreleaseLifecycle) Init(c *generator.Context, w io.Writer) error { + return nil +} + +func (g *genPreleaseLifecycle) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + klog.V(3).Infof("Generating prerelease-lifecycle for type %v", t) + + sw := generator.NewSnippetWriter(w, c, "$", "$") + args, err := g.argsFromType(c, t) + if err != nil { + return err + } + + if versionedMethodOrDie("APILifecycleIntroduced", t) == nil { + sw.Do("// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.\n", args) + sw.Do("// It is controlled by \""+introducedTagName+"\" tags in types.go.\n", args) + sw.Do("func (in *$.type|intrapackage$) APILifecycleIntroduced() (major, minor int) {\n", args) + sw.Do(" return $.introducedMajor$, $.introducedMinor$\n", args) + sw.Do("}\n\n", nil) + } + + if _, hasDeprecated := args["deprecatedMajor"]; hasDeprecated { + if versionedMethodOrDie("APILifecycleDeprecated", t) == nil { + sw.Do("// APILifecycleDeprecated is an autogenerated function, returning the release in which the API struct was or will be deprecated as int versions of major and minor for comparison.\n", args) + sw.Do("// It is controlled by \""+deprecatedTagName+"\" tags in types.go or \""+introducedTagName+"\" plus three minor.\n", args) + sw.Do("func (in *$.type|intrapackage$) APILifecycleDeprecated() (major, minor int) {\n", args) + sw.Do(" return $.deprecatedMajor$, $.deprecatedMinor$\n", args) + sw.Do("}\n\n", nil) + } + } + + if _, hasReplacement := args["replacementKind"]; hasReplacement { + if versionedMethodOrDie("APILifecycleReplacement", t) == nil { + sw.Do("// APILifecycleReplacement is an autogenerated function, returning the group, version, and kind that should be used instead of this deprecated type.\n", args) + sw.Do("// It is controlled by \""+replacementTagName+"=,,\" tags in types.go.\n", args) + sw.Do("func (in *$.type|intrapackage$) APILifecycleReplacement() ($.GroupVersionKind|raw$) {\n", args) + sw.Do(" return $.GroupVersionKind|raw${Group:\"$.replacementGroup$\", Version:\"$.replacementVersion$\", Kind:\"$.replacementKind$\"}\n", args) + sw.Do("}\n\n", nil) + } + } + + if _, hasRemoved := args["removedMajor"]; hasRemoved { + if versionedMethodOrDie("APILifecycleRemoved", t) == nil { + sw.Do("// APILifecycleRemoved is an autogenerated function, returning the release in which the API is no longer served as int versions of major and minor for comparison.\n", args) + sw.Do("// It is controlled by \""+removedTagName+"\" tags in types.go or \""+deprecatedTagName+"\" plus three minor.\n", args) + sw.Do("func (in *$.type|intrapackage$) APILifecycleRemoved() (major, minor int) {\n", args) + sw.Do(" return $.removedMajor$, $.removedMinor$\n", args) + sw.Do("}\n\n", nil) + } + } + + return sw.Error() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_targets_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_targets_test.go new file mode 100644 index 0000000000..f811f04f8c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_targets_test.go @@ -0,0 +1,168 @@ +/* +Copyright 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 prereleaselifecyclegenerators + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + + "k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" +) + +func TestGetTargets(t *testing.T) { + cases := []struct { + name string + pkgs map[string]*types.Package + inputs []string + wantPkgs []string + }{ + { + name: "enabled package activates", + pkgs: map[string]*types.Package{ + "example.com/api/v1": { + Path: "example.com/api/v1", + Dir: "/tmp/example/api/v1", + Comments: []string{"+k8s:prerelease-lifecycle-gen=true"}, + }, + }, + inputs: []string{"example.com/api/v1"}, + wantPkgs: []string{"example.com/api/v1"}, + }, + { + name: "sole =false opts out", + pkgs: map[string]*types.Package{ + "example.com/api/v1": { + Path: "example.com/api/v1", + Dir: "/tmp/example/api/v1", + Comments: []string{"+k8s:prerelease-lifecycle-gen=false"}, + }, + }, + inputs: []string{"example.com/api/v1"}, + wantPkgs: nil, + }, + { + name: "no relevant tag is skipped", + pkgs: map[string]*types.Package{ + "example.com/api/v1": { + Path: "example.com/api/v1", + Dir: "/tmp/example/api/v1", + Comments: []string{"+groupName=example.com"}, + }, + }, + inputs: []string{"example.com/api/v1"}, + wantPkgs: nil, + }, + { + name: "enabled with introduced subtag activates", + pkgs: map[string]*types.Package{ + "example.com/api/v1beta1": { + Path: "example.com/api/v1beta1", + Dir: "/tmp/example/api/v1beta1", + Comments: []string{ + "+k8s:prerelease-lifecycle-gen=true", + "+k8s:prerelease-lifecycle-gen:introduced=1.30", + }, + }, + }, + inputs: []string{"example.com/api/v1beta1"}, + wantPkgs: []string{"example.com/api/v1beta1"}, + }, + // Ecosystem regression: a third-party generator's tag in the same + // doc.go must NOT cause prerelease-gen to fail or skip. + { + name: "foreign third-party generator tag is ignored", + pkgs: map[string]*types.Package{ + "example.com/api/v1": { + Path: "example.com/api/v1", + Dir: "/tmp/example/api/v1", + Comments: []string{ + "+k8s:my-custom-gen=value", + "+k8s:prerelease-lifecycle-gen=true", + }, + }, + }, + inputs: []string{"example.com/api/v1"}, + wantPkgs: []string{"example.com/api/v1"}, + }, + { + name: "mix of all cases", + pkgs: map[string]*types.Package{ + "example.com/enabled/v1": { + Path: "example.com/enabled/v1", + Dir: "/tmp/enabled/v1", + Comments: []string{"+k8s:prerelease-lifecycle-gen=true"}, + }, + "example.com/optedout/v1": { + Path: "example.com/optedout/v1", + Dir: "/tmp/optedout/v1", + Comments: []string{"+k8s:prerelease-lifecycle-gen=false"}, + }, + "example.com/untagged/v1": { + Path: "example.com/untagged/v1", + Dir: "/tmp/untagged/v1", + Comments: []string{"+groupName=example.com"}, + }, + "example.com/withsubtag/v1beta1": { + Path: "example.com/withsubtag/v1beta1", + Dir: "/tmp/withsubtag/v1beta1", + Comments: []string{ + "+k8s:prerelease-lifecycle-gen=true", + "+k8s:prerelease-lifecycle-gen:introduced=1.30", + }, + }, + }, + inputs: []string{ + "example.com/enabled/v1", + "example.com/optedout/v1", + "example.com/untagged/v1", + "example.com/withsubtag/v1beta1", + }, + wantPkgs: []string{ + "example.com/enabled/v1", + "example.com/withsubtag/v1beta1", + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ctx := &generator.Context{ + Universe: types.Universe(tc.pkgs), + Inputs: tc.inputs, + } + a := &args.Args{OutputFile: "zz_generated.prerelease_lifecycle.go"} + + got := GetTargets(ctx, a) + + var gotPkgs []string + for _, tgt := range got { + gotPkgs = append(gotPkgs, tgt.Path()) + } + sort.Strings(gotPkgs) + want := append([]string(nil), tc.wantPkgs...) + sort.Strings(want) + + if diff := cmp.Diff(want, gotPkgs); diff != "" { + t.Errorf("GetTargets package paths mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_test.go new file mode 100644 index 0000000000..96e392047e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators/status_test.go @@ -0,0 +1,798 @@ +/* +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 prereleaselifecyclegenerators + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +var mockType = &types.Type{ + CommentLines: []string{ + "RandomType defines a random structure in Kubernetes", + "It should be used just when you need something different than 42", + }, + SecondClosestCommentLines: []string{}, +} + +func TestArgsFromType(t *testing.T) { + type testcase struct { + name string + t *types.Type + expected generator.Args + expectedError string + } + + tests := []testcase{ + { + name: "no comments", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + }, + expectedError: `missing`, + }, + { + name: "GA type", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + }, + }, + { + name: "GA type v2", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v2", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + }, + }, + { + name: "GA type - explicit deprecated", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:deprecated=1.7", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 7, + }, + }, + { + name: "GA type - explicit removed", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:removed=1.9", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "removedMajor": 1, + "removedMinor": 9, + }, + }, + { + name: "GA type - explicit", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:deprecated=1.7", + "+k8s:prerelease-lifecycle-gen:removed=1.9", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 7, + "removedMajor": 1, + "removedMinor": 9, + }, + }, + { + name: "beta type - defaulted", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1beta1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 8, + "removedMajor": 1, + "removedMinor": 11, + }, + }, + { + name: "beta type - explicit", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1beta1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:deprecated=1.7", + "+k8s:prerelease-lifecycle-gen:removed=1.9", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 7, + "removedMajor": 1, + "removedMinor": 9, + }, + }, + { + name: "beta type - explicit deprecated only", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1beta1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:deprecated=1.7", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 7, + "removedMajor": 1, + "removedMinor": 10, + }, + }, + { + name: "beta type - explicit removed only", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1beta1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + "+k8s:prerelease-lifecycle-gen:removed=1.9", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 8, + "removedMajor": 1, + "removedMinor": 9, + }, + }, + { + name: "alpha type - defaulted", + t: &types.Type{ + Name: types.Name{ + Name: "Simple", + Package: "k8s.io/apis/core/v1alpha1", + }, + CommentLines: []string{ + "+k8s:prerelease-lifecycle-gen:introduced=1.5", + }, + }, + expected: generator.Args{ + "introducedMajor": 1, + "introducedMinor": 5, + "deprecatedMajor": 1, + "deprecatedMinor": 8, + "removedMajor": 1, + "removedMinor": 11, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.expected != nil { + test.expected["type"] = test.t + } + gen := genPreleaseLifecycle{} + args, err := gen.argsFromType(nil, test.t) + if test.expectedError != "" { + if err == nil { + t.Errorf("expected error, got none") + } else if !strings.Contains(err.Error(), test.expectedError) { + t.Errorf("expected error %q, got %q", test.expectedError, err.Error()) + } + return + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if diff := cmp.Diff(test.expected, args); diff != "" { + t.Error(diff) + } + }) + } +} + +func Test_extractKubeVersionTag(t *testing.T) { + oldKlogOsExit := klog.OsExit + defer func() { + klog.OsExit = oldKlogOsExit + }() + klog.OsExit = customExit + + tests := []struct { + name string + tagName string + tagComments []string + wantValue *tagValue + wantMajor int + wantMinor int + wantErr bool + wantFatal bool + }{ + { + name: "not found tag should generate an error", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someOtherTag:version=1.5", + }, + wantValue: nil, + wantErr: true, + }, + { + name: "found tag should return correctly", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someVersionTag:version=1.5", + }, + wantValue: &tagValue{ + value: "1.5", + }, + wantMajor: 1, + wantMinor: 5, + wantErr: false, + }, + { + name: "multiple declarations of same tag should return an error", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someVersionTag:version=1.5", + "+someVersionTag:version=v1.7", + }, + wantValue: nil, + wantFatal: true, + }, + { + name: "multiple values on same tag should return an error", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someVersionTag:version=1.5,something", + }, + wantValue: nil, + wantFatal: true, + }, + { + name: "wrong tag major value should return an error", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someVersionTag:version=.5", + }, + wantErr: true, + }, + { + name: "wrong tag minor value should return an error", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someVersionTag:version=1.", + }, + wantErr: true, + }, + { + name: "wrong tag format should return an error", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someVersionTag:version=1.5.7", + }, + wantErr: true, + }, + { + name: "wrong tag major int value should return an error", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someVersionTag:version=blah.5", + }, + wantErr: true, + }, + { + name: "wrong tag minor int value should return an error", + tagName: "someVersionTag:version", + tagComments: []string{ + "+someVersionTag:version=1.blah", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockType.SecondClosestCommentLines = tt.tagComments + gotTag, gotMajor, gotMinor, err, fatalErr := safeExtractKubeVersionTag(tt.tagName, mockType) + if (fatalErr != nil) != tt.wantFatal { + t.Errorf("extractKubeVersionTag() fatalErr = %v, wantFatal %v", fatalErr, tt.wantFatal) + return + } + if tt.wantFatal { + return + } + if (err != nil) != tt.wantErr { + t.Errorf("extractKubeVersionTag() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr { + return + } + if !reflect.DeepEqual(gotTag, tt.wantValue) { + t.Errorf("extractKubeVersionTag() got = %v, want %v", gotTag, tt.wantValue) + } + if gotMajor != tt.wantMajor { + t.Errorf("extractKubeVersionTag() got1 = %v, want %v", gotMajor, tt.wantMajor) + } + if gotMinor != tt.wantMinor { + t.Errorf("extractKubeVersionTag() got2 = %v, want %v", gotMinor, tt.wantMinor) + } + }) + } +} + +func customExit(exitCode int) { + panic(strconv.Itoa(exitCode)) +} + +func safeExtractKubeVersionTag(tagName string, t *types.Type) (value *tagValue, major int, minor int, err error, localErr error) { + defer func() { + if e := recover(); e != nil { + localErr = fmt.Errorf("extractKubeVersionTag returned error: %v", e) + } + }() + value, major, minor, err = extractKubeVersionTag(tagName, t) + return +} + +func safeExtractTag(t *testing.T, tagName string, comments []string) (value *tagValue, err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("extractTag returned error: %v", e) + } + }() + value = extractTag(tagName, comments) + return +} + +func Test_extractTag(t *testing.T) { + oldKlogOsExit := klog.OsExit + defer func() { + klog.OsExit = oldKlogOsExit + }() + klog.OsExit = customExit + + comments := []string{ + "+variable=7", + "+anotherVariable=8", + "+yetAnotherVariable=9", + "variableWithoutMarker=10", + "+variableWithoutValue", + "+variable=11", + "+multi-valuedVariable=12,13,14", + "+strangeVariable=15,=16", + } + + tests := []struct { + name string + tagComments []string + variableName string + wantError bool + wantValue *tagValue + }{ + { + name: "variable with explicit value", + tagComments: comments, + variableName: "anotherVariable", + wantValue: &tagValue{value: "8"}, + }, + { + name: "variable without explicit value", + tagComments: comments, + variableName: "variableWithoutValue", + wantValue: &tagValue{value: ""}, + }, + { + name: "variable not present in comments", + tagComments: comments, + variableName: "variableOutOfNowhere", + wantValue: nil, + }, + { + name: "variable without marker test", + tagComments: comments, + variableName: "variableWithoutMarker", + wantValue: nil, + }, + { + name: "abort duplicated variable", + tagComments: comments, + variableName: "variable", + wantError: true, + wantValue: nil, + }, + { + name: "abort variable with multiple values", + tagComments: comments, + variableName: "multi-valuedVariable", + wantError: true, + wantValue: nil, + }, + { + name: "this test documents strange behaviour", // TODO: Is this behaviour intended? + tagComments: comments, + variableName: "strangeVariable", + wantValue: &tagValue{value: "15"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotTag, err := safeExtractTag(t, tt.variableName, tt.tagComments) + if (err != nil) != tt.wantError { + t.Errorf("extractTag() err = %v, wantError = %v.", gotTag, tt.wantError) + return + } + if tt.wantError { + return + } + if !reflect.DeepEqual(gotTag, tt.wantValue) { + t.Errorf("extractTag() got = %v, want %v", gotTag, tt.wantValue) + } + }) + } +} + +func Test_extractEnabledTypeTag(t *testing.T) { + someComments := []string{ + "+variable=7", + "+k8s:prerelease-lifecycle-gen=8", + } + moreComments := []string{ + "+yetAnotherVariable=9", + "variableWithoutMarker=10", + "+variableWithoutValue", + "+variable=11", + "+multi-valuedVariable=12,13,14", + } + + tests := []struct { + name string + mockType *types.Type + wantValue *tagValue + }{ + { + name: "desired info in main comments", + mockType: &types.Type{CommentLines: someComments, SecondClosestCommentLines: moreComments}, + wantValue: &tagValue{value: "8"}, + }, + { + name: "secondary comments empty", + mockType: &types.Type{CommentLines: someComments}, + wantValue: &tagValue{value: "8"}, + }, + { + name: "main comments empty", + mockType: &types.Type{SecondClosestCommentLines: someComments}, + wantValue: &tagValue{value: "8"}, + }, + { + name: "lack of desired info, empty secondary comments", + mockType: &types.Type{CommentLines: moreComments}, + wantValue: nil, + }, + { + name: "lack of desired info, empty main comments", + mockType: &types.Type{SecondClosestCommentLines: moreComments}, + wantValue: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotTag := extractEnabledTypeTag(tt.mockType) + if !reflect.DeepEqual(gotTag, tt.wantValue) { + t.Errorf("extractEnabledTypeTag() got = %v, want %v", gotTag, tt.wantValue) + } + }) + } +} + +func Test_extractReplacementTag(t *testing.T) { + replacementTag := "+k8s:prerelease-lifecycle-gen:replacement" + tests := []struct { + name string + mainComments []string + secondaryComments []string + wantGroup string + wantVersion string + wantKind string + wantHasReplacement bool + wantErr bool + }{ + { + name: "no replacement tag", + mainComments: []string{"randomText=7"}, + secondaryComments: []string{"importantFlag=8.8.8.8"}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: false, + }, + { + name: "replacement tag correct", + mainComments: []string{fmt.Sprintf("%v=my_group,v1,KindOf", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "my_group", + wantVersion: "v1", + wantKind: "KindOf", + wantHasReplacement: true, + wantErr: false, + }, + { + name: "correct replacement tag in secondary comments", + mainComments: []string{}, + secondaryComments: []string{fmt.Sprintf("%v=my_group,v1,KindOf", replacementTag)}, + wantGroup: "my_group", + wantVersion: "v1", + wantKind: "KindOf", + wantHasReplacement: true, + wantErr: false, + }, + { + name: "4 values instead of 3", + mainComments: []string{fmt.Sprintf("%v=my_group,v1,KindOf,subKind", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: true, + }, + { + name: "4 values instead of 3 in secondary comments", + mainComments: []string{}, + secondaryComments: []string{fmt.Sprintf("%v=my_group,v1,KindOf,subKind", replacementTag)}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: true, + }, + { + name: "2 values instead of 3", + mainComments: []string{fmt.Sprintf("%v=my_group,v1", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: true, + }, + { + name: "group name not all upper", + mainComments: []string{fmt.Sprintf("%v=myGroup,v1,KindOf", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: true, + }, + { + name: "version name does not start with v", + mainComments: []string{fmt.Sprintf("%v=my_group,bestVersion,KindOf", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: true, + }, + { + name: "kind name does not start with capital", + mainComments: []string{fmt.Sprintf("%v=my_group,v1,kindOf", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: true, + }, + { + name: "empty group name", // TODO: is it a valid input or a bug? + mainComments: []string{fmt.Sprintf("%v=,v1,KindOf", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "", + wantVersion: "v1", + wantKind: "KindOf", + wantHasReplacement: true, + wantErr: false, + }, + { + name: "empty version", + mainComments: []string{fmt.Sprintf("%v=my_group,,KindOf", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: true, + }, + { + name: "empty kind", + mainComments: []string{fmt.Sprintf("%v=my_group,v1,", replacementTag)}, + secondaryComments: []string{}, + wantGroup: "", + wantVersion: "", + wantKind: "", + wantHasReplacement: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + replacementGroup, replacementVersion, replacementKind, hasReplacement, err := extractReplacementTag(&types.Type{ + CommentLines: tt.mainComments, + SecondClosestCommentLines: tt.secondaryComments, + }) + if replacementGroup != tt.wantGroup { + t.Errorf("extractReplacementTag() group got = %v, want %v", replacementGroup, tt.wantGroup) + } + if replacementVersion != tt.wantVersion { + t.Errorf("extractReplacementTag() version got = %v, want %v", replacementVersion, tt.wantVersion) + } + if replacementKind != tt.wantKind { + t.Errorf("extractReplacementTag() kind got = %v, want %v", replacementKind, tt.wantKind) + } + if hasReplacement != tt.wantHasReplacement { + t.Errorf("extractReplacementTag() hasReplacement got = %v, want %v", hasReplacement, tt.wantHasReplacement) + } + if (err != nil) != tt.wantErr { + t.Errorf("extractReplacementTag() err got = %v, want %v", err, tt.wantErr) + } + }) + } +} + +func Test_isAPIType(t *testing.T) { + tests := []struct { + name string + t *types.Type + want bool + }{ + { + name: "private name is not apitype", + want: false, + t: &types.Type{ + Name: types.Name{ + Name: "notpublic", + }, + }, + }, + { + name: "non struct is not apitype", + want: false, + t: &types.Type{ + Name: types.Name{ + Name: "Public", + }, + Kind: types.Slice, + }, + }, + { + name: "contains member type", + want: true, + t: &types.Type{ + Name: types.Name{ + Name: "Public", + }, + Kind: types.Struct, + Members: []types.Member{ + { + Embedded: true, + Name: "TypeMeta", + }, + }, + }, + }, + + { + name: "contains no type", + want: false, + t: &types.Type{ + Name: types.Name{ + Name: "Public", + }, + Kind: types.Struct, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isAPIType(tt.t); got != tt.want { + t.Errorf("isAPIType() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/args/args.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/args/args.go new file mode 100644 index 0000000000..70e2b1d739 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/args/args.go @@ -0,0 +1,57 @@ +/* +Copyright 2018 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 args + +import ( + "fmt" + + "github.com/spf13/pflag" + "k8s.io/code-generator/pkg/apidefinitions" +) + +type Args struct { + OutputFile string + GoHeaderFile string + + apidefinitions.LintArgs +} + +// New returns default arguments for the generator. +func New() *Args { + return &Args{} +} + +// AddFlags add the generator flags to the flag set. +func (args *Args) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&args.OutputFile, "output-file", "generated.register.go", + "the name of the file to be generated") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputFile) == 0 { + return fmt.Errorf("output file base name cannot be empty") + } + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/register_external.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/register_external.go new file mode 100644 index 0000000000..4c3fd37e90 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/register_external.go @@ -0,0 +1,122 @@ +/* +Copyright 2025 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 generators + +import ( + "io" + "sort" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +type registerExternalGenerator struct { + generator.GoGenerator + outputPackage string + gv clientgentypes.GroupVersion + typesToGenerate []*types.Type + imports namer.ImportTracker +} + +var _ generator.Generator = ®isterExternalGenerator{} + +func (g *registerExternalGenerator) Filter(_ *generator.Context, _ *types.Type) bool { + return false +} + +func (g *registerExternalGenerator) Imports(c *generator.Context) (imports []string) { + return g.imports.ImportLines() +} + +func (g *registerExternalGenerator) Namers(_ *generator.Context) namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *registerExternalGenerator) Finalize(context *generator.Context, w io.Writer) error { + typesToGenerateOnlyNames := make([]string, len(g.typesToGenerate)) + for index, typeToGenerate := range g.typesToGenerate { + typesToGenerateOnlyNames[index] = typeToGenerate.Name.Name + } + + // sort the list of types to register, so that the generator produces stable output + sort.Strings(typesToGenerateOnlyNames) + + sw := generator.NewSnippetWriter(w, context, "$", "$") + m := map[string]interface{}{ + "groupName": g.gv.Group, + "version": g.gv.Version, + "types": typesToGenerateOnlyNames, + "addToGroupVersion": context.Universe.Function(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "AddToGroupVersion"}), + "groupVersion": context.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "GroupVersion"}), + "schemaGroupVersion": context.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupVersion"}), + "schemaGroupResource": context.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime/schema", Name: "GroupResource"}), + "scheme": context.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "Scheme"}), + "schemeBuilder": context.Universe.Type(types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "SchemeBuilder"}), + } + sw.Do(registerExternalTypesTemplate, m) + return sw.Error() +} + +var registerExternalTypesTemplate = ` +// GroupName specifies the group name used to register the objects. +const GroupName = "$.groupName$" + +// GroupVersion specifies the group and the version used to register the objects. +var GroupVersion = $.groupVersion|raw${Group: GroupName, Version: "$.version$"} + +// SchemeGroupVersion is group version used to register these objects +// +// Deprecated: use GroupVersion instead. +var SchemeGroupVersion = $.schemaGroupVersion|raw${Group: GroupName, Version: "$.version$"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) $.schemaGroupResource|raw$ { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder $.schemeBuilder|raw$ + localSchemeBuilder = &SchemeBuilder + // Deprecated: use Install instead + AddToScheme = localSchemeBuilder.AddToScheme + Install = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *$.scheme|raw$) error { + scheme.AddKnownTypes(SchemeGroupVersion, + $range .types -$ + &$.${}, + $end$ + ) + // AddToGroupVersion allows the serialization of client types like ListOptions. + $.addToGroupVersion|raw$(scheme, SchemeGroupVersion) + return nil +} +` diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/targets.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/targets.go new file mode 100644 index 0000000000..9d66e20d96 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/targets.go @@ -0,0 +1,162 @@ +/* +Copyright 2018 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 generators + +import ( + "fmt" + "os" + "path" + "strings" + + "k8s.io/klog/v2" + + clientgentypes "k8s.io/code-generator/cmd/client-gen/types" + "k8s.io/code-generator/cmd/register-gen/args" + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" +) + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{} +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +// GetTargets makes targets to generate. +func GetTargets(context *generator.Context, args *args.Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, gengo.StdBuildTag, gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + targetList := []generator.Target{} + for _, input := range context.Inputs { + pkg := context.Universe.Package(input) + if !isRegisterGenTarget(pkg, idOpts) { + continue + } + internal, err := isInternal(pkg) + if err != nil { + klog.V(5).Infof("skipping the generation of %s file, due to err %v", args.OutputFile, err) + continue + } + if internal { + klog.V(5).Infof("skipping the generation of %s file because %s package contains internal types, note that internal types don't have \"json\" tags", args.OutputFile, pkg.Name) + continue + } + registerFileName := "register.go" + searchPath := path.Join(pkg.Dir, registerFileName) + if _, err := os.Stat(path.Join(searchPath)); err == nil { + klog.V(5).Infof("skipping the generation of %s file because %s already exists in the path %s", args.OutputFile, registerFileName, searchPath) + continue + } else if err != nil && !os.IsNotExist(err) { + klog.Fatalf("an error %v has occurred while checking if %s exists", err, registerFileName) + } + + gv := clientgentypes.GroupVersion{} + { + pathParts := strings.Split(pkg.Path, "/") + if len(pathParts) < 2 { + klog.Errorf("the path of the package must contain the group name and the version, path = %s", pkg.Path) + continue + } + gv.Group = clientgentypes.Group(pathParts[len(pathParts)-2]) + gv.Version = clientgentypes.Version(pathParts[len(pathParts)-1]) + + // if there is a comment of the form "// +groupName=somegroup" or "// +groupName=somegroup.foo.bar.io", + // extract the fully qualified API group name from it and overwrite the group inferred from the package path + override, ok, err := apidefinitions.GroupNameForPackage(pkg.Comments) + if err != nil { + klog.Fatalf("error resolving group name: %v", err) + } + if ok { + klog.V(5).Infof("overriding the group name with = %s", override) + gv.Group = clientgentypes.Group(override) + } + } + + typesToRegister := []*types.Type{} + for _, t := range pkg.Types { + klog.V(5).Infof("considering type = %s", t.Name.String()) + for _, typeMember := range t.Members { + if typeMember.Name == "TypeMeta" && typeMember.Embedded { + typesToRegister = append(typesToRegister, t) + } + } + } + + targetList = append(targetList, + &generator.SimpleTarget{ + PkgName: pkg.Name, + PkgPath: pkg.Path, // output to same pkg as input + PkgDir: pkg.Dir, // output to same pkg as input + HeaderComment: boilerplate, + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + return []generator.Generator{ + ®isterExternalGenerator{ + GoGenerator: generator.GoGenerator{ + OutputFilename: args.OutputFile, + }, + gv: gv, + typesToGenerate: typesToRegister, + outputPackage: pkg.Path, + imports: generator.NewImportTrackerForPackage(pkg.Path), + }, + } + }, + }) + } + + return targetList +} + +// isRegisterGenTarget reports whether pkg has opted in to register-gen. +// Activation rules are encoded in apidefinitions.Register's Spec +// (Boolean ActivationTag with a +groupName= fallback). +func isRegisterGenTarget(pkg *types.Package, idOpts []apidefinitions.Option) bool { + info, err := apidefinitions.Identify(pkg, apidefinitions.Register, idOpts...) + if err != nil { + klog.Fatal(err) + } + return info.ShouldGenerate() +} + +// isInternal determines whether the given package +// contains the internal types or not +func isInternal(p *types.Package) (bool, error) { + for _, t := range p.Types { + for _, member := range t.Members { + if member.Name == "TypeMeta" { + return !strings.Contains(member.Tags, "json"), nil + } + } + } + return false, fmt.Errorf("unable to find TypeMeta for any types in package %s", p.Path) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/targets_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/targets_test.go new file mode 100644 index 0000000000..e3febf5eb0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/generators/targets_test.go @@ -0,0 +1,229 @@ +/* +Copyright 2025 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 generators + +import ( + "os" + "path/filepath" + "sort" + "testing" + + "k8s.io/code-generator/cmd/register-gen/args" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" +) + +func TestGetTargets(t *testing.T) { + type pkgSpec struct { + path string + dir string // optional; will use t.TempDir() if needRegisterFile + comments []string + // typeMembers describes a single struct type in the package. nil means no types. + typeMembers []types.Member + // needRegisterFile, when true, creates dir/register.go before invoking GetTargets. + needRegisterFile bool + } + + externalTypeMeta := []types.Member{ + {Name: "TypeMeta", Embedded: true, Tags: `json:",inline"`}, + } + internalTypeMeta := []types.Member{ + {Name: "TypeMeta", Embedded: true}, + } + + cases := []struct { + name string + pkgs []pkgSpec + wantPaths []string // package paths expected in returned targets, sorted + wantGroups map[string]string // pkgPath -> expected group + wantHasType map[string]bool // pkgPath -> whether typesToGenerate is non-empty + }{ + { + name: "external pkg with +groupName activates", + pkgs: []pkgSpec{{ + path: "k8s.io/api/foo/v1", + comments: []string{"+groupName=foo.k8s.io"}, + typeMembers: externalTypeMeta, + }}, + wantPaths: []string{"k8s.io/api/foo/v1"}, + wantGroups: map[string]string{"k8s.io/api/foo/v1": "foo.k8s.io"}, + wantHasType: map[string]bool{"k8s.io/api/foo/v1": true}, + }, + { + name: "external pkg with +k8s:register-gen=false is opted out", + pkgs: []pkgSpec{{ + path: "k8s.io/api/foo/v1", + comments: []string{"+k8s:register-gen=false"}, + typeMembers: externalTypeMeta, + }}, + wantPaths: nil, + }, + { + name: "opt-out wins over +groupName", + pkgs: []pkgSpec{{ + path: "k8s.io/api/foo/v1", + comments: []string{"+groupName=foo.k8s.io", "+k8s:register-gen=false"}, + typeMembers: externalTypeMeta, + }}, + wantPaths: nil, + }, + { + name: "no +groupName and no +k8s:register-gen tag is skipped", + pkgs: []pkgSpec{{ + path: "k8s.io/api/foo/v1", + typeMembers: externalTypeMeta, + }}, + wantPaths: nil, + }, + { + // isInternal returns an error when the package has no TypeMeta- + // bearing types at all, and GetTargets treats that error as a skip. + name: "+groupName but no TypeMeta types is skipped (isInternal errors)", + pkgs: []pkgSpec{{ + path: "k8s.io/api/foo/v1", + comments: []string{"+groupName=foo.k8s.io"}, + }}, + wantPaths: nil, + }, + { + name: "internal pkg (TypeMeta without json tag) is skipped", + pkgs: []pkgSpec{{ + path: "k8s.io/api/foo/v1", + comments: []string{"+groupName=foo"}, + typeMembers: internalTypeMeta, + }}, + wantPaths: nil, + }, + { + name: "pkg with existing register.go is skipped", + pkgs: []pkgSpec{{ + path: "k8s.io/api/foo/v1", + comments: []string{"+groupName=foo.k8s.io"}, + typeMembers: externalTypeMeta, + needRegisterFile: true, + }}, + wantPaths: nil, + }, + // Ecosystem regression: a third-party generator's tag in the same + // doc.go must NOT cause register-gen to fail. The +groupName= still + // activates as expected. + { + name: "foreign third-party generator tag is ignored", + pkgs: []pkgSpec{{ + path: "k8s.io/api/foo/v1", + comments: []string{ + "+k8s:my-custom-gen=value", + "+groupName=foo.k8s.io", + }, + typeMembers: externalTypeMeta, + }}, + wantPaths: []string{"k8s.io/api/foo/v1"}, + wantGroups: map[string]string{"k8s.io/api/foo/v1": "foo.k8s.io"}, + wantHasType: map[string]bool{"k8s.io/api/foo/v1": true}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + universe := types.Universe{} + ctx := &generator.Context{ + Universe: universe, + } + for i, ps := range tc.pkgs { + dir := ps.dir + if dir == "" { + dir = t.TempDir() + } + if ps.needRegisterFile { + f, err := os.Create(filepath.Join(dir, "register.go")) + if err != nil { + t.Fatalf("creating register.go for pkg[%d]: %v", i, err) + } + if err := f.Close(); err != nil { + t.Fatalf("closing register.go for pkg[%d]: %v", i, err) + } + } + p := universe.Package(ps.path) + p.Name = filepath.Base(ps.path) + p.Dir = dir + p.Comments = ps.comments + if ps.typeMembers != nil { + p.Types["MyType"] = &types.Type{ + Name: types.Name{Package: ps.path, Name: "MyType"}, + Kind: types.Struct, + Members: ps.typeMembers, + } + } + ctx.Inputs = append(ctx.Inputs, ps.path) + } + + a := &args.Args{OutputFile: "zz_generated.register.go"} + got := GetTargets(ctx, a) + + gotPaths := make([]string, 0, len(got)) + for _, g := range got { + gotPaths = append(gotPaths, g.Path()) + } + sort.Strings(gotPaths) + wantPaths := append([]string(nil), tc.wantPaths...) + sort.Strings(wantPaths) + if !equalStrSlice(gotPaths, wantPaths) { + t.Fatalf("target paths: got %v, want %v", gotPaths, wantPaths) + } + + // Verify per-package group and typesToGenerate by exercising the + // generator function the SimpleTarget would run. + for _, tgt := range got { + st, ok := tgt.(*generator.SimpleTarget) + if !ok { + t.Fatalf("%s: target is %T, want *generator.SimpleTarget", tgt.Path(), tgt) + } + gens := st.GeneratorsFunc(ctx) + if len(gens) != 1 { + t.Fatalf("%s: got %d generators, want 1", tgt.Path(), len(gens)) + } + rg, ok := gens[0].(*registerExternalGenerator) + if !ok { + t.Fatalf("%s: generator is %T, want *registerExternalGenerator", tgt.Path(), gens[0]) + } + if want, ok := tc.wantGroups[tgt.Path()]; ok { + if string(rg.gv.Group) != want { + t.Errorf("%s: group = %q, want %q", tgt.Path(), rg.gv.Group, want) + } + } + if want, ok := tc.wantHasType[tgt.Path()]; ok { + hasType := len(rg.typesToGenerate) > 0 + if hasType != want { + t.Errorf("%s: hasType = %v, want %v (typesToGenerate=%v)", tgt.Path(), hasType, want, rg.typesToGenerate) + } + } + } + }) + } +} + +func equalStrSlice(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/main.go new file mode 100644 index 0000000000..ac28f87fb7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/main.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 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 main + +import ( + "flag" + + "github.com/spf13/pflag" + "k8s.io/code-generator/cmd/register-gen/args" + "k8s.io/code-generator/cmd/register-gen/generators" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + args := args.New() + args.AddFlags(pflag.CommandLine) + flag.Set("logtostderr", "true") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + + pflag.Parse() + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + myTargets := func(context *generator.Context) []generator.Target { + return generators.GetTargets(context, args) + } + + if err := gengo.Execute( + generators.NameSystems(), + generators.DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/generate.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/generate.go new file mode 100644 index 0000000000..efbf7791a5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/generate.go @@ -0,0 +1,20 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2025 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. +*/ + +//go:generate go run k8s.io/code-generator/cmd/register-gen --output-file zz_generated.register.go --go-header-file=../../../examples/hack/boilerplate.go.txt k8s.io/code-generator/cmd/register-gen/output_tests/... +package outputtests diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/doc.go new file mode 100644 index 0000000000..6c1622a73b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:register-gen=simpletype +// +k8s:deepcopy-gen=false + +package v1 diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/fake_deepcopy.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/fake_deepcopy.go new file mode 100644 index 0000000000..c35312d268 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/fake_deepcopy.go @@ -0,0 +1,44 @@ +/* +Copyright 2025 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 v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SimpleType) DeepCopyInto(out *SimpleType) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Inner. +func (in *SimpleType) DeepCopy() *SimpleType { + if in == nil { + return nil + } + out := new(SimpleType) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SimpleType) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/types.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/types.go new file mode 100644 index 0000000000..53d28cc5c8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/types.go @@ -0,0 +1,25 @@ +/* +Copyright 2025 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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type SimpleType struct { + metav1.TypeMeta `json:""` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/zz_generated.register.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/zz_generated.register.go new file mode 100644 index 0000000000..f4fdd30b6a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/register-gen/output_tests/simpletype/v1/zz_generated.register.go @@ -0,0 +1,70 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by register-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName specifies the group name used to register the objects. +const GroupName = "simpletype" + +// GroupVersion specifies the group and the version used to register the objects. +var GroupVersion = metav1.GroupVersion{Group: GroupName, Version: "v1"} + +// SchemeGroupVersion is group version used to register these objects +// +// Deprecated: use GroupVersion instead. +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + // Deprecated: use Install instead + AddToScheme = localSchemeBuilder.AddToScheme + Install = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &SimpleType{}, + ) + // AddToGroupVersion allows the serialization of client types like ListOptions. + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint.go new file mode 100644 index 0000000000..63e54e755e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint.go @@ -0,0 +1,157 @@ +/* +Copyright 2024 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 main + +import ( + "fmt" + "sort" + + "k8s.io/gengo/v2/codetags" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +// linter is a struct that holds the state of the linting process. +// It contains a map of types that have been linted, a list of linting rules, +// and a list of errors that occurred during the linting process. +type linter struct { + linted map[*types.Type]bool + rules []lintRule + // lintErrors is all the errors, grouped by type, that occurred during the + // linting process. + lintErrors map[*types.Type][]error +} + +// lintRule is a function that validates a slice of comments. +// container is the type containing the element being linted (e.g. the Struct when linting a Field). +// It may be nil if the element is top-level (e.g. a Type definition). +// t is the type of the element being linted (e.g. the Field's type, or the Type itself). +// It returns a string as an error message if the comments are invalid, +// and an error there is an error happened during the linting process. +type lintRule func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) + +func (l *linter) AddError(t *types.Type, field, msg string) { + var err error + if field == "" { + err = fmt.Errorf("%s", msg) + } else { + err = fmt.Errorf("field %s: %s", field, msg) + } + l.lintErrors[t] = append(l.lintErrors[t], err) +} + +func newLinter(rules ...lintRule) *linter { + if len(rules) == 0 { + klog.Errorf("rules are not passed to the linter") + } + return &linter{ + linted: make(map[*types.Type]bool), + rules: rules, + lintErrors: map[*types.Type][]error{}, + } +} + +func (l *linter) lintType(t *types.Type) error { + if _, ok := l.linted[t]; ok { + return nil + } + l.linted[t] = true + + if t.CommentLines != nil { + extracted := codetags.Extract("+", t.CommentLines) + if _, ok := extracted["k8s:validation-gen-nolint"]; ok { + return nil + } + klog.V(5).Infof("linting type %s", t.Name.String()) + lintErrs, err := l.lintComments(t, t, t.CommentLines) + if err != nil { + return err + } + for _, lintErr := range lintErrs { + l.AddError(t, "", lintErr) + } + } + switch t.Kind { + case types.Alias: + // Recursively lint the underlying type of the alias. + if err := l.lintType(t.Underlying); err != nil { + return err + } + case types.Struct: + // Recursively lint each member of the struct. + for _, member := range t.Members { + klog.V(5).Infof("linting comments for field %s of type %s", member.String(), t.Name.String()) + lintErrs, err := l.lintComments(t, member.Type, member.CommentLines) + if err != nil { + return err + } + for _, lintErr := range lintErrs { + l.AddError(t, member.Name, lintErr) + } + if err := l.lintType(member.Type); err != nil { + return err + } + } + case types.Slice, types.Array, types.Pointer: + // Recursively lint the element type of the slice or array. + if err := l.lintType(t.Elem); err != nil { + return err + } + case types.Map: + // Recursively lint the key and element types of the map. + if err := l.lintType(t.Key); err != nil { + return err + } + if err := l.lintType(t.Elem); err != nil { + return err + } + } + return nil +} + +// lintComments runs all registered rules on a slice of comments. +func (l *linter) lintComments(container *types.Type, t *types.Type, comments []string) ([]string, error) { + var lintErrs []string + var tags []codetags.Tag + + extracted := codetags.Extract("+", comments) + keys := make([]string, 0, len(extracted)) + for k := range extracted { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, tagName := range keys { + lines := extracted[tagName] + t, err := codetags.ParseAll(lines) + if err != nil { + // If parsing fails, it means it is not a valid tag. + continue + } + tags = append(tags, t...) + } + + for _, rule := range l.rules { + if msg, err := rule(container, t, tags); err != nil { + return nil, err + } else if msg != "" { + lintErrs = append(lintErrs, msg) + } + } + + return lintErrs, nil +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint_rules.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint_rules.go new file mode 100644 index 0000000000..7320369498 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint_rules.go @@ -0,0 +1,379 @@ +/* +Copyright 2025 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 main + +import ( + "fmt" + "path" + "strings" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/code-generator/cmd/validation-gen/validators" + "k8s.io/gengo/v2/codetags" + "k8s.io/gengo/v2/types" +) + +func checkAlphaBetaUsage(tag codetags.Tag, isRoot bool) (string, error) { + if tag.Name == "k8s:alpha" || tag.Name == "k8s:beta" { + if !isRoot { + return fmt.Sprintf("tag %q can't be used in between", tag.Name), nil + } + if tag.ValueTag == nil { + return fmt.Sprintf("tag %q requires a validation tag as its value payload", tag.Name), nil + } + } + + if tag.ValueTag != nil { + return checkAlphaBetaUsage(*tag.ValueTag, false) + } + return "", nil +} + +// alphaBetaPrefix enforces that +k8s:alpha and +k8s:beta tags are always used as prefix to +func alphaBetaPrefix() lintRule { + return func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + for _, tag := range tags { + // Only check alpha/beta tags or validation tags. + if msg, err := checkAlphaBetaUsage(tag, true); err != nil || msg != "" { + return msg, err + } + } + return "", nil + } +} + +// checkTagStability recursively checks that a tag and its nested tags +// satisfy the stability requirements of the context. +func checkTagStability(tag codetags.Tag, contextLevel validators.TagStabilityLevel) (string, error) { + tagStability, err := validators.GetStability(tag.Name) + // all DV tags have stability, if a tag doesn't have stability then it is not a valid DV tag. + if err != nil { + return "", nil + } + cmpOrder, err := tagStability.Compare(contextLevel) + if err != nil { + return "", err + } + if cmpOrder < 0 { + return fmt.Sprintf("tag %q with stability level %q cannot be used in %s validation", tag.Name, tagStability, contextLevel), nil + } + if tag.ValueTag != nil { + return checkTagStability(*tag.ValueTag, contextLevel) + } + return "", nil +} + +// validationStability enforces stability level constraints on tags. +func validationStability() lintRule { + return func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + pkgPath := t.Name.Package + if container != nil { + pkgPath = container.Name.Package + } + + // Unprefixed validations are normally required to be Stable. + // In Alpha packages, we allow Alpha-level and Beta-level validations + // without a prefix. In Beta packages, we allow Beta-level validations + // without a prefix. + defaultContextLevel := validators.TagStabilityLevelStable + // APIVersion is the last element of the package path. + apiVersion := path.Base(pkgPath) + if strings.Contains(apiVersion, "alpha") { + defaultContextLevel = validators.TagStabilityLevelAlpha + } else if strings.Contains(apiVersion, "beta") { + defaultContextLevel = validators.TagStabilityLevelBeta + } + + for _, tag := range tags { + contextLevel := defaultContextLevel + tagToCheck := tag + + // For stability level tags, set the stability context for the inner validation, + // overriding the package-level default. + if tag.Name == "k8s:alpha" || tag.Name == "k8s:beta" { + if tag.Name == "k8s:alpha" { + contextLevel = validators.TagStabilityLevelAlpha + } else { + contextLevel = validators.TagStabilityLevelBeta + } + if tag.ValueTag == nil { + continue + } + tagToCheck = *tag.ValueTag + } + + // For feature gate tags, we allow developers to use nested beta validation tags + // without forcing validation authors to write redundant handwritten code (bypassing the + // declarative validation equivalence check), we automatically relax the stability + // context to Beta if the current context is Stable. + if tagToCheck.Name == "k8s:ifEnabled" || tagToCheck.Name == "k8s:ifDisabled" { + if contextLevel == validators.TagStabilityLevelStable { + contextLevel = validators.TagStabilityLevelBeta + } + } + + msg, err := checkTagStability(tagToCheck, contextLevel) + if err != nil { + return "", err + } + if msg != "" { + return msg, nil + } + } + return "", nil + } +} + +// hasTag recursively checks if a tag with given name exists in the tag tree. +func hasTag(tags []codetags.Tag, name string) bool { + for _, tag := range tags { + if tag.Name == name { + return true + } + // Also check conditional tags value + if tag.ValueTag != nil && hasTag([]codetags.Tag{*tag.ValueTag}, name) { + return true + } + } + return false +} + +// hasRequirednessTag returns true if tags contain +k8s:optional, +k8s:required, or +k8s:forbidden. +func hasRequirednessTag(tags []codetags.Tag) bool { + return hasTag(tags, "k8s:optional") || hasTag(tags, "k8s:required") || hasTag(tags, "k8s:forbidden") +} + +// hasNonOpaqueValidationTag returns true if tags contain any registered validation tag that is not opaqueType. +func hasNonOpaqueValidationTag(extractor validators.ValidationExtractor, chainTags sets.Set[string], tags []codetags.Tag) bool { + for _, tag := range tags { + if tag.Name == "k8s:optional" || tag.Name == "k8s:opaqueType" { + continue + } + if chainTags.Has(tag.Name) { + if tag.ValueTag != nil && hasNonOpaqueValidationTag(extractor, chainTags, []codetags.Tag{*tag.ValueTag}) { + return true + } + continue + } + // Check if it's a known validation tag. + if extractor.IsKnownTag(tag.Name) { + return true + } + } + return false +} + +// requiredAndOptional checks that fields (pointers, slices, maps, arrays) with validation +// (either direct or transitive) explicitly declare +k8s:optional or +k8s:required. +func requiredAndOptional(extractor validators.ValidationExtractor) lintRule { + chainTags := sets.New[string]() + for _, doc := range extractor.Docs() { + if doc.PayloadsType == codetags.ValueTypeTag { + chainTags.Insert(doc.Tag) + } + } + + type opacity struct { + typ, key, val bool + } + + filterTags := func(tags []codetags.Tag) []codetags.Tag { + var filtered []codetags.Tag + for _, tag := range tags { + if extractor.IsKnownTag(tag.Name) { + filtered = append(filtered, tag) + } + } + return filtered + } + + type cacheKey struct { + t *types.Type + op opacity + } + hasValidation := make(map[cacheKey]*bool) + + // returns hasValidation, hasCycle, error + var hasTransitiveValidation func(t *types.Type, op opacity) (bool, bool, error) + hasTransitiveValidation = func(t *types.Type, op opacity) (bool, bool, error) { + if op.typ { + return false, false, nil + } + + ck := cacheKey{t, op} + visitedVal, visited := hasValidation[ck] + if visited { + if visitedVal == nil { + return false, true, nil // cycle detected + } + return *visitedVal, false, nil + } + hasValidation[ck] = nil + + tTags, err := extractor.ExtractTags(validators.Context{Scope: validators.ScopeType, Type: t}, t.CommentLines) + if err != nil { + return false, false, err + } + + typeVals, err := extractor.ExtractValidations( + validators.Context{Scope: validators.ScopeType, Type: t}, + filterTags(tTags)..., + ) + if err != nil { + return false, false, err + } + + op.typ = op.typ || typeVals.OpaqueType + op.key = op.key || typeVals.OpaqueKeyType + op.val = op.val || typeVals.OpaqueValType + + if op.typ { + hasVal := false + hasValidation[ck] = &hasVal + return false, false, nil + } + + if typeVals.HasEmitable() { + hasVal := true + hasValidation[ck] = &hasVal + return true, false, nil + } + + var hasVal, cycleBroken bool + switch t.Kind { + case types.Alias: + hasVal, cycleBroken, err = hasTransitiveValidation(t.Underlying, op) + case types.Slice, types.Array: + hasVal, cycleBroken, err = hasTransitiveValidation(t.Elem, opacity{typ: op.val}) + case types.Map: + kVal, cbKey, err := hasTransitiveValidation(t.Key, opacity{typ: op.key}) + if err != nil { + return false, false, err + } + eVal, cbVal, err := hasTransitiveValidation(t.Elem, opacity{typ: op.val}) + if err != nil { + return false, false, err + } + hasVal = kVal || eVal + cycleBroken = cbKey || cbVal + case types.Pointer: + hasVal, cycleBroken, err = hasTransitiveValidation(t.Elem, op) + case types.Struct: + for _, m := range t.Members { + mTags, err := extractor.ExtractTags(validators.Context{Scope: validators.ScopeField, Type: m.Type}, m.CommentLines) + if err != nil { + return false, false, err + } + if hasNonOpaqueValidationTag(extractor, chainTags, mTags) { + hasVal = true + break + } + fieldVals, err := extractor.ExtractValidations( + validators.Context{Scope: validators.ScopeField, Type: m.Type}, + filterTags(mTags)..., + ) + if err != nil { + return false, false, err + } + mOp := opacity{ + typ: op.typ || fieldVals.OpaqueType, + key: op.key || fieldVals.OpaqueKeyType, + val: op.val || fieldVals.OpaqueValType, + } + hv, cb, err := hasTransitiveValidation(m.Type, mOp) + if err != nil { + return false, false, err + } + cycleBroken = cycleBroken || cb + if hv { + hasVal = true + break + } + } + } + + if err != nil { + return false, false, err + } + + if !cycleBroken { + hasValidation[ck] = &hasVal + } else { + delete(hasValidation, ck) + } + return hasVal, cycleBroken, nil + } + + return func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + // We only care about fields in a struct. Skip if linting the struct itself. + if container == nil || container.Kind != types.Struct || container == t { + return "", nil + } + + // Skip non-pointer structs (and aliases to them) as they don't support requiredness tags. + underlying := t + for underlying.Kind == types.Alias { + underlying = underlying.Underlying + } + if underlying.Kind == types.Struct { + return "", nil + } + + // Check if already has requiredness tag + if hasRequirednessTag(tags) { + return "", nil + } + + // Check if it has validation (direct or active transitive) + if hasNonOpaqueValidationTag(extractor, chainTags, tags) { + return "field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", nil + } + + fieldVals, err := extractor.ExtractValidations( + validators.Context{Scope: validators.ScopeField, Type: t}, + filterTags(tags)..., + ) + if err != nil { + return fmt.Sprintf("invalid validation tags: %v", err), nil + } + + topOp := opacity{ + typ: fieldVals.OpaqueType, + key: fieldVals.OpaqueKeyType, + val: fieldVals.OpaqueValType, + } + + hasTransitiveVal, _, err := hasTransitiveValidation(t, topOp) + if err != nil { + return fmt.Sprintf("invalid validation tags: %v", err), nil + } + + if hasTransitiveVal { + return "field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", nil + } + + return "", nil + } +} + +func lintRules(extractor validators.ValidationExtractor) []lintRule { + return []lintRule{ + alphaBetaPrefix(), + validationStability(), + requiredAndOptional(extractor), + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint_test.go new file mode 100644 index 0000000000..77d8dd941c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/lint_test.go @@ -0,0 +1,1022 @@ +/* +Copyright 2024 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 main + +import ( + "errors" + "testing" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/code-generator/cmd/validation-gen/validators" + "k8s.io/gengo/v2/codetags" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/types" +) + +func ruleAlwaysPass(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + return "", nil +} + +func ruleAlwaysFail(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + return "lintfail", nil +} + +func ruleAlwaysErr(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + return "", errors.New("linterr") +} + +func mkCountRule(counter *int, realRule lintRule) lintRule { + return func(container *types.Type, t *types.Type, tags []codetags.Tag) (string, error) { + (*counter)++ + return realRule(container, t, tags) + } +} + +var validator = validators.InitGlobalValidator(&generator.Context{}, nil) + +func TestLintCommentsRuleInvocation(t *testing.T) { + tests := []struct { + name string + rules []lintRule + commentLineGroups [][]string + wantErr bool + wantCount int + }{ + { + name: "0 rules, 0 comments", + rules: []lintRule{}, + commentLineGroups: [][]string{}, + wantErr: false, + wantCount: 0, + }, + { + name: "1 rule, 1 comment", + rules: []lintRule{ruleAlwaysPass}, + commentLineGroups: [][]string{{"comment"}}, + wantErr: false, + wantCount: 1, + }, + { + name: "3 rules, 3 comments", + rules: []lintRule{ruleAlwaysPass, ruleAlwaysFail, ruleAlwaysErr}, + commentLineGroups: [][]string{{"comment1"}, {"comment2"}, {"comment3"}}, + wantErr: true, + wantCount: 9, + }, + { + name: "1 rule, 1 comment, rule fails", + rules: []lintRule{ruleAlwaysFail}, + commentLineGroups: [][]string{{"comment"}}, + wantErr: false, + wantCount: 1, + }, + { + name: "1 rule, 1 comment, rule errors", + rules: []lintRule{ruleAlwaysErr}, + commentLineGroups: [][]string{{"comment"}}, + wantErr: true, + wantCount: 1, + }, + { + name: "3 rules, 1 comment, rule errors in the middle", + rules: []lintRule{ruleAlwaysPass, ruleAlwaysErr, ruleAlwaysFail}, + commentLineGroups: [][]string{{"comment"}}, + wantErr: true, + wantCount: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + counter := 0 + rules := make([]lintRule, len(tt.rules)) + for i, rule := range tt.rules { + rules[i] = mkCountRule(&counter, rule) + } + l := newLinter(rules...) + for _, commentLines := range tt.commentLineGroups { + _, err := l.lintComments(nil, nil, commentLines) + gotErr := err != nil + if gotErr != tt.wantErr { + t.Errorf("lintComments() error = %v, wantErr %v", err, tt.wantErr) + } + } + if counter != tt.wantCount { + t.Errorf("expected %d rule invocations, got %d", tt.wantCount, counter) + } + }) + } +} + +func TestRuleAlphaBetaPrefix(t *testing.T) { + tests := []struct { + name string + comments []string + wantMsg string + }{ + { + name: "valid alpha prefix", + comments: []string{"+k8s:alpha=+k8s:required"}, + wantMsg: "", + }, + { + name: "valid beta prefix", + comments: []string{"+k8s:beta=+k8s:required"}, + wantMsg: "", + }, + { + name: "invalid alpha prefix (no value)", + comments: []string{"+k8s:alpha"}, + wantMsg: `tag "k8s:alpha" requires a validation tag as its value payload`, + }, + { + name: "invalid beta prefix (no value)", + comments: []string{"+k8s:beta"}, + wantMsg: `tag "k8s:beta" requires a validation tag as its value payload`, + }, + { + name: "invalid alpha prefix (value not tag)", + comments: []string{"+k8s:alpha=foo"}, + wantMsg: `tag "k8s:alpha" requires a validation tag as its value payload`, + }, + { + name: "invalid usage of alpha prefix", + comments: []string{`+k8s:item(type: "Approved")=+k8s:alpha=+k8s:zeroOrOneOfMember`}, + wantMsg: `tag "k8s:alpha" can't be used in between`, + }, + { + name: "nested alpha in item", + comments: []string{`+k8s:item=+k8s:alpha=+k8s:required`}, + wantMsg: `tag "k8s:alpha" can't be used in between`, + }, + { + name: "alpha nested in listType", + comments: []string{`+k8s:listType=+k8s:alpha=+k8s:required`}, + wantMsg: `tag "k8s:alpha" can't be used in between`, + }, + { + name: "deeply nested alpha", + comments: []string{`+k8s:item=+k8s:listType=+k8s:alpha=+k8s:required`}, + wantMsg: `tag "k8s:alpha" can't be used in between`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tags, _ := validator.ExtractTags(validators.Context{}, tt.comments) + msg, err := alphaBetaPrefix()(nil, nil, tags) + if err != nil { + t.Errorf("unexpected error: %v", err) + } else if msg != tt.wantMsg { + t.Errorf("got %q, want %q", msg, tt.wantMsg) + } + }) + } +} + +func TestRuleStability(t *testing.T) { + tests := []struct { + name string + comments []string + pkg string + wantMsg string + }{ + { + name: "stable context, stable tag", + comments: []string{"+k8s:required"}, // Stable + wantMsg: "", + }, + { + name: "beta context, stable tag", + comments: []string{"+k8s:beta=+k8s:required"}, // Beta context, Stable tag + wantMsg: "", + }, + { + name: "alpha context, stable tag", + comments: []string{"+k8s:alpha=+k8s:required"}, // Alpha context, Stable tag + wantMsg: "", + }, + { + name: "alpha context, alpha tag", + comments: []string{"+k8s:alpha=+k8s:validateTrueAlpha"}, // Alpha context, Alpha tag + wantMsg: "", + }, + { + name: "stable context, alpha tag", + comments: []string{"+k8s:validateTrueAlpha"}, // Stable context, Alpha tag + wantMsg: `tag "k8s:validateTrueAlpha" with stability level "Alpha" cannot be used in Stable validation`, + }, + { + name: "beta context, alpha tag", + comments: []string{"+k8s:beta=+k8s:validateTrueAlpha"}, // Beta context, Alpha tag + wantMsg: `tag "k8s:validateTrueAlpha" with stability level "Alpha" cannot be used in Beta validation`, + }, + { + name: "alpha pkg context, beta tag (allowed)", + comments: []string{"+k8s:validateTrueBeta"}, // Beta tag in Alpha package + pkg: "k8s.io/api/apps/v1alpha1", + wantMsg: "", + }, + { + name: "alpha pkg context, alpha tag (allowed)", + comments: []string{"+k8s:validateTrueAlpha"}, // Alpha tag in Alpha package + pkg: "k8s.io/api/apps/v1alpha1", + wantMsg: "", + }, + { + name: "beta pkg context, beta tag (allowed)", + comments: []string{"+k8s:validateTrueBeta"}, // Beta tag in Beta package + pkg: "k8s.io/api/apps/v1beta1", + wantMsg: "", + }, + { + name: "beta pkg context, alpha tag (fails)", + comments: []string{"+k8s:validateTrueAlpha"}, // Alpha tag in Beta package + pkg: "k8s.io/api/apps/v1beta1", + wantMsg: `tag "k8s:validateTrueAlpha" with stability level "Alpha" cannot be used in Beta validation`, + }, + { + name: "ifEnabled context allows beta tag", + comments: []string{"+k8s:ifEnabled(SomeFeature)=+k8s:validateTrueBeta"}, // Beta tag in ifEnabled + wantMsg: "", + }, + { + name: "ifEnabled context fails alpha tag", + comments: []string{"+k8s:ifEnabled(SomeFeature)=+k8s:validateTrueAlpha"}, // Alpha tag in ifEnabled + wantMsg: `tag "k8s:validateTrueAlpha" with stability level "Alpha" cannot be used in Beta validation`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dummyType := &types.Type{Name: types.Name{Package: tt.pkg, Name: "Dummy"}} + rule := validationStability() + tags, _ := validator.ExtractTags(validators.Context{}, tt.comments) + msg, err := rule(nil, dummyType, tags) + if err != nil { + t.Errorf("unexpected error: %v", err) + } else if msg != tt.wantMsg { + t.Errorf("got %q, want %q", msg, tt.wantMsg) + } + }) + } +} + +func TestLintType(t *testing.T) { + tests := []struct { + name string + typeToLint *types.Type + wantCount int + expectError bool + }{ + { + name: "No comments", + typeToLint: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "TestType"}, + CommentLines: nil, + }, + wantCount: 0, + expectError: false, + }, + { + name: "Valid comments", + typeToLint: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "TestType"}, + CommentLines: []string{"+k8s:optional"}, + }, + wantCount: 1, + expectError: false, + }, + { + name: "Pointer type", + typeToLint: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "TestPointer"}, + Kind: types.Pointer, + Elem: &types.Type{Name: types.Name{Package: "testpkg", Name: "ElemType"}, CommentLines: []string{"+k8s:optional"}}, + CommentLines: []string{"+k8s:optional"}, + }, + wantCount: 2, + expectError: false, + }, + { + name: "Slice of pointers", + typeToLint: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "TestSlice"}, + Kind: types.Slice, + Elem: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "PointerElem"}, + Kind: types.Pointer, + Elem: &types.Type{Name: types.Name{Package: "testpkg", Name: "ElemType"}, CommentLines: []string{"+k8s:optional"}}, + CommentLines: []string{"+k8s:optional"}, + }, + CommentLines: []string{"+k8s:optional"}, + }, + wantCount: 3, + expectError: false, + }, + { + name: "Map to pointers", + typeToLint: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "TestMap"}, + Kind: types.Map, + Key: &types.Type{Name: types.Name{Package: "testpkg", Name: "KeyType"}, CommentLines: []string{"+k8s:required"}}, + Elem: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "PointerElem"}, + Kind: types.Pointer, + Elem: &types.Type{Name: types.Name{Package: "testpkg", Name: "ElemType"}, CommentLines: []string{"+k8s:optional"}}, + CommentLines: []string{"+k8s:optional"}, + }, + CommentLines: []string{"+k8s:optional"}, + }, + wantCount: 4, + expectError: false, + }, + { + name: "Alias to pointers", + typeToLint: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "TestAlias"}, + Kind: types.Alias, + Underlying: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "PointerElem"}, + Kind: types.Pointer, + Elem: &types.Type{Name: types.Name{Package: "testpkg", Name: "ElemType"}, CommentLines: []string{"+k8s:optional"}}, + CommentLines: []string{"+k8s:optional"}, + }, + CommentLines: []string{"+k8s:optional"}, + }, + wantCount: 3, + expectError: false, + }, + { + name: "Struct with members", + typeToLint: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "TestStruct"}, + Kind: types.Struct, + Members: []types.Member{ + { + Name: "Field1", + Type: &types.Type{Name: types.Name{Package: "testpkg", Name: "FieldType"}}, + CommentLines: []string{"+k8s:optional"}, + }, + { + Name: "Field2", + Type: &types.Type{Name: types.Name{Package: "testpkg", Name: "FieldType"}}, + CommentLines: []string{"+k8s:required"}, + }, + }, + }, + wantCount: 2, + expectError: false, + }, + { + name: "Nested types", + typeToLint: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "TestStruct"}, + Kind: types.Struct, + Members: []types.Member{ + { + Name: "Field1", + Type: &types.Type{ + Name: types.Name{Package: "testpkg", Name: "NestedStruct"}, + Kind: types.Struct, + CommentLines: []string{"+k8s:optional"}, + Members: []types.Member{ + { + Name: "NestedField1", + Type: &types.Type{Name: types.Name{Package: "testpkg", Name: "NestedFieldType"}}, + CommentLines: []string{"+k8s:required"}, + }, + }, + }, + }, + }, + }, + wantCount: 3, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + counter := 0 + rules := []lintRule{mkCountRule(&counter, ruleAlwaysPass)} + l := newLinter(rules...) + if err := l.lintType(tt.typeToLint); err != nil { + t.Fatal(err) + } + gotErr := len(l.lintErrors) > 0 + if gotErr != tt.expectError { + t.Errorf("LintType() errors = %v, expectError %v", l.lintErrors, tt.expectError) + } + if counter != tt.wantCount { + t.Errorf("expected %d rule invocations, got %d", tt.wantCount, counter) + } + }) + } +} + +func TestHasAnyValidationTag(t *testing.T) { + tests := []struct { + name string + comments []string + want bool + }{ + { + name: "empty", + comments: []string{}, + want: false, + }, + { + name: "no k8s tags", + comments: []string{"just a comment"}, + want: false, + }, + { + name: "optional only", + comments: []string{"+k8s:optional"}, + want: false, + }, + { + name: "required only", + comments: []string{"+k8s:required"}, + want: true, + }, + { + name: "forbidden only", + comments: []string{"+k8s:forbidden"}, + want: true, + }, + { + name: "unrecognized k8s tag", + comments: []string{"+k8s:openapi-gen=true"}, + want: false, + }, + { + name: "minimum tag", + comments: []string{"+k8s:minimum=0"}, + want: true, + }, + { + name: "enum tag", + comments: []string{"+k8s:enum"}, + want: true, + }, + { + name: "mixed with optional", + comments: []string{"+k8s:optional", "+k8s:minimum=0"}, + want: true, + }, + } + + chainTags := sets.New[string]() + for _, doc := range validator.Docs() { + if doc.PayloadsType == codetags.ValueTypeTag { + chainTags.Insert(doc.Tag) + } + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tags, _ := validator.ExtractTags(validators.Context{}, tt.comments) + if got := hasNonOpaqueValidationTag(validator, chainTags, tags); got != tt.want { + t.Errorf("hasNonOpaqueValidationTag() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestHasRequirednessTag(t *testing.T) { + tests := []struct { + name string + comments []string + want bool + }{ + { + name: "empty", + comments: []string{}, + want: false, + }, + { + name: "no requireness", + comments: []string{"+k8s:minimum=0"}, + want: false, + }, + { + name: "optional", + comments: []string{"+k8s:optional"}, + want: true, + }, + { + name: "required", + comments: []string{"+k8s:required"}, + want: true, + }, + { + name: "optional with value", + comments: []string{"+k8s:optional=true"}, + want: true, + }, + { + name: "conditional optional", + comments: []string{`+k8s:alpha(since:"1.35")=+k8s:optional`}, + want: true, + }, + { + name: "conditional required", + comments: []string{`+k8s:alpha(since:"1.35")=+k8s:required`}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tags, _ := validator.ExtractTags(validators.Context{}, tt.comments) + if got := hasRequirednessTag(tags); got != tt.want { + t.Errorf("hasRequirednessTag() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLintRequiredness(t *testing.T) { + sharedAlias := testAlias("MyAlias", testSlice(testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }))) + + tests := []struct { + name string + typeToLint *types.Type + wantError string + }{ + { + name: "pointer field without validation - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr(testType("string"))), + }), + wantError: "", + }, + { + name: "pointer field with direct validation, no requireness - error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr(testType("int")), "+k8s:minimum=0"), + }), + wantError: "field Foo: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "pointer field with transitive validation, no requireness - error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + )), + }), + wantError: "field Foo: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "pointer field with validation and +k8s:optional - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr(testType("int")), "+k8s:optional", "+k8s:minimum=0"), + }), + wantError: "", + }, + { + name: "slice field with validation, no requireness - error", + typeToLint: testStruct("T", []types.Member{ + testField("Items", testSlice(testType("string")), "+k8s:maxItems=10"), + }), + wantError: "field Items: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "map field with validation, no requireness - error", + typeToLint: testStruct("T", []types.Member{ + testField("Data", testMap(testType("string"), testType("string")), "+k8s:maxItems=5"), + }), + wantError: "field Data: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "non-pointer struct field with validation - no error (exempt)", + typeToLint: testStruct("T", []types.Member{ + testField("Nested", testStruct("Inner", nil, "+k8s:minimum=0")), + }), + wantError: "", + }, + { + name: "recursive type with pointer to self - no infinite loop", + typeToLint: func() *types.Type { + t := testStruct("Node", nil) + t.Members = []types.Member{ + testField("Next", testPtr(t), "+k8s:optional"), + } + return t + }(), + wantError: "", + }, + { + name: "recursive type with pointer to self - no infinite loop, missing required validation", + typeToLint: func() *types.Type { + t := testStruct("Node", nil) + t.Members = []types.Member{ + testField("Next", testPtr(t), "+k8s:immutable"), + } + return t + }(), + wantError: "field Next: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "recursive type with validation - detects validation on first visit", + typeToLint: func() *types.Type { + t := testStruct("Node", nil, "+k8s:immutable") + t.Members = []types.Member{ + testField("Next", testPtr(t), "+k8s:optional"), + } + return t + }(), + wantError: "", + }, + { + name: "array field with transitive validation, no requiredNess - error", + typeToLint: testStruct("T", []types.Member{ + testField("Arr", testArray(testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }))), + }), + wantError: "field Arr: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "pointer field with transitive validation but marked opaqueType - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), "+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "pointer to alias field with transitive validation but marked opaqueType - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr( + testAlias("MyAlias", + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), + ), "+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "alias field (to slice) with transitive validation but marked opaqueType - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testAlias("MyAlias", + testSlice( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), + ), "+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "slice field with transitive validation marked opaqueValType - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testSlice( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), "+k8s:eachVal=+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "array field with transitive validation marked opaqueValType - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testArray( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), "+k8s:eachVal=+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "map field with transitive validation on key, marked opaqueKeyType - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testMap( + testAlias("KeyType", types.String, "+k8s:minLength=1"), + types.String, + ), "+k8s:eachKey=+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "map field with transitive validation on key, not marked opaqueKeyType - error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testMap( + testAlias("KeyType", types.String, "+k8s:minLength=1"), + types.String, + )), + }), + wantError: "field Foo: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "map field with transitive validation on value, marked opaqueValType - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testMap( + types.String, + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), "+k8s:eachVal=+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "map field with transitive validation on both, both marked opaque - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testMap( + testAlias("KeyType", types.String, "+k8s:minLength=1"), + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), "+k8s:eachKey=+k8s:opaqueType", "+k8s:eachVal=+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "map field with transitive validation on both, only key marked opaque - error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testMap( + testAlias("KeyType", types.String, "+k8s:minLength=1"), + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), "+k8s:eachKey=+k8s:opaqueType"), + }), + wantError: "field Foo: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "map field with transitive validation on both, only value marked opaque - error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testMap( + testAlias("KeyType", types.String, "+k8s:minLength=1"), + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), "+k8s:eachVal=+k8s:opaqueType"), + }), + wantError: "field Foo: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "pointer field with nested opaque field - bypasses transitive validation", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr( + testStruct("Inner", []types.Member{ + testField("Bar", testPtr( + testStruct("NestedInner", []types.Member{ + testField("Val", testType("int"), "+k8s:minimum=0"), + }), + ), "+k8s:opaqueType"), + }), + )), + }), + wantError: "", + }, + { + name: "alias field (to slice) with transitive validation but no opacity tags on type - error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testAlias("MyAlias", + testSlice( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), + )), + }), + wantError: "field Foo: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "alias field (to slice) with transitive validation and eachVal marked opaque on the field - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testAlias("MyAlias", + testSlice( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), + ), "+k8s:eachVal=+k8s:opaqueType"), + }), + wantError: "", + }, + { + name: "alias field (to slice) with transitive validation and eachVal marked opaque on the type definition - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testAlias("MyAlias", + testSlice( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), + "+k8s:eachVal=+k8s:opaqueType", + )), + }), + wantError: "", + }, + { + name: "pointer to alias field (to slice) with transitive validation, eachVal marked opaque on type definition - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr( + testAlias("MyAlias", + testSlice( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), + "+k8s:eachVal=+k8s:opaqueType", + ), + )), + }), + wantError: "", + }, + { + name: "alias field (to map) with transitive validation, eachVal marked opaque on type definition - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testAlias("MyAlias", + testMap( + types.String, + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), + "+k8s:eachVal=+k8s:opaqueType", + )), + }), + wantError: "", + }, + { + name: "pointer to alias field (to struct) with transitive validation, opaqueType marked on type definition - no error", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr( + testAlias("MyAlias", + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + "+k8s:opaqueType", + ), + )), + }), + wantError: "", + }, + { + name: "alias field (to slice) with local validation tag and elements marked opaque on the type definition - reports error", + typeToLint: testStruct("T", []types.Member{ + testField("Widgets", testAlias("WidgetList", + testSlice( + testStruct("Inner", []types.Member{ + testField("Bar", testType("int"), "+k8s:minimum=0"), + }), + ), + "+k8s:maxItems=100", + "+k8s:eachVal=+k8s:opaqueType", + )), + }), + wantError: "field Widgets: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + { + name: "pointer field with transitive malformed tag on alias type definition - reports error as lint warning instead of crashing", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr( + testAlias("MyString", testType("string"), "+k8s:minimum=0"), + )), + }), + wantError: "field Foo: invalid validation tags: tag \"k8s:minimum\": can only be used on integer types (pkg.MyString -> string)", + }, + { + name: "pointer field with transitive malformed tag on struct type definition - reports error as lint warning instead of crashing", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", testPtr( + testStruct("MyStruct", []types.Member{ + testField("Bar", testType("int")), + }, "+k8s:minimum=0"), + )), + }), + wantError: "field Foo: invalid validation tags: tag \"k8s:minimum\": can only be used on integer types (pkg.MyStruct)", + }, + { + name: "same alias used with different opacity contexts caches correctly", + typeToLint: testStruct("T", []types.Member{ + testField("Foo", sharedAlias, "+k8s:opaqueType"), + testField("Bar", sharedAlias), + }), + wantError: "field Bar: field with validation must have +k8s:optional, +k8s:required or +k8s:forbidden", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := newLinter(requiredAndOptional(validator)) + if err := l.lintType(tt.typeToLint); err != nil { + t.Fatalf("lintType() unexpected error: %v", err) + } + errs := l.lintErrors[tt.typeToLint] + if len(errs) > 1 { + t.Fatalf("got %d errors, but expected 0 or 1 error: %v", len(errs), errs) + } + var gotError string + if len(errs) == 1 { + gotError = errs[0].Error() + } + if gotError != tt.wantError { + t.Errorf("lintRequiredness() error = %q, want %q", gotError, tt.wantError) + } + }) + } +} + +func testType(name string, comments ...string) *types.Type { + return &types.Type{ + Name: types.Name{Package: "", Name: name}, + Kind: types.Builtin, + CommentLines: comments, + } +} + +func testPtr(elem *types.Type, comments ...string) *types.Type { + return &types.Type{ + Name: types.Name{Package: "pkg", Name: elem.Name.Name + "Ptr"}, + Kind: types.Pointer, + Elem: elem, + CommentLines: comments, + } +} + +func testStruct(name string, members []types.Member, comments ...string) *types.Type { + return &types.Type{ + Name: types.Name{Package: "pkg", Name: name}, + Kind: types.Struct, + Members: members, + CommentLines: comments, + } +} + +func testField(name string, fieldType *types.Type, comments ...string) types.Member { + return types.Member{ + Name: name, + Type: fieldType, + CommentLines: comments, + } +} + +func testSlice(elem *types.Type, comments ...string) *types.Type { + return &types.Type{ + Name: types.Name{Package: "pkg", Name: "[]" + elem.Name.Name}, + Kind: types.Slice, + Elem: elem, + CommentLines: comments, + } +} + +func testArray(elem *types.Type, comments ...string) *types.Type { + return &types.Type{ + Name: types.Name{Package: "pkg", Name: "array_" + elem.Name.Name}, + Kind: types.Array, + Elem: elem, + CommentLines: comments, + } +} + +func testMap(key, val *types.Type, comments ...string) *types.Type { + return &types.Type{ + Name: types.Name{Package: "pkg", Name: "map_" + key.Name.Name + "_" + val.Name.Name}, + Kind: types.Map, + Key: key, + Elem: val, + CommentLines: comments, + } +} + +func testAlias(name string, underlying *types.Type, comments ...string) *types.Type { + return &types.Type{ + Name: types.Name{Package: "pkg", Name: name}, + Kind: types.Alias, + Underlying: underlying, + CommentLines: comments, + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/main.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/main.go new file mode 100644 index 0000000000..4385d38381 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/main.go @@ -0,0 +1,193 @@ +/* +Copyright 2024 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. +*/ + +// validation-gen is a tool for auto-generating Validation functions. +package main + +import ( + "bytes" + "cmp" + "encoding/json" + "flag" + "fmt" + "os" + "slices" + + "github.com/spf13/pflag" + + "k8s.io/code-generator/cmd/validation-gen/validators" + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +func main() { + klog.InitFlags(nil) + args := &Args{} + + args.AddFlags(pflag.CommandLine) + if err := flag.Set("logtostderr", "true"); err != nil { + klog.Fatalf("Error: %v", err) + } + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + if err := args.Validate(); err != nil { + klog.Fatalf("Error: %v", err) + } + + if args.PrintDocs { + printDocs() + os.Exit(0) + } + + myTargets := func(context *generator.Context) []generator.Target { + return GetTargets(context, args) + } + + // Run it. + if err := gengo.Execute( + NameSystems(), + DefaultNameSystem(), + myTargets, + gengo.StdBuildTag, + pflag.Args(), + ); err != nil { + klog.Fatalf("Error: %v", err) + } + klog.V(2).Info("Completed successfully.") +} + +type Args struct { + OutputFile string + ReadOnlyPkgs []string // Always consider these as last-ditch possibilities for validations. + GoHeaderFile string + PrintDocs bool + // TestOutputRoot, when non-empty, enables coverage test fixture + // generation. For each Kind with declared rules, emits a test directory + // at /// containing one + // _test.go per version plus a shared + // main_test.go. + TestOutputRoot string + + // TestOutputFilePrefix is prepended to every emitted test fixture + // filename. Empty by default; consumers that mark generated files via a + // linguist-generated gitattributes pattern (e.g. "zz_generated.") set + // this to that prefix. + TestOutputFilePrefix string + + // TestAllowlist, when non-empty, is the path to a YAML file of + // rule-level filters to exclude from fixture generation. Each entry has + // fields apiVersion, kind, path, errorType, origin (use "*" to wildcard + // kind/path/errorType/origin) plus a required reason. + TestAllowlist string + + apidefinitions.LintArgs +} + +// AddFlags add the generator flags to the flag set. +func (args *Args) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&args.OutputFile, "output-file", "generated.validations.go", + "the name of the file to be generated") + fs.StringSliceVar(&args.ReadOnlyPkgs, "readonly-pkg", args.ReadOnlyPkgs, + "the import path of a package whose validation can be used by generated code, but is not being generated for") + fs.StringVar(&args.GoHeaderFile, "go-header-file", "", + "the path to a file containing boilerplate header text; the string \"YEAR\" will be replaced with the current 4-digit year") + fs.BoolVar(&args.PrintDocs, "docs", false, + "print documentation for supported declarative validations, and then exit") + fs.StringVar(&args.TestOutputRoot, "test-output-root", "", + "if non-empty, also emit declarative-validation coverage test fixtures under this path, organized as ///{,main}_test.go") + fs.StringVar(&args.TestOutputFilePrefix, "test-output-file-prefix", "", + "prefix prepended to every emitted test fixture filename; useful for marking files via a linguist-generated gitattributes pattern (e.g. \"zz_generated.\")") + fs.StringVar(&args.TestAllowlist, "test-allowlist", "", + "path to a YAML config file of rule-level filters to exclude from coverage fixture generation; only meaningful with --test-output-root") + apidefinitions.AddFlags(&args.LintArgs, fs) +} + +// Validate checks the given arguments. +func (args *Args) Validate() error { + if len(args.OutputFile) == 0 { + return fmt.Errorf("--output-file must be specified") + } + if args.TestAllowlist != "" && args.TestOutputRoot == "" { + return fmt.Errorf("--test-allowlist is only meaningful with --test-output-root") + } + if args.TestOutputFilePrefix != "" && args.TestOutputRoot == "" { + return fmt.Errorf("--test-output-file-prefix is only meaningful with --test-output-root") + } + + if err := apidefinitions.ValidateFlags(args.LintRules); err != nil { + return err + } + return nil +} + +func printDocs() { + // We need a fake context to init the validator plugins. + c := &generator.Context{ + Namers: namer.NameSystems{}, + Universe: types.Universe{}, + FileTypes: map[string]generator.FileType{}, + } + + // Initialize all registered validators. + validator := validators.InitGlobalValidator(c, nil) + + docs := validator.Docs() + for i := range docs { + d := &docs[i] + slices.Sort(d.Scopes) + if d.Usage == "" { + // Try to generate a usage string if none was provided. + usage := d.Tag + if len(d.Args) > 0 { + usage += "(" + for i := range d.Args { + if i > 0 { + usage += ", " + } + usage += d.Args[i].Description + } + usage += ")" + } + if len(d.Payloads) > 0 { + usage += "=" + if len(d.Payloads) == 1 { + usage += d.Payloads[0].Description + } else { + usage += "" + } + } + d.Usage = usage + } + } + slices.SortFunc(docs, func(a, b validators.TagDoc) int { + return cmp.Compare(a.Tag, b.Tag) + }) + + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + if err := encoder.Encode(docs); err != nil { + klog.Fatalf("failed to marshal docs: %v", err) + } + + fmt.Println(buf.String()) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other/doc.go new file mode 100644 index 0000000000..6e0cb7b4b5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other/doc.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* + +// This is a test package. It exists to demonstrate references to types that +// are not part of the gengo args. Even though this package purports to have +// validations, it is outside of the args used when generating output_tests, +// and so the generated could should NOT descend into these. +// +k8s:validation-gen-nolint +package other + +// +k8s:validateFalse="you should not see this outside of this pkg" +type StringType string + +// +k8s:validateFalse="you should not see this outside of this pkg" +type IntType int + +// +k8s:validateFalse="you should not see this outside of this pkg" +type StructType struct { + // +k8s:validateFalse="you should not see this outside of this pkg" + StringField string `json:"stringField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/doc.go new file mode 100644 index 0000000000..13a64d2919 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/doc.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 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. +*/ + +// Note: this selects all types in the package. +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package trivial + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct{} + +type T2 struct{} + +type E1 string + +type E2 string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/testdata/validate-false.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/testdata/validate-false.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/zz_generated.validations.go new file mode 100644 index 0000000000..7fa85b5807 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/zz_generated.validations.go @@ -0,0 +1,22 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package trivial diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/zz_generated.validations_test.go new file mode 100644 index 0000000000..510f04e229 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/trivial/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package trivial + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/doc.go new file mode 100644 index 0000000000..bb1ffb53b9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/doc.go @@ -0,0 +1,59 @@ +/* +Copyright 2024 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. +*/ + +// Note: this selects all types in the package. +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package withfieldvalidations + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + // +k8s:validateFalse="field T1.S" + S string `json:"s"` + // +k8s:validateFalse="field T1.T2" + T2 T2 `json:"t2"` + // +k8s:validateFalse="field T1.T3" + T3 T3 `json:"t3"` +} + +// Note: this has validations and is linked into T1. +type T2 struct { + // +k8s:validateFalse="field T2.S" + S string `json:"s"` +} + +// Note: this has no validations and is linked into T1. +type T3 struct { + S string `json:"s"` +} + +// Note: this has validations and is not linked into T1. +type T4 struct { + // +k8s:validateFalse="field T4.S" + S string `json:"s"` +} + +// Note: this has no validations and is not linked into T1. +type T5 struct { + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/testdata/validate-false.json new file mode 100644 index 0000000000..65581342cf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/testdata/validate-false.json @@ -0,0 +1,26 @@ +{ + "*withfieldvalidations.T1": { + "s": [ + "field T1.S" + ], + "t2": [ + "field T1.T2" + ], + "t2.s": [ + "field T2.S" + ], + "t3": [ + "field T1.T3" + ] + }, + "*withfieldvalidations.T2": { + "s": [ + "field T2.S" + ] + }, + "*withfieldvalidations.T4": { + "s": [ + "field T4.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/zz_generated.validations.go new file mode 100644 index 0000000000..6165600d00 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/zz_generated.validations.go @@ -0,0 +1,235 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package withfieldvalidations + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T2 + scheme.AddValidationFunc( + (*T2)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T2( + ctx, op, nil, /* fldPath */ + obj.(*T2), + safe.Cast[*T2](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T4 + scheme.AddValidationFunc( + (*T4)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T4( + ctx, op, nil, /* fldPath */ + obj.(*T4), + safe.Cast[*T4](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field T1.T3 + fn := func( + fldPath *field.Path, + obj, oldObj *T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T3"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T3 { + return &oldObj.T3 + }) + errs = append(errs, fn(fldPath.Child("t3"), &obj.T3, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T4 validates an instance of T4 according +// to declarative validation rules in the API schema. +func Validate_T4( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T4) (errs field.ErrorList) { + + { // field T4.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T4.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T4) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/zz_generated.validations_test.go new file mode 100644 index 0000000000..d96d788694 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_field_validations/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package withfieldvalidations + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/doc.go new file mode 100644 index 0000000000..68b6ad8cdc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 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. +*/ + +// Note: this selects all types in the package. +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package withtypevalidations + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type T1" +type T1 struct { + // +k8s:validateFalse="field T1.S" + S string `json:"s"` +} + +// Note: this has no validations. +type T2 struct{} + +// +k8s:validateFalse="type E1" +type E1 string + +// Note: this has no validations. +type E2 string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/testdata/validate-false.json new file mode 100644 index 0000000000..54d46424b6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/testdata/validate-false.json @@ -0,0 +1,15 @@ +{ + "*withtypevalidations.E1": { + "": [ + "type E1" + ] + }, + "*withtypevalidations.T1": { + "": [ + "type T1" + ], + "s": [ + "field T1.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/zz_generated.validations.go new file mode 100644 index 0000000000..0fb3e785ea --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/zz_generated.validations.go @@ -0,0 +1,121 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package withtypevalidations + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type E1 + scheme.AddValidationFunc( + (*E1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_E1( + ctx, op, nil, /* fldPath */ + obj.(*E1), + safe.Cast[*E1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_E1 validates an instance of E1 according +// to declarative validation rules in the API schema. +func Validate_E1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E1) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type E1"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T1"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field T1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/zz_generated.validations_test.go new file mode 100644 index 0000000000..97db075d26 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/all_types_match/with_type_validations/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package withtypevalidations + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go new file mode 100644 index 0000000000..21c04730f2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go @@ -0,0 +1,56 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package cohorts + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: the following are out of order on purpose. +// They should be emitted as: +// - T {ShortCircuit,Regular} +// - T c2 {ShortCircuit,Regular} +// - T c1 {ShortCircuit,Regular} +// +// +k8s:validateFalse(cohort: "c2")="type T c2 Regular" +// +k8s:validateFalse(cohort: "c1")="type T c1 Regular" +// +k8s:validateFalse(cohort: "c1", flags: "ShortCircuit")="type T c1 ShortCircuit" +// +k8s:validateFalse(cohort: "c2", flags: "ShortCircuit")="type T c2 ShortCircuit" +// +k8s:validateFalse="type T Regular" +// +k8s:validateFalse(flags: "ShortCircuit")="type T ShortCircuit" +type T struct { + TypeMeta int + + // Note: the following are out of order on purpose. + // They should be emitted as: + // - T.S {ShortCircuit,Regular} + // - T.S c2 {ShortCircuit,Regular} + // - T.S c1 {ShortCircuit,Regular} + // +k8s:validateFalse(cohort: "c2")="field T.S c2 Regular" + // +k8s:validateFalse(cohort: "c1")="field T.S c1 Regular" + // +k8s:validateFalse(cohort: "c1", flags: "ShortCircuit")="field T.S c1 ShortCircuit" + // +k8s:validateFalse(cohort: "c2", flags: "ShortCircuit")="field T.S c2 ShortCircuit" + // +k8s:validateFalse="field T.S Regular" + // +k8s:validateFalse(flags: "ShortCircuit")="field T.S ShortCircuit" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/testdata/validate-false.json new file mode 100644 index 0000000000..ad04e39912 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/testdata/validate-false.json @@ -0,0 +1,7 @@ +{ + "*cohorts.T": { + "": [ + "type T ShortCircuit" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations.go new file mode 100644 index 0000000000..bbb5c6c8a9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations.go @@ -0,0 +1,163 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package cohorts + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T + scheme.AddValidationFunc( + (*T)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T( + ctx, op, nil, /* fldPath */ + obj.(*T), + safe.Cast[*T](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T validates an instance of T according +// to declarative validation rules in the API schema. +func Validate_T( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T Regular"); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "c2" + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T c2 ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T c2 Regular"); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "c1" + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T c1 ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T c1 Regular"); len(e) != 0 { + errs = append(errs, e...) + } + }() + + // field T.TypeMeta has no validation + + { // field T.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S Regular"); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "c2" + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S c2 ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S c2 Regular"); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "c1" + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S c1 ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S c1 Regular"); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations_test.go new file mode 100644 index 0000000000..e9214fd3eb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package cohorts + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/doc.go new file mode 100644 index 0000000000..a369311265 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/doc.go @@ -0,0 +1,123 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +//nolint:unused + +// This is a test package. +// +k8s:validation-gen-nolint +package crosspkg + +import ( + "k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other" + "k8s.io/code-generator/cmd/validation-gen/output_tests/primitives" + "k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs" + "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + TypeMeta int + + // +k8s:validateTrue="field T1.PrimitivesT1" + PrimitivesT1 primitives.T1 `json:"primitivest1"` + // +k8s:validateTrue="field T1.PrimitivesT1Ptr" + PrimitivesT1Ptr *primitives.T1 `json:"primitivest1Ptr"` + // +k8s:validateTrue="field T1.PrimitivesT2" + PrimitivesT2 primitives.T2 `json:"primitivest2"` + // +k8s:validateTrue="field T1.PrimitivesT2Ptr" + PrimitivesT2Ptr *primitives.T1 `json:"primitivest2Ptr"` + // +k8s:validateTrue="field T1.PrimitivesT3" + PrimitivesT3 primitives.T3 `json:"primitivest3"` + // +k8s:validateTrue="field T1.PrimitivesT3Ptr" + PrimitivesT3Ptr *primitives.T1 `json:"primitivest3Ptr"` + // T4 and T5 are not root types in that pkg and are not linked into any + // root type's transitive graph, so they have no functions. + + // +k8s:validateTrue="field T1.TypedefsE1" + TypedefsE1 typedefs.E1 `json:"typedefse1"` + // +k8s:validateTrue="field T1.TypedefsE1Ptr" + TypedefsE1Ptr *typedefs.E1 `json:"typedefse1Ptr"` + // +k8s:validateTrue="field T1.TypedefsE2" + TypedefsE2 typedefs.E2 `json:"typedefse2"` + // +k8s:validateTrue="field T1.TypedefsE2Ptr" + TypedefsE2Ptr *typedefs.E2 `json:"typedefse2Ptr"` + // +k8s:validateTrue="field T1.TypedefsE3" + TypedefsE3 typedefs.E3 `json:"typedefse3"` + // +k8s:validateTrue="field T1.TypedefsE3Ptr" + TypedefsE3Ptr *typedefs.E3 `json:"typedefse3Ptr"` + // +k8s:validateTrue="field T1.TypedefsE4" + TypedefsE4 typedefs.E4 `json:"typedefse4"` + // +k8s:validateTrue="field T1.TypedefsE4Ptr" + TypedefsE4Ptr *typedefs.E4 `json:"typedefse4Ptr"` + + // +k8s:validateTrue="field T1.OtherString" + // +k8s:opaqueType + OtherString other.StringType `json:"otherString"` + // +k8s:validateTrue="field T1.OtherStringPtr" + // +k8s:opaqueType + OtherStringPtr *other.StringType `json:"otherStringPtr"` + // +k8s:validateTrue="field T1.OtherInt" + // +k8s:opaqueType + OtherInt other.IntType `json:"otherInt"` + // +k8s:validateTrue="field T1.OtherIntPtr" + // +k8s:opaqueType + OtherIntPtr *other.IntType `json:"otherIntPtr"` + // +k8s:validateTrue="field T1.OtherStruct" + // +k8s:opaqueType + OtherStruct other.StructType `json:"otherStruct"` + // +k8s:validateTrue="field T1.OtherStructPtr" + // +k8s:opaqueType + OtherStructPtr *other.StructType `json:"otherStructPtr"` + + // +k8s:validateTrue="field T1.SliceOfOtherStruct" + // +k8s:eachVal=+k8s:validateTrue="field T1.SliceOfOtherStruct values" + // +k8s:eachVal=+k8s:opaqueType + SliceOfOtherStruct []other.StructType `json:"sliceOfOtherStruct"` + + // +k8s:validateTrue="field T1.ListMapOfOtherStruct" + // +k8s:eachVal=+k8s:validateTrue="field T1.SliceOfOtherStruct values" + // +k8s:listType=map + // +k8s:listMapKey=stringField + // +k8s:eachVal=+k8s:opaqueType + ListMapOfOtherStruct []other.StructType `json:"listMapOfOtherStruct"` + + // +k8s:validateTrue="field T1.MapOfOtherStringToOtherStruct" + // +k8s:eachKey=+k8s:validateTrue="field T1.MapOfOtherStringToOtherStruct keys" + // +k8s:eachVal=+k8s:validateTrue="field T1.MapOfOtherStringToOtherStruct values" + // +k8s:eachKey=+k8s:opaqueType + // +k8s:eachVal=+k8s:opaqueType + MapOfOtherStringToOtherStruct map[other.StringType]other.StructType `json:"mapOfOtherStringToOtherStruct"` +} + +// TODO: the validateFalse test fixture doesn't handle map and slice types, and +// fixing it requires fixing randfill. That is a tomorrow problem. For now, the +// following types have been tested to fail without +k8s:opaqueType. + +/* +// +k8s:validateTrue="type TypedefSliceOther" +// +k8s:eachVal=+k8s:opaqueType +type TypedefSliceOther []other.StructType + +// +k8s:validateTrue="type TypedefMapOther" +// +k8s:eachKey=+k8s:opaqueType +// +k8s:eachVal=+k8s:opaqueType +type TypedefMapOther map[other.StringType]other.StructType +*/ diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/testdata/validate-false.json new file mode 100644 index 0000000000..4793b76adb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/testdata/validate-false.json @@ -0,0 +1,202 @@ +{ + "*crosspkg.T1": { + "primitivest1.anothert2.b": [ + "field T2.B" + ], + "primitivest1.anothert2.f": [ + "field T2.F" + ], + "primitivest1.anothert2.i": [ + "field T2.I" + ], + "primitivest1.anothert2.s": [ + "field T2.S" + ], + "primitivest1.b": [ + "field T1.B" + ], + "primitivest1.f": [ + "field T1.F" + ], + "primitivest1.i": [ + "field T1.I" + ], + "primitivest1.s": [ + "field T1.S" + ], + "primitivest1.t2": [ + "field T1.T2" + ], + "primitivest1.t2.b": [ + "field T2.B" + ], + "primitivest1.t2.f": [ + "field T2.F" + ], + "primitivest1.t2.i": [ + "field T2.I" + ], + "primitivest1.t2.s": [ + "field T2.S" + ], + "primitivest1Ptr.anothert2.b": [ + "field T2.B" + ], + "primitivest1Ptr.anothert2.f": [ + "field T2.F" + ], + "primitivest1Ptr.anothert2.i": [ + "field T2.I" + ], + "primitivest1Ptr.anothert2.s": [ + "field T2.S" + ], + "primitivest1Ptr.b": [ + "field T1.B" + ], + "primitivest1Ptr.f": [ + "field T1.F" + ], + "primitivest1Ptr.i": [ + "field T1.I" + ], + "primitivest1Ptr.s": [ + "field T1.S" + ], + "primitivest1Ptr.t2": [ + "field T1.T2" + ], + "primitivest1Ptr.t2.b": [ + "field T2.B" + ], + "primitivest1Ptr.t2.f": [ + "field T2.F" + ], + "primitivest1Ptr.t2.i": [ + "field T2.I" + ], + "primitivest1Ptr.t2.s": [ + "field T2.S" + ], + "primitivest2.b": [ + "field T2.B" + ], + "primitivest2.f": [ + "field T2.F" + ], + "primitivest2.i": [ + "field T2.I" + ], + "primitivest2.s": [ + "field T2.S" + ], + "primitivest2Ptr.anothert2.b": [ + "field T2.B" + ], + "primitivest2Ptr.anothert2.f": [ + "field T2.F" + ], + "primitivest2Ptr.anothert2.i": [ + "field T2.I" + ], + "primitivest2Ptr.anothert2.s": [ + "field T2.S" + ], + "primitivest2Ptr.b": [ + "field T1.B" + ], + "primitivest2Ptr.f": [ + "field T1.F" + ], + "primitivest2Ptr.i": [ + "field T1.I" + ], + "primitivest2Ptr.s": [ + "field T1.S" + ], + "primitivest2Ptr.t2": [ + "field T1.T2" + ], + "primitivest2Ptr.t2.b": [ + "field T2.B" + ], + "primitivest2Ptr.t2.f": [ + "field T2.F" + ], + "primitivest2Ptr.t2.i": [ + "field T2.I" + ], + "primitivest2Ptr.t2.s": [ + "field T2.S" + ], + "primitivest3Ptr.anothert2.b": [ + "field T2.B" + ], + "primitivest3Ptr.anothert2.f": [ + "field T2.F" + ], + "primitivest3Ptr.anothert2.i": [ + "field T2.I" + ], + "primitivest3Ptr.anothert2.s": [ + "field T2.S" + ], + "primitivest3Ptr.b": [ + "field T1.B" + ], + "primitivest3Ptr.f": [ + "field T1.F" + ], + "primitivest3Ptr.i": [ + "field T1.I" + ], + "primitivest3Ptr.s": [ + "field T1.S" + ], + "primitivest3Ptr.t2": [ + "field T1.T2" + ], + "primitivest3Ptr.t2.b": [ + "field T2.B" + ], + "primitivest3Ptr.t2.f": [ + "field T2.F" + ], + "primitivest3Ptr.t2.i": [ + "field T2.I" + ], + "primitivest3Ptr.t2.s": [ + "field T2.S" + ], + "typedefse1": [ + "type E1" + ], + "typedefse1Ptr": [ + "type E1" + ], + "typedefse2": [ + "type E2" + ], + "typedefse2Ptr": [ + "type E2" + ], + "typedefse3": [ + "type E3" + ], + "typedefse3Ptr": [ + "type E3" + ], + "typedefse4": [ + "type E4" + ], + "typedefse4.s": [ + "field T2.S" + ], + "typedefse4Ptr": [ + "type E4" + ], + "typedefse4Ptr.s": [ + "field T2.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/zz_generated.validations.go new file mode 100644 index 0000000000..012b7b67b6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/zz_generated.validations.go @@ -0,0 +1,679 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package crosspkg + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + other "k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other" + primitives "k8s.io/code-generator/cmd/validation-gen/output_tests/primitives" + typedefs "k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + // field T1.TypeMeta has no validation + + { // field T1.PrimitivesT1 + fn := func( + fldPath *field.Path, + obj, oldObj *primitives.T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.PrimitivesT1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, primitives.Validate_T1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *primitives.T1 { + return &oldObj.PrimitivesT1 + }) + errs = append(errs, fn(fldPath.Child("primitivest1"), &obj.PrimitivesT1, oldVal, oldObj != nil)...) + } + + { // field T1.PrimitivesT1Ptr + fn := func( + fldPath *field.Path, + obj, oldObj *primitives.T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.PrimitivesT1Ptr"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, primitives.Validate_T1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *primitives.T1 { + return oldObj.PrimitivesT1Ptr + }) + errs = append(errs, fn(fldPath.Child("primitivest1Ptr"), obj.PrimitivesT1Ptr, oldVal, oldObj != nil)...) + } + + { // field T1.PrimitivesT2 + fn := func( + fldPath *field.Path, + obj, oldObj *primitives.T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.PrimitivesT2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, primitives.Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *primitives.T2 { + return &oldObj.PrimitivesT2 + }) + errs = append(errs, fn(fldPath.Child("primitivest2"), &obj.PrimitivesT2, oldVal, oldObj != nil)...) + } + + { // field T1.PrimitivesT2Ptr + fn := func( + fldPath *field.Path, + obj, oldObj *primitives.T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.PrimitivesT2Ptr"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, primitives.Validate_T1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *primitives.T1 { + return oldObj.PrimitivesT2Ptr + }) + errs = append(errs, fn(fldPath.Child("primitivest2Ptr"), obj.PrimitivesT2Ptr, oldVal, oldObj != nil)...) + } + + { // field T1.PrimitivesT3 + fn := func( + fldPath *field.Path, + obj, oldObj *primitives.T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.PrimitivesT3"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *primitives.T3 { + return &oldObj.PrimitivesT3 + }) + errs = append(errs, fn(fldPath.Child("primitivest3"), &obj.PrimitivesT3, oldVal, oldObj != nil)...) + } + + { // field T1.PrimitivesT3Ptr + fn := func( + fldPath *field.Path, + obj, oldObj *primitives.T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.PrimitivesT3Ptr"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, primitives.Validate_T1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *primitives.T1 { + return oldObj.PrimitivesT3Ptr + }) + errs = append(errs, fn(fldPath.Child("primitivest3Ptr"), obj.PrimitivesT3Ptr, oldVal, oldObj != nil)...) + } + + { // field T1.TypedefsE1 + fn := func( + fldPath *field.Path, + obj, oldObj *typedefs.E1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.TypedefsE1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, typedefs.Validate_E1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *typedefs.E1 { + return &oldObj.TypedefsE1 + }) + errs = append(errs, fn(fldPath.Child("typedefse1"), &obj.TypedefsE1, oldVal, oldObj != nil)...) + } + + { // field T1.TypedefsE1Ptr + fn := func( + fldPath *field.Path, + obj, oldObj *typedefs.E1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.TypedefsE1Ptr"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, typedefs.Validate_E1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *typedefs.E1 { + return oldObj.TypedefsE1Ptr + }) + errs = append(errs, fn(fldPath.Child("typedefse1Ptr"), obj.TypedefsE1Ptr, oldVal, oldObj != nil)...) + } + + { // field T1.TypedefsE2 + fn := func( + fldPath *field.Path, + obj, oldObj *typedefs.E2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.TypedefsE2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, typedefs.Validate_E2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *typedefs.E2 { + return &oldObj.TypedefsE2 + }) + errs = append(errs, fn(fldPath.Child("typedefse2"), &obj.TypedefsE2, oldVal, oldObj != nil)...) + } + + { // field T1.TypedefsE2Ptr + fn := func( + fldPath *field.Path, + obj, oldObj *typedefs.E2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.TypedefsE2Ptr"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, typedefs.Validate_E2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *typedefs.E2 { + return oldObj.TypedefsE2Ptr + }) + errs = append(errs, fn(fldPath.Child("typedefse2Ptr"), obj.TypedefsE2Ptr, oldVal, oldObj != nil)...) + } + + { // field T1.TypedefsE3 + fn := func( + fldPath *field.Path, + obj, oldObj *typedefs.E3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.TypedefsE3"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, typedefs.Validate_E3(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *typedefs.E3 { + return &oldObj.TypedefsE3 + }) + errs = append(errs, fn(fldPath.Child("typedefse3"), &obj.TypedefsE3, oldVal, oldObj != nil)...) + } + + { // field T1.TypedefsE3Ptr + fn := func( + fldPath *field.Path, + obj, oldObj *typedefs.E3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.TypedefsE3Ptr"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, typedefs.Validate_E3(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *typedefs.E3 { + return oldObj.TypedefsE3Ptr + }) + errs = append(errs, fn(fldPath.Child("typedefse3Ptr"), obj.TypedefsE3Ptr, oldVal, oldObj != nil)...) + } + + { // field T1.TypedefsE4 + fn := func( + fldPath *field.Path, + obj, oldObj *typedefs.E4, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.TypedefsE4"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, typedefs.Validate_E4(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *typedefs.E4 { + return &oldObj.TypedefsE4 + }) + errs = append(errs, fn(fldPath.Child("typedefse4"), &obj.TypedefsE4, oldVal, oldObj != nil)...) + } + + { // field T1.TypedefsE4Ptr + fn := func( + fldPath *field.Path, + obj, oldObj *typedefs.E4, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.TypedefsE4Ptr"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, typedefs.Validate_E4(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *typedefs.E4 { + return oldObj.TypedefsE4Ptr + }) + errs = append(errs, fn(fldPath.Child("typedefse4Ptr"), obj.TypedefsE4Ptr, oldVal, oldObj != nil)...) + } + + { // field T1.OtherString + fn := func( + fldPath *field.Path, + obj, oldObj *other.StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.OtherString"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *other.StringType { + return &oldObj.OtherString + }) + errs = append(errs, fn(fldPath.Child("otherString"), &obj.OtherString, oldVal, oldObj != nil)...) + } + + { // field T1.OtherStringPtr + fn := func( + fldPath *field.Path, + obj, oldObj *other.StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.OtherStringPtr"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *other.StringType { + return oldObj.OtherStringPtr + }) + errs = append(errs, fn(fldPath.Child("otherStringPtr"), obj.OtherStringPtr, oldVal, oldObj != nil)...) + } + + { // field T1.OtherInt + fn := func( + fldPath *field.Path, + obj, oldObj *other.IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.OtherInt"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *other.IntType { + return &oldObj.OtherInt + }) + errs = append(errs, fn(fldPath.Child("otherInt"), &obj.OtherInt, oldVal, oldObj != nil)...) + } + + { // field T1.OtherIntPtr + fn := func( + fldPath *field.Path, + obj, oldObj *other.IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.OtherIntPtr"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *other.IntType { + return oldObj.OtherIntPtr + }) + errs = append(errs, fn(fldPath.Child("otherIntPtr"), obj.OtherIntPtr, oldVal, oldObj != nil)...) + } + + { // field T1.OtherStruct + fn := func( + fldPath *field.Path, + obj, oldObj *other.StructType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *other.StructType { + return &oldObj.OtherStruct + }) + errs = append(errs, fn(fldPath.Child("otherStruct"), &obj.OtherStruct, oldVal, oldObj != nil)...) + } + + { // field T1.OtherStructPtr + fn := func( + fldPath *field.Path, + obj, oldObj *other.StructType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.OtherStructPtr"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *other.StructType { + return oldObj.OtherStructPtr + }) + errs = append(errs, fn(fldPath.Child("otherStructPtr"), obj.OtherStructPtr, oldVal, oldObj != nil)...) + } + + { // field T1.SliceOfOtherStruct + fn := func( + fldPath *field.Path, + obj, oldObj []other.StructType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.SliceOfOtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *other.StructType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.SliceOfOtherStruct values") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) []other.StructType { + return oldObj.SliceOfOtherStruct + }) + errs = append(errs, fn(fldPath.Child("sliceOfOtherStruct"), obj.SliceOfOtherStruct, oldVal, oldObj != nil)...) + } + + { // field T1.ListMapOfOtherStruct + fn := func( + fldPath *field.Path, + obj, oldObj []other.StructType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.ListMapOfOtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *other.StructType, b *other.StructType) bool { return a.StringField == b.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *other.StructType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.SliceOfOtherStruct values") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *other.StructType, b *other.StructType) bool { return a.StringField == b.StringField }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) []other.StructType { + return oldObj.ListMapOfOtherStruct + }) + errs = append(errs, fn(fldPath.Child("listMapOfOtherStruct"), obj.ListMapOfOtherStruct, oldVal, oldObj != nil)...) + } + + { // field T1.MapOfOtherStringToOtherStruct + fn := func( + fldPath *field.Path, + obj, oldObj map[other.StringType]other.StructType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *other.StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.MapOfOtherStringToOtherStruct keys") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.MapOfOtherStringToOtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *other.StructType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.MapOfOtherStringToOtherStruct values") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) map[other.StringType]other.StructType { + return oldObj.MapOfOtherStringToOtherStruct + }) + errs = append(errs, fn(fldPath.Child("mapOfOtherStringToOtherStruct"), obj.MapOfOtherStringToOtherStruct, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/zz_generated.validations_test.go new file mode 100644 index 0000000000..0988aa7296 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/cross_pkg/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package crosspkg + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/doc.go new file mode 100644 index 0000000000..f72747773b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/doc.go @@ -0,0 +1,113 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse +// +k8s:validation-gen-test-targets + +// This is a test package. +// +k8s:validation-gen-nolint +package elidenovalidations + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + TypeMeta int + + HasTypeVal HasTypeVal `json:"hasTypeVal"` + + HasFieldVal HasFieldVal `json:"hasFieldVal"` + + HasNoVal HasNoVal `json:"hasNoVal"` + + // +k8s:validateFalse="field T1.HasNoValFieldVal" + HasNoValFieldVal HasNoVal `json:"hasNoValFieldVal"` + + ValidatedSlice TypedefSliceWithValidations `json:"validatedSlice"` + ValidatedMap TypedefMapWithValidations `json:"validatedMap"` + ValidatedMapKey TypedefMapWithKeyValidations `json:"validatedMapKey"` + + DeepValidatedSlice DeepTypedefSlice `json:"deepValidatedSlice"` + DeepValidatedMap DeepTypedefMap `json:"deepValidatedMap"` + + DoubleDeepValidatedSlice DoubleDeepTypedefSlice `json:"doubleDeepValidatedSlice "` + DoubleDeepValidatedMap DoubleDeepTypedefMap `json:"doubleDeepValidatedMap"` +} + +// +k8s:validateFalse="type HasTypeVal" +type HasTypeVal struct { + // Note: no field validation. + S string `json:"s"` +} + +// Note: no type validation. +type HasFieldVal struct { + // +k8s:validateFalse="field HasFieldVal.S" + S string `json:"s"` +} + +// Note: no type validation. +type HasNoVal struct { + // Note: no field validation. + S string `json:"s"` +} + +// +k8s:validateFalse="type HasTypeValNotLinked" +type HasTypeValNotLinked struct { + // Note: no field validation. + S string `json:"s"` +} + +// Note: no type validation. +type HasFieldValNotLinked struct { + // +k8s:validateFalse="field HasFieldValNotLinked.S" + S string `json:"s"` +} + +// Note: no type validation. +type HasNoValNotLinked struct { + // Note: no field validation. + S string `json:"s"` +} + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct { + S string `json:"s"` +} + +// +k8s:validateFalse="type ValidatedKeyType" +type ValidatedKeyType string + +type TypedefSliceWithValidations []OtherStruct + +type TypedefMapWithValidations map[string]OtherStruct + +type TypedefMapWithKeyValidations map[ValidatedKeyType]string + +// FIXME: The following validation is not being generated for DoubleDeepTypedefSlice. +// Validation-gen is ignoring this validation, because Go directly translates +// DoubleDeepTypedefSlice to TypedefSliceWithValidations. +// +k8s:eachVal=+k8s:validateFalse="type DeepTypedefSlice" +type DeepTypedefSlice TypedefSliceWithValidations + +type DeepTypedefMap TypedefMapWithValidations + +type DoubleDeepTypedefSlice DeepTypedefSlice + +type DoubleDeepTypedefMap DeepTypedefMap diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/testdata/validate-false.json new file mode 100644 index 0000000000..70fda952fd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/testdata/validate-false.json @@ -0,0 +1,55 @@ +{ + "*elidenovalidations.T1": { + "deepValidatedMap[%ɜ¤0ȻG炕炎鷖Ʊ]": [ + "type OtherStruct" + ], + "deepValidatedMap[tƓ晲銩靨能鷸ȳ琍殪]": [ + "type OtherStruct" + ], + "deepValidatedSlice[0]": [ + "type DeepTypedefSlice", + "type OtherStruct" + ], + "deepValidatedSlice[1]": [ + "type DeepTypedefSlice", + "type OtherStruct" + ], + "doubleDeepValidatedMap[滧ǖvq]": [ + "type OtherStruct" + ], + "doubleDeepValidatedMap[黰¢ơđ?ȵɓf*Z迖瓼轊]": [ + "type OtherStruct" + ], + "doubleDeepValidatedSlice [0]": [ + "type OtherStruct" + ], + "doubleDeepValidatedSlice [1]": [ + "type OtherStruct" + ], + "hasFieldVal.s": [ + "field HasFieldVal.S" + ], + "hasNoValFieldVal": [ + "field T1.HasNoValFieldVal" + ], + "hasTypeVal": [ + "type HasTypeVal" + ], + "validatedMapKey": [ + "type ValidatedKeyType", + "type ValidatedKeyType" + ], + "validatedMap[ʅȀʙĄĥ腲ʤaLJ趴]": [ + "type OtherStruct" + ], + "validatedMap[赖{³*Ę鄏þƿ髈儱Ŀ蒫÷K]": [ + "type OtherStruct" + ], + "validatedSlice[0]": [ + "type OtherStruct" + ], + "validatedSlice[1]": [ + "type OtherStruct" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations.go new file mode 100644 index 0000000000..fb41e0374c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations.go @@ -0,0 +1,471 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package elidenovalidations + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_DeepTypedefMap validates an instance of DeepTypedefMap according +// to declarative validation rules in the API schema. +func Validate_DeepTypedefMap( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj DeepTypedefMap) (errs field.ErrorList) { + + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_DeepTypedefSlice validates an instance of DeepTypedefSlice according +// to declarative validation rules in the API schema. +func Validate_DeepTypedefSlice( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj DeepTypedefSlice) (errs field.ErrorList) { + + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type DeepTypedefSlice") + }); len(e) != 0 { + errs = append(errs, e...) + } + + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_DoubleDeepTypedefMap validates an instance of DoubleDeepTypedefMap according +// to declarative validation rules in the API schema. +func Validate_DoubleDeepTypedefMap( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj DoubleDeepTypedefMap) (errs field.ErrorList) { + + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_DoubleDeepTypedefSlice validates an instance of DoubleDeepTypedefSlice according +// to declarative validation rules in the API schema. +func Validate_DoubleDeepTypedefSlice( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj DoubleDeepTypedefSlice) (errs field.ErrorList) { + + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_HasFieldVal validates an instance of HasFieldVal according +// to declarative validation rules in the API schema. +func Validate_HasFieldVal( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *HasFieldVal) (errs field.ErrorList) { + + { // field HasFieldVal.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field HasFieldVal.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *HasFieldVal) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_HasTypeVal validates an instance of HasTypeVal according +// to declarative validation rules in the API schema. +func Validate_HasTypeVal( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *HasTypeVal) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type HasTypeVal"); len(e) != 0 { + errs = append(errs, e...) + } + + // field HasTypeVal.S has no validation + return errs +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field OtherStruct.S has no validation + return errs +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + // field T1.TypeMeta has no validation + + { // field T1.HasTypeVal + fn := func( + fldPath *field.Path, + obj, oldObj *HasTypeVal, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_HasTypeVal(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *HasTypeVal { + return &oldObj.HasTypeVal + }) + errs = append(errs, fn(fldPath.Child("hasTypeVal"), &obj.HasTypeVal, oldVal, oldObj != nil)...) + } + + { // field T1.HasFieldVal + fn := func( + fldPath *field.Path, + obj, oldObj *HasFieldVal, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_HasFieldVal(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *HasFieldVal { + return &oldObj.HasFieldVal + }) + errs = append(errs, fn(fldPath.Child("hasFieldVal"), &obj.HasFieldVal, oldVal, oldObj != nil)...) + } + + // field T1.HasNoVal has no validation + + { // field T1.HasNoValFieldVal + fn := func( + fldPath *field.Path, + obj, oldObj *HasNoVal, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.HasNoValFieldVal"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *HasNoVal { + return &oldObj.HasNoValFieldVal + }) + errs = append(errs, fn(fldPath.Child("hasNoValFieldVal"), &obj.HasNoValFieldVal, oldVal, oldObj != nil)...) + } + + { // field T1.ValidatedSlice + fn := func( + fldPath *field.Path, + obj, oldObj TypedefSliceWithValidations, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_TypedefSliceWithValidations(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) TypedefSliceWithValidations { + return oldObj.ValidatedSlice + }) + errs = append(errs, fn(fldPath.Child("validatedSlice"), obj.ValidatedSlice, oldVal, oldObj != nil)...) + } + + { // field T1.ValidatedMap + fn := func( + fldPath *field.Path, + obj, oldObj TypedefMapWithValidations, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_TypedefMapWithValidations(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) TypedefMapWithValidations { + return oldObj.ValidatedMap + }) + errs = append(errs, fn(fldPath.Child("validatedMap"), obj.ValidatedMap, oldVal, oldObj != nil)...) + } + + { // field T1.ValidatedMapKey + fn := func( + fldPath *field.Path, + obj, oldObj TypedefMapWithKeyValidations, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_TypedefMapWithKeyValidations(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) TypedefMapWithKeyValidations { + return oldObj.ValidatedMapKey + }) + errs = append(errs, fn(fldPath.Child("validatedMapKey"), obj.ValidatedMapKey, oldVal, oldObj != nil)...) + } + + { // field T1.DeepValidatedSlice + fn := func( + fldPath *field.Path, + obj, oldObj DeepTypedefSlice, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_DeepTypedefSlice(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) DeepTypedefSlice { + return oldObj.DeepValidatedSlice + }) + errs = append(errs, fn(fldPath.Child("deepValidatedSlice"), obj.DeepValidatedSlice, oldVal, oldObj != nil)...) + } + + { // field T1.DeepValidatedMap + fn := func( + fldPath *field.Path, + obj, oldObj DeepTypedefMap, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_DeepTypedefMap(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) DeepTypedefMap { + return oldObj.DeepValidatedMap + }) + errs = append(errs, fn(fldPath.Child("deepValidatedMap"), obj.DeepValidatedMap, oldVal, oldObj != nil)...) + } + + { // field T1.DoubleDeepValidatedSlice + fn := func( + fldPath *field.Path, + obj, oldObj DoubleDeepTypedefSlice, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_DoubleDeepTypedefSlice(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) DoubleDeepTypedefSlice { + return oldObj.DoubleDeepValidatedSlice + }) + errs = append(errs, fn(fldPath.Child("doubleDeepValidatedSlice "), obj.DoubleDeepValidatedSlice, oldVal, oldObj != nil)...) + } + + { // field T1.DoubleDeepValidatedMap + fn := func( + fldPath *field.Path, + obj, oldObj DoubleDeepTypedefMap, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_DoubleDeepTypedefMap(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) DoubleDeepTypedefMap { + return oldObj.DoubleDeepValidatedMap + }) + errs = append(errs, fn(fldPath.Child("doubleDeepValidatedMap"), obj.DoubleDeepValidatedMap, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TypedefMapWithKeyValidations validates an instance of TypedefMapWithKeyValidations according +// to declarative validation rules in the API schema. +func Validate_TypedefMapWithKeyValidations( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj TypedefMapWithKeyValidations) (errs field.ErrorList) { + + // iterate the map and call the key type's validation function + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, Validate_ValidatedKeyType); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_TypedefMapWithValidations validates an instance of TypedefMapWithValidations according +// to declarative validation rules in the API schema. +func Validate_TypedefMapWithValidations( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj TypedefMapWithValidations) (errs field.ErrorList) { + + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_TypedefSliceWithValidations validates an instance of TypedefSliceWithValidations according +// to declarative validation rules in the API schema. +func Validate_TypedefSliceWithValidations( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj TypedefSliceWithValidations) (errs field.ErrorList) { + + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ValidatedKeyType validates an instance of ValidatedKeyType according +// to declarative validation rules in the API schema. +func Validate_ValidatedKeyType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ValidatedKeyType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ValidatedKeyType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations_coverage_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations_coverage_test.go new file mode 100644 index 0000000000..f9d260d46a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations_coverage_test.go @@ -0,0 +1,81 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package elidenovalidations + +import ( + fmt "fmt" + os "os" + testing "testing" + + schema "k8s.io/apimachinery/pkg/runtime/schema" + coverage "k8s.io/apimachinery/pkg/test/coverage" +) + +func init() { + coverage.RegisterDeclaredRules( + schema.GroupVersionKind{Group: "k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations", Version: "elidenovalidations", Kind: "T1"}, + coverage.FieldRules{ + "deepValidatedMap[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "deepValidatedSlice[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "doubleDeepValidatedMap[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "doubleDeepValidatedSlice [*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "hasFieldVal.s": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "hasNoValFieldVal": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "hasTypeVal": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "validatedMapKey": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "validatedMap[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "validatedSlice[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + }, + ) +} + +func TestMain(m *testing.M) { + code := m.Run() + if err := coverage.AssertDeclarativeCoverage(); err != nil { + fmt.Fprintln(os.Stderr, err) + if code == 0 { + code = 1 + } + } + os.Exit(code) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations_test.go new file mode 100644 index 0000000000..c8a8a7fb06 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/elide_no_validations/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package elidenovalidations + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go new file mode 100644 index 0000000000..bbfbed6fdf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/doc.go @@ -0,0 +1,48 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package embedded + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + TypeMeta int + + // NOTE: It's weird to have IntField in both, but Go allows it. + T2 `json:""` + *T3 `json:""` +} + +type T2 struct { + // +k8s:validateFalse="T2.IntField" + IntField int `json:"intField"` +} + +type T3 struct { + // +k8s:validateFalse="T3.StringField" + StringField string `json:"stringField"` + + // +k8s:validateFalse="T3.IntField" + IntField int `json:"intField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/testdata/validate-false.json new file mode 100644 index 0000000000..6c05395412 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/testdata/validate-false.json @@ -0,0 +1,13 @@ +{ + "*embedded.T1": { + "T2.intField": [ + "T2.IntField" + ], + "T3.intField": [ + "T3.IntField" + ], + "T3.stringField": [ + "T3.StringField" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations.go new file mode 100644 index 0000000000..c6be187574 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations.go @@ -0,0 +1,201 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package embedded + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + // field T1.TypeMeta has no validation + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(safe.Value(fldPath, func() *field.Path { return fldPath.Child("T2") }), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field T1.T3 + fn := func( + fldPath *field.Path, + obj, oldObj *T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T3(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T3 { + return oldObj.T3 + }) + errs = append(errs, fn(safe.Value(fldPath, func() *field.Path { return fldPath.Child("T3") }), obj.T3, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T2.IntField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T3 validates an instance of T3 according +// to declarative validation rules in the API schema. +func Validate_T3( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T3) (errs field.ErrorList) { + + { // field T3.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T3.StringField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T3) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field T3.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T3.IntField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T3) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations_test.go new file mode 100644 index 0000000000..a71c048db4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/embedded/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package embedded + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/generate.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/generate.go new file mode 100644 index 0000000000..14fd870eba --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/generate.go @@ -0,0 +1,22 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2024 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. +*/ + +// Ignore this file to prevent zz_generated for this package + +//go:generate go run k8s.io/code-generator/cmd/validation-gen --output-file zz_generated.validations.go --go-header-file=../../../examples/hack/boilerplate.go.txt ./... +package outputtests diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/doc.go new file mode 100644 index 0000000000..0e396c7cf5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/doc.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package keys + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.MapField" + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapField(keys)" + MapField map[string]string `json:"mapField"` + + // +k8s:validateFalse="field Struct.MapTypedefField" + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapTypedefField(keys)" + MapTypedefField map[UnvalidatedStringType]string `json:"mapTypedefField"` + + // +k8s:validateFalse="field Struct.MapValidatedTypedefField" + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapValidatedTypedefField(keys)" + MapValidatedTypedefField map[ValidatedStringType]string `json:"mapValidatedTypedefField"` + + // +k8s:validateFalse="field Struct.MapTypeField" + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapTypeField(keys)" + MapTypeField UnvalidatedMapType `json:"mapTypeField"` + + // +k8s:validateFalse="field Struct.ValidatedMapTypeField" + // +k8s:eachKey=+k8s:validateFalse="field Struct.ValidatedMapTypeField(keys)" + ValidatedMapTypeField ValidatedMapType `json:"validatedMapTypeField"` +} + +// Note: no validations. +type UnvalidatedStringType string + +// +k8s:validateFalse="ValidatedStringType" +type ValidatedStringType string + +// Note: no validations. +type UnvalidatedMapType map[string]string + +// +k8s:validateFalse="ValidatedMapType" +// +k8s:eachKey=+k8s:validateFalse="type ValidatedMapType(keys)" +type ValidatedMapType map[string]string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/testdata/validate-false.json new file mode 100644 index 0000000000..e8cf6cebfa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/testdata/validate-false.json @@ -0,0 +1,37 @@ +{ + "*keys.Struct": { + "": [ + "type Struct" + ], + "mapField": [ + "field Struct.MapField", + "field Struct.MapField(keys)", + "field Struct.MapField(keys)" + ], + "mapTypeField": [ + "field Struct.MapTypeField", + "field Struct.MapTypeField(keys)", + "field Struct.MapTypeField(keys)" + ], + "mapTypedefField": [ + "field Struct.MapTypedefField", + "field Struct.MapTypedefField(keys)", + "field Struct.MapTypedefField(keys)" + ], + "mapValidatedTypedefField": [ + "ValidatedStringType", + "ValidatedStringType", + "field Struct.MapValidatedTypedefField", + "field Struct.MapValidatedTypedefField(keys)", + "field Struct.MapValidatedTypedefField(keys)" + ], + "validatedMapTypeField": [ + "ValidatedMapType", + "field Struct.ValidatedMapTypeField", + "field Struct.ValidatedMapTypeField(keys)", + "field Struct.ValidatedMapTypeField(keys)", + "type ValidatedMapType(keys)", + "type ValidatedMapType(keys)" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/zz_generated.validations.go new file mode 100644 index 0000000000..d79569aa2b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/zz_generated.validations.go @@ -0,0 +1,260 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package keys + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[UnvalidatedStringType]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UnvalidatedStringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[UnvalidatedStringType]string { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[ValidatedStringType]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ValidatedStringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapValidatedTypedefField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapValidatedTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the key type's validation function + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, Validate_ValidatedStringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[ValidatedStringType]string { + return oldObj.MapValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapValidatedTypedefField"), obj.MapValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypeField + fn := func( + fldPath *field.Path, + obj, oldObj UnvalidatedMapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypeField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypeField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) UnvalidatedMapType { + return oldObj.MapTypeField + }) + errs = append(errs, fn(fldPath.Child("mapTypeField"), obj.MapTypeField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedMapTypeField + fn := func( + fldPath *field.Path, + obj, oldObj ValidatedMapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ValidatedMapTypeField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ValidatedMapTypeField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ValidatedMapType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ValidatedMapType { + return oldObj.ValidatedMapTypeField + }) + errs = append(errs, fn(fldPath.Child("validatedMapTypeField"), obj.ValidatedMapTypeField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ValidatedMapType validates an instance of ValidatedMapType according +// to declarative validation rules in the API schema. +func Validate_ValidatedMapType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ValidatedMapType) (errs field.ErrorList) { + + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ValidatedMapType(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "ValidatedMapType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ValidatedStringType validates an instance of ValidatedStringType according +// to declarative validation rules in the API schema. +func Validate_ValidatedStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ValidatedStringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "ValidatedStringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/zz_generated.validations_test.go new file mode 100644 index 0000000000..7cc3aa090f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/keys/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package keys + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go new file mode 100644 index 0000000000..a8017edc07 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/doc.go @@ -0,0 +1,55 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package mapofprimitive + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.MapField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]" + MapField map[string]string `json:"mapField"` + + // +k8s:validateFalse="field Struct.MapTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapTypedefField[*]" + MapTypedefField map[string]StringType `json:"mapTypedefField"` + + UnvalidatedMapField map[string]string `json:"UnvalidatedMapField"` + + // +k8s:validateFalse="field Struct.MapPtrField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrField[*]" + MapPtrField map[string]*string `json:"mapPtrField"` + + // +k8s:validateFalse="field Struct.MapPtrTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrTypedefField[*]" + MapPtrTypedefField map[string]*StringType `json:"mapPtrTypedefField"` + + UnvalidatedMapPtrField map[string]*string `json:"UnvalidatedMapPtrField"` +} + +// +k8s:validateFalse="type StringType" +type StringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/testdata/validate-false.json new file mode 100644 index 0000000000..b7758a6448 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/testdata/validate-false.json @@ -0,0 +1,47 @@ +{ + "*mapofprimitive.Struct": { + "": [ + "type Struct" + ], + "mapField": [ + "field Struct.MapField" + ], + "mapField[ÀYǎ3g成oɜH偩j0âȰ]": [ + "field Struct.MapField[*]" + ], + "mapField[岯Ȉ\u0026\u003c沲3]": [ + "field Struct.MapField[*]" + ], + "mapPtrField": [ + "field Struct.MapPtrField" + ], + "mapPtrField[`ȝ懿沇]": [ + "field Struct.MapPtrField[*]" + ], + "mapPtrField[ǝ祇Fæƭō]": [ + "field Struct.MapPtrField[*]" + ], + "mapPtrTypedefField": [ + "field Struct.MapPtrTypedefField" + ], + "mapPtrTypedefField[ʈ劉j蕓ư銩6ij憏歅%ɜ¤0Ȼ]": [ + "field Struct.MapPtrTypedefField[*]", + "type StringType" + ], + "mapPtrTypedefField[剛ň=Z?ǿ\u003c]": [ + "field Struct.MapPtrTypedefField[*]", + "type StringType" + ], + "mapTypedefField": [ + "field Struct.MapTypedefField" + ], + "mapTypedefField[V噘¢\u003eóDz岋笨Gń條ģ]": [ + "field Struct.MapTypedefField[*]", + "type StringType" + ], + "mapTypedefField[þƿ髈儱Ŀ蒫÷K鬣壈]": [ + "field Struct.MapTypedefField[*]", + "type StringType" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations.go new file mode 100644 index 0000000000..a4c8f92c3a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations.go @@ -0,0 +1,232 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapofprimitive + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type StringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]StringType { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedMapField has no validation + + { // field Struct.MapPtrField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]*string { + return oldObj.MapPtrField + }) + errs = append(errs, fn(fldPath.Child("mapPtrField"), obj.MapPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapPtrTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, StringType](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]*StringType { + return oldObj.MapPtrTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapPtrTypedefField"), obj.MapPtrTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedMapPtrField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations_test.go new file mode 100644 index 0000000000..c25381762f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_primitive/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapofprimitive + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go new file mode 100644 index 0000000000..2f771fb1de --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/doc.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package mapofstruct + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.MapField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]" + MapField map[string]OtherStruct `json:"mapField"` + + // +k8s:validateFalse="field Struct.MapTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapTypedefField[*]" + MapTypedefField map[string]OtherTypedefStruct `json:"mapTypedefField"` + + UnvalidatedMapField map[string]UnvalidatedStruct `json:"UnvalidatedMapField"` + + // +k8s:validateFalse="field Struct.MapPtrField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrField[*]" + MapPtrField map[string]*OtherStruct `json:"mapPtrField"` + + // +k8s:validateFalse="field Struct.MapPtrTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrTypedefField[*]" + MapPtrTypedefField map[string]*OtherTypedefStruct `json:"mapPtrTypedefField"` + + UnvalidatedMapPtrField map[string]*UnvalidatedStruct `json:"UnvalidatedMapPtrField"` +} + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct{} + +// +k8s:validateFalse="type OtherTypedefStruct" +type OtherTypedefStruct OtherStruct + +type UnvalidatedStruct struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/testdata/validate-false.json new file mode 100644 index 0000000000..2253b92760 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/testdata/validate-false.json @@ -0,0 +1,51 @@ +{ + "*mapofstruct.Struct": { + "": [ + "type Struct" + ], + "mapField": [ + "field Struct.MapField" + ], + "mapField[岯Ȉ\u0026\u003c沲3]": [ + "field Struct.MapField[*]", + "type OtherStruct" + ], + "mapField[铃]3g!fȺ苬ĥəƣ]": [ + "field Struct.MapField[*]", + "type OtherStruct" + ], + "mapPtrField": [ + "field Struct.MapPtrField" + ], + "mapPtrField[\u003eóDz岋笨Gń條]": [ + "field Struct.MapPtrField[*]", + "type OtherStruct" + ], + "mapPtrField[]": [ + "field Struct.MapPtrField[*]", + "type OtherStruct" + ], + "mapPtrTypedefField": [ + "field Struct.MapPtrTypedefField" + ], + "mapPtrTypedefField[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´]": [ + "field Struct.MapPtrTypedefField[*]", + "type OtherTypedefStruct" + ], + "mapPtrTypedefField[ș$0đ\u003eƯ]ɹȽ東]": [ + "field Struct.MapPtrTypedefField[*]", + "type OtherTypedefStruct" + ], + "mapTypedefField": [ + "field Struct.MapTypedefField" + ], + "mapTypedefField[]": [ + "field Struct.MapTypedefField[*]", + "type OtherTypedefStruct" + ], + "mapTypedefField[x飖Ǒp!ǪŰ]": [ + "field Struct.MapTypedefField[*]", + "type OtherTypedefStruct" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations.go new file mode 100644 index 0000000000..ebe69b0254 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations.go @@ -0,0 +1,253 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapofstruct + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_OtherTypedefStruct validates an instance of OtherTypedefStruct according +// to declarative validation rules in the API schema. +func Validate_OtherTypedefStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherTypedefStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherTypedefStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]OtherStruct { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherTypedefStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherTypedefStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]OtherTypedefStruct { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedMapField has no validation + + { // field Struct.MapPtrField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, OtherStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]*OtherStruct { + return oldObj.MapPtrField + }) + errs = append(errs, fn(fldPath.Child("mapPtrField"), obj.MapPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapPtrTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, OtherTypedefStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherTypedefStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherTypedefStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]*OtherTypedefStruct { + return oldObj.MapPtrTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapPtrTypedefField"), obj.MapPtrTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedMapPtrField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations_test.go new file mode 100644 index 0000000000..fab636bc7a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/map_of_struct/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapofstruct + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/doc.go new file mode 100644 index 0000000000..bdd9f638fe --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/doc.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package multiplevalidations + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.MapField #1" + // +k8s:validateFalse="field Struct.MapField #2" + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapField(keys) #1" + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapField(keys) #2" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*] #1" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*] #2" + MapField map[string]string `json:"mapField"` + + UnvalidatedMapField []string `json:"UnvalidatedMapField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/testdata/validate-false.json new file mode 100644 index 0000000000..ea27e9a111 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/testdata/validate-false.json @@ -0,0 +1,23 @@ +{ + "*multiplevalidations.Struct": { + "": [ + "type Struct" + ], + "mapField": [ + "field Struct.MapField #1", + "field Struct.MapField #2", + "field Struct.MapField(keys) #1", + "field Struct.MapField(keys) #1", + "field Struct.MapField(keys) #2", + "field Struct.MapField(keys) #2" + ], + "mapField[ÀYǎ3g成oɜH偩j0âȰ]": [ + "field Struct.MapField[*] #1", + "field Struct.MapField[*] #2" + ], + "mapField[岯Ȉ\u0026\u003c沲3]": [ + "field Struct.MapField[*] #1", + "field Struct.MapField[*] #2" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/zz_generated.validations.go new file mode 100644 index 0000000000..2578e4ca88 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/zz_generated.validations.go @@ -0,0 +1,124 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiplevalidations + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField(keys) #1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField(keys) #2") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField #1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField #2"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*] #1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*] #2") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedMapField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/zz_generated.validations_test.go new file mode 100644 index 0000000000..d30e8f8db8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/multiple_validations/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiplevalidations + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go new file mode 100644 index 0000000000..a4b62cc13e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/doc.go @@ -0,0 +1,77 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package typedeftomap + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: no validation here +type UnvalidatedType map[string]string + +// Note: no validation here +type UnvalidatedPtrType map[string]*string + +// +k8s:validateFalse="type MapType" +// +k8s:eachVal=+k8s:validateFalse="type MapType[*]" +type MapType map[string]string + +// +k8s:validateFalse="type MapPtrType" +// +k8s:eachVal=+k8s:validateFalse="type MapPtrType[*]" +type MapPtrType map[string]*string + +// +k8s:validateFalse="type MapTypedefType" +// +k8s:eachVal=+k8s:validateFalse="type MapTypedefType[*]" +type MapTypedefType map[string]StringType + +// +k8s:validateFalse="type MapPtrTypedefType" +// +k8s:eachVal=+k8s:validateFalse="type MapPtrTypedefType[*]" +type MapPtrTypedefType map[string]*StringType + +// +k8s:validateFalse="type StringType" +type StringType string + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.MapField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]" + MapField MapType `json:"mapField"` + + // +k8s:validateFalse="field Struct.MapTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapTypedefField[*]" + MapTypedefField MapTypedefType `json:"mapTypedefField"` + + UnvalidatedMapField UnvalidatedType `json:"UnvalidatedMapField"` + + // +k8s:validateFalse="field Struct.MapPtrField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrField[*]" + MapPtrField MapPtrType `json:"mapPtrField"` + + // +k8s:validateFalse="field Struct.MapPtrTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPtrTypedefField[*]" + MapPtrTypedefField MapPtrTypedefType `json:"mapPtrTypedefField"` + + UnvalidatedMapPtrField UnvalidatedPtrType `json:"UnvalidatedMapPtrField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/testdata/validate-false.json new file mode 100644 index 0000000000..65a321116f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/testdata/validate-false.json @@ -0,0 +1,59 @@ +{ + "*typedeftomap.Struct": { + "": [ + "type Struct" + ], + "mapField": [ + "field Struct.MapField", + "type MapType" + ], + "mapField[ÀYǎ3g成oɜH偩j0âȰ]": [ + "field Struct.MapField[*]", + "type MapType[*]" + ], + "mapField[岯Ȉ\u0026\u003c沲3]": [ + "field Struct.MapField[*]", + "type MapType[*]" + ], + "mapPtrField": [ + "field Struct.MapPtrField", + "type MapPtrType" + ], + "mapPtrField[`ȝ懿沇]": [ + "field Struct.MapPtrField[*]", + "type MapPtrType[*]" + ], + "mapPtrField[ǝ祇Fæƭō]": [ + "field Struct.MapPtrField[*]", + "type MapPtrType[*]" + ], + "mapPtrTypedefField": [ + "field Struct.MapPtrTypedefField", + "type MapPtrTypedefType" + ], + "mapPtrTypedefField[ʈ劉j蕓ư銩6ij憏歅%ɜ¤0Ȼ]": [ + "field Struct.MapPtrTypedefField[*]", + "type MapPtrTypedefType[*]", + "type StringType" + ], + "mapPtrTypedefField[剛ň=Z?ǿ\u003c]": [ + "field Struct.MapPtrTypedefField[*]", + "type MapPtrTypedefType[*]", + "type StringType" + ], + "mapTypedefField": [ + "field Struct.MapTypedefField", + "type MapTypedefType" + ], + "mapTypedefField[V噘¢\u003eóDz岋笨Gń條ģ]": [ + "field Struct.MapTypedefField[*]", + "type MapTypedefType[*]", + "type StringType" + ], + "mapTypedefField[þƿ髈儱Ŀ蒫÷K鬣壈]": [ + "field Struct.MapTypedefField[*]", + "type MapTypedefType[*]", + "type StringType" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations.go new file mode 100644 index 0000000000..15a29c153d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations.go @@ -0,0 +1,318 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftomap + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_MapPtrType validates an instance of MapPtrType according +// to declarative validation rules in the API schema. +func Validate_MapPtrType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MapPtrType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapPtrType"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapPtrType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_MapPtrTypedefType validates an instance of MapPtrTypedefType according +// to declarative validation rules in the API schema. +func Validate_MapPtrTypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MapPtrTypedefType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapPtrTypedefType"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapPtrTypedefType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + // iterate the map and call the value type's validation function + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_MapType validates an instance of MapType according +// to declarative validation rules in the API schema. +func Validate_MapType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MapType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapType"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_MapTypedefType validates an instance of MapTypedefType according +// to declarative validation rules in the API schema. +func Validate_MapTypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MapTypedefType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapTypedefType"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapTypedefType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type StringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj MapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MapType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapType { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj MapTypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MapTypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapTypedefType { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedMapField has no validation + + { // field Struct.MapPtrField + fn := func( + fldPath *field.Path, + obj, oldObj MapPtrType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MapPtrType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapPtrType { + return oldObj.MapPtrField + }) + errs = append(errs, fn(fldPath.Child("mapPtrField"), obj.MapPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapPtrTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj MapPtrTypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, StringType](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPtrTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MapPtrTypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapPtrTypedefType { + return oldObj.MapPtrTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapPtrTypedefField"), obj.MapPtrTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedMapPtrField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations_test.go new file mode 100644 index 0000000000..d04e460740 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/maps/typedef_to_map/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftomap + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/doc.go new file mode 100644 index 0000000000..b71be252dc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/doc.go @@ -0,0 +1,27 @@ +/* +Copyright 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. +*/ + +// Its types reference ../types; validators must call the canonical (registered) copy. +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package consumer + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/types.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/types.go new file mode 100644 index 0000000000..6158792959 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/types.go @@ -0,0 +1,25 @@ +/* +Copyright 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 consumer + +import "k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types" + +type Consumer struct { + TypeMeta int + + Shared types.T2 `json:"shared"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/zz_generated.validations.go new file mode 100644 index 0000000000..7025a9c8d2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/consumer/zz_generated.validations.go @@ -0,0 +1,90 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package consumer + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + field "k8s.io/apimachinery/pkg/util/validation/field" + registered "k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered" + types "k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Consumer + scheme.AddValidationFunc( + (*Consumer)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Consumer( + ctx, op, nil, /* fldPath */ + obj.(*Consumer), + safe.Cast[*Consumer](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Consumer validates an instance of Consumer according +// to declarative validation rules in the API schema. +func Validate_Consumer( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Consumer) (errs field.ErrorList) { + + // field Consumer.TypeMeta has no validation + + { // field Consumer.Shared + fn := func( + fldPath *field.Path, + obj, oldObj *types.T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, registered.Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Consumer) *types.T2 { + return &oldObj.Shared + }) + errs = append(errs, fn(fldPath.Child("shared"), &obj.Shared, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/doc.go new file mode 100644 index 0000000000..e1f9c1e0f0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 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. +*/ + +// A non-registering copy of ../types' validators, selecting all types via *. +// +k8s:validation-gen=* +// +k8s:validation-gen-input=k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types +// +k8s:validation-gen-scheme-registry=nil + +// This is a test package. +// +k8s:validation-gen-nolint +package external diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/validation_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/validation_test.go new file mode 100644 index 0000000000..fa271ca9c0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/validation_test.go @@ -0,0 +1,50 @@ +/* +Copyright 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 external + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered" + "k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types" +) + +// TestSelfContained runs this non-registering package's validators via direct +// calls (no scheme) and checks they agree with the registered copy. +func TestSelfContained(t *testing.T) { + ctx := context.Background() + matcher := field.ErrorMatcher{}.ByField().ByDetailExact() + + // Shared types must match the registered copy. + obj := &types.T1{List: []types.T2{{}, {}}} + ext := Validate_T1(ctx, operation.Operation{}, nil, obj, nil) + if len(ext) == 0 { + t.Fatalf("expected validation errors from Validate_T1, got none") + } + reg := registered.Validate_T1(ctx, operation.Operation{}, nil, obj, nil) + matcher.Test(t, reg, ext) + + // T3 is selected only by external; registered has no Validate_T3. + got := Validate_T3(ctx, operation.Operation{}, nil, &types.T3{}, nil) + want := field.ErrorList{ + field.Invalid(field.NewPath("s"), nil, "forced failure: field T3.S"), + } + matcher.Test(t, want, got) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/zz_generated.validations.go new file mode 100644 index 0000000000..816b2e3cc3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/external/zz_generated.validations.go @@ -0,0 +1,167 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package external + +import ( + context "context" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + types "k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types" +) + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *types.T1) (errs field.ErrorList) { + + // field types.T1.TypeMeta has no validation + + { // field types.T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *types.T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *types.T1) *types.T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field types.T1.List + fn := func( + fldPath *field.Path, + obj, oldObj []types.T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *types.T2) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.List[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_T2); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *types.T1) []types.T2 { + return oldObj.List + }) + errs = append(errs, fn(fldPath.Child("list"), obj.List, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *types.T2) (errs field.ErrorList) { + + { // field types.T2.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *types.T2) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T3 validates an instance of T3 according +// to declarative validation rules in the API schema. +func Validate_T3( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *types.T3) (errs field.ErrorList) { + + { // field types.T3.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T3.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *types.T3) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/doc.go new file mode 100644 index 0000000000..9f87aeceef --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/doc.go @@ -0,0 +1,28 @@ +/* +Copyright 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. +*/ + +// The registered (canonical) copy of ../types' validators. +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-input=k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package registered + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/validation_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/validation_test.go new file mode 100644 index 0000000000..98e5aa632a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/validation_test.go @@ -0,0 +1,33 @@ +/* +Copyright 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 registered + +import ( + "testing" + + "k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types" +) + +// Test runs the registering copy through its scheme. +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&types.T1{}).ExpectValidateFalseByPath(map[string][]string{ + "t2": {"field T1.T2"}, + "t2.s": {"field T2.S"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/zz_generated.validations.go new file mode 100644 index 0000000000..47ff25fa02 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/registered/zz_generated.validations.go @@ -0,0 +1,159 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package registered + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + types "k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*types.T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*types.T1), + safe.Cast[*types.T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *types.T1) (errs field.ErrorList) { + + // field types.T1.TypeMeta has no validation + + { // field types.T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *types.T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *types.T1) *types.T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field types.T1.List + fn := func( + fldPath *field.Path, + obj, oldObj []types.T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *types.T2) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.List[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_T2); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *types.T1) []types.T2 { + return oldObj.List + }) + errs = append(errs, fn(fldPath.Child("list"), obj.List, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *types.T2) (errs field.ErrorList) { + + { // field types.T2.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *types.T2) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types/types.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types/types.go new file mode 100644 index 0000000000..75c7f058b4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_packages/types/types.go @@ -0,0 +1,40 @@ +/* +Copyright 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 types holds the shared input types generated into ../registered and +// ../external and referenced by ../consumer. +package types + +type T1 struct { + TypeMeta int + + // +k8s:validateFalse="field T1.T2" + T2 T2 `json:"t2"` + + // +k8s:eachVal=+k8s:validateFalse="field T1.List[*]" + List []T2 `json:"list"` +} + +type T2 struct { + // +k8s:validateFalse="field T2.S" + S string `json:"s"` +} + +// T3 has no TypeMeta, so only external's validation-gen=* selects it. +type T3 struct { + // +k8s:validateFalse="field T3.S" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/doc.go new file mode 100644 index 0000000000..b92b11fcc9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/doc.go @@ -0,0 +1,58 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package multipletags + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// validateTrue should sort after validateFalse, but all the validateFalse +// should retain their order. + +// +k8s:validateTrue="type T1 #0" +// +k8s:validateFalse="type T1 #1" +// +k8s:validateFalse="type T1 #2" +// +k8s:validateFalse="type T1 #3" +type T1 struct { + TypeMeta int + // +k8s:validateTrue="field T1.S true" + // +k8s:validateFalse="field T1.S false #1" + // +k8s:validateFalse="field T1.S false #2" + // +k8s:validateFalse="field T1.S false #3" + S string `json:"s"` + // +k8s:validateTrue="field T1.T2 true" + // +k8s:validateFalse="field T1.T2 false #1" + // +k8s:validateFalse="field T1.T2 false #2" + // +k8s:validateFalse="field T1.T2 false #3" + T2 T2 `json:"t2"` +} + +// +k8s:validateTrue="type T2 true" +// +k8s:validateFalse="type T2 false #1" +// +k8s:validateFalse="type T2 false #2" +type T2 struct { + // +k8s:validateTrue="field T2.S true" + // +k8s:validateFalse="field T2.S false #1" + // +k8s:validateFalse="field T2.S false #2" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/testdata/validate-false.json new file mode 100644 index 0000000000..7b8a6fec4a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/testdata/validate-false.json @@ -0,0 +1,25 @@ +{ + "*multipletags.T1": { + "": [ + "type T1 #1", + "type T1 #2", + "type T1 #3" + ], + "s": [ + "field T1.S false #1", + "field T1.S false #2", + "field T1.S false #3" + ], + "t2": [ + "field T1.T2 false #1", + "field T1.T2 false #2", + "field T1.T2 false #3", + "type T2 false #1", + "type T2 false #2" + ], + "t2.s": [ + "field T2.S false #1", + "field T2.S false #2" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/zz_generated.validations.go new file mode 100644 index 0000000000..5b1416b359 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/zz_generated.validations.go @@ -0,0 +1,197 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multipletags + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T1 #1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T1 #2"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T1 #3"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "type T1 #0"); len(e) != 0 { + errs = append(errs, e...) + } + + // field T1.TypeMeta has no validation + + { // field T1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.S false #1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.S false #2"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.S false #3"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.S true"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2 false #1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2 false #2"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2 false #3"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.T2 true"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T2 false #1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T2 false #2"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "type T2 true"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field T2.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S false #1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S false #2"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T2.S true"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/zz_generated.validations_test.go new file mode 100644 index 0000000000..c3e7bd7643 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/multiple_tags/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multipletags + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_generation/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_generation/doc.go new file mode 100644 index 0000000000..a7063c8b0c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_generation/doc.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 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. +*/ + +// Note: validation generation is not enabled. + +// Package nogeneration is a test package. +// +// +k8s:validation-gen-nolint +// +//nolint:unused +package nogeneration + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + // +k8s:validateFalse="from field T1.S" + S string + // +k8s:validateFalse="from field T1.T2" + T2 T2 +} + +type T2 struct { + // +k8s:validateFalse="from field T2.S" + S string +} + +type private struct { + // +k8s:validateFalse="from field private.S" + S string +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/doc.go new file mode 100644 index 0000000000..9e83aafd30 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/doc.go @@ -0,0 +1,40 @@ +/* +Copyright 2024 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. +*/ + +// Note: no types match this. +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package notypesmatch + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + // +k8s:validateFalse="from field T1.S" + S string + // +k8s:validateFalse="from field T1.T2" + T2 T2 +} + +type T2 struct { + // +k8s:validateFalse="from field T2.S" + S string +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/testdata/validate-false.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/testdata/validate-false.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/zz_generated.validations.go new file mode 100644 index 0000000000..e754b00e83 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/zz_generated.validations.go @@ -0,0 +1,22 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package notypesmatch diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/zz_generated.validations_test.go new file mode 100644 index 0000000000..d065ce1373 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/no_types_match/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package notypesmatch + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/doc.go new file mode 100644 index 0000000000..357ae69cdc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/doc.go @@ -0,0 +1,30 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package trivial + +type T1 struct { + TypeMeta int +} + +type T2 struct{} + +type E1 string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/zz_generated.validations.go new file mode 100644 index 0000000000..7fa85b5807 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/trivial/zz_generated.validations.go @@ -0,0 +1,22 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package trivial diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/doc.go new file mode 100644 index 0000000000..cd04410ced --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/doc.go @@ -0,0 +1,79 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package withfieldvalidations + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + TypeMeta int + + // +k8s:validateFalse="field T1.S" + S string `json:"s"` + // +k8s:validateFalse="field T1.T2" + T2 T2 `json:"t2"` + // +k8s:validateFalse="field T1.T3" + T3 T3 `json:"t3"` + + // +k8s:validateFalse="field T1.E1" + E1 E1 `json:"e1"` + // +k8s:validateFalse="field T1.E2" + E2 E2 `json:"e2"` +} + +// Note: this has validations and is linked into T1. +type T2 struct { + // +k8s:validateFalse="field T2.S" + S string `json:"s"` +} + +// Note: this has no validations and is linked into T1. +type T3 struct { + S string `json:"s"` +} + +// Note: this has validations and is not linked into T1. +type T4 struct { + // +k8s:validateFalse="field T4.S" + S string `json:"s"` +} + +// Note: this has no validations and is not linked into T1. +type T5 struct { + S string `json:"s"` +} + +// Note: this has validations and is linked into T1. +// +k8s:validateFalse="type E1" +type E1 string + +// Note: this has no validations and is linked into T1. +type E2 string + +// Note: this has validations and is not linked into T1. +// +k8s:validateFalse="field type E3" +type E3 string + +// Note: this has no validations and is not linked into T1. +type E4 string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/testdata/validate-false.json new file mode 100644 index 0000000000..1f9f7529be --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/testdata/validate-false.json @@ -0,0 +1,23 @@ +{ + "*withfieldvalidations.T1": { + "e1": [ + "field T1.E1", + "type E1" + ], + "e2": [ + "field T1.E2" + ], + "s": [ + "field T1.S" + ], + "t2": [ + "field T1.T2" + ], + "t2.s": [ + "field T2.S" + ], + "t3": [ + "field T1.T3" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/zz_generated.validations.go new file mode 100644 index 0000000000..7ce6c6c862 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/zz_generated.validations.go @@ -0,0 +1,237 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package withfieldvalidations + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_E1 validates an instance of E1 according +// to declarative validation rules in the API schema. +func Validate_E1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E1) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type E1"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + // field T1.TypeMeta has no validation + + { // field T1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field T1.T3 + fn := func( + fldPath *field.Path, + obj, oldObj *T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T3"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T3 { + return &oldObj.T3 + }) + errs = append(errs, fn(fldPath.Child("t3"), &obj.T3, oldVal, oldObj != nil)...) + } + + { // field T1.E1 + fn := func( + fldPath *field.Path, + obj, oldObj *E1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.E1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E1 { + return &oldObj.E1 + }) + errs = append(errs, fn(fldPath.Child("e1"), &obj.E1, oldVal, oldObj != nil)...) + } + + { // field T1.E2 + fn := func( + fldPath *field.Path, + obj, oldObj *E2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.E2"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E2 { + return &oldObj.E2 + }) + errs = append(errs, fn(fldPath.Child("e2"), &obj.E2, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/zz_generated.validations_test.go new file mode 100644 index 0000000000..d96d788694 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_field_validations/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package withfieldvalidations + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/doc.go new file mode 100644 index 0000000000..a439eda809 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/doc.go @@ -0,0 +1,38 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package withtypevalidations + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type T1" +type T1 struct { + TypeMeta int +} + +// Note: this has no validations. +type T2 struct{} + +// +k8s:validateFalse="type E1" +type E1 string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/testdata/validate-false.json new file mode 100644 index 0000000000..4129153986 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/testdata/validate-false.json @@ -0,0 +1,7 @@ +{ + "*withtypevalidations.T1": { + "": [ + "type T1" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/zz_generated.validations.go new file mode 100644 index 0000000000..1508bd7134 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/zz_generated.validations.go @@ -0,0 +1,70 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package withtypevalidations + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T1"); len(e) != 0 { + errs = append(errs, e...) + } + + // field T1.TypeMeta has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/zz_generated.validations_test.go new file mode 100644 index 0000000000..97db075d26 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/one_type_match/with_type_validations/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package withtypevalidations + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/doc.go new file mode 100644 index 0000000000..d159e24691 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/doc.go @@ -0,0 +1,123 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package structs + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Tother struct { + // +k8s:validateFalse="Tother, no flags" + OS string `json:"os"` +} + +// Treat these as 4 bits, and ensure all combinations +// bit 0: no flags +// bit 1: ShortCircuit + +// Note: No validations. +type T00 struct { + TypeMeta int + S string `json:"s"` + PS *string `json:"ps"` + T Tother `json:"t"` + PT *Tother `json:"pt"` +} + +// +k8s:validateFalse="T01, no flags" +type T01 struct { + TypeMeta int + // +k8s:validateFalse="T01.S, no flags" + S string `json:"s"` + // +k8s:validateFalse="T01.PS, no flags" + PS *string `json:"ps"` + // +k8s:validateFalse="T01.T, no flags" + T Tother `json:"t"` + // +k8s:validateFalse="T01.PT, no flags" + PT *Tother `json:"pt"` +} + +// +k8s:validateFalse(flags: "ShortCircuit")="T02, ShortCircuit" +type T02 struct { + TypeMeta int + // +k8s:validateFalse(flags: "ShortCircuit")="T02.S, ShortCircuit" + S string `json:"s"` + // +k8s:validateFalse(flags: "ShortCircuit")="T02.PS, ShortCircuit" + PS *string `json:"ps"` + // +k8s:validateFalse(flags: "ShortCircuit")="T02.T, ShortCircuit" + T Tother `json:"t"` + // +k8s:validateFalse(flags: "ShortCircuit")="T02.PT, ShortCircuit" + PT *Tother `json:"pt"` +} + +// +k8s:validateFalse="T03, no flags" +// +k8s:validateFalse(flags: "ShortCircuit")="T03, ShortCircuit" +type T03 struct { + TypeMeta int + // +k8s:validateFalse="T03.S, no flags" + // +k8s:validateFalse(flags: "ShortCircuit")="T03.S, ShortCircuit" + S string `json:"s"` + // +k8s:validateFalse="T03.PS, no flags" + // +k8s:validateFalse(flags: "ShortCircuit")="T03.PS, ShortCircuit" + PS *string `json:"ps"` + // +k8s:validateFalse="T03.T, no flags" + // +k8s:validateFalse(flags: "ShortCircuit")="T03.T, ShortCircuit" + T Tother `json:"t"` + // +k8s:validateFalse="T03.PT, no flags" + // +k8s:validateFalse(flags: "ShortCircuit")="T03.PT, ShortCircuit" + PT *Tother `json:"pt"` +} + +// Note: these are intentionally in the wrong final order. +// +k8s:validateFalse="TMultiple, no flags 1" +// +k8s:validateFalse(flags: "ShortCircuit")="TMultiple, ShortCircuit 1" +// +k8s:validateFalse="T0, string payload" +// +k8s:validateFalse="TMultiple, no flags 2" +// +k8s:validateFalse(flags: "ShortCircuit")="TMultiple, ShortCircuit 2" +type TMultiple struct { + TypeMeta int + // +k8s:validateFalse="TMultiple.S, no flags 1" + // +k8s:validateFalse(flags: "ShortCircuit")="TMultiple.S, ShortCircuit 1" + // +k8s:validateFalse="T0, string payload" + // +k8s:validateFalse="TMultiple.S, no flags 2" + // +k8s:validateFalse(flags: "ShortCircuit")="TMultiple.S, ShortCircuit 2" + S string `json:"s"` + // +k8s:validateFalse="TMultiple.PS, no flags 1" + // +k8s:validateFalse(flags: "ShortCircuit")="TMultiple.PS, ShortCircuit 1" + // +k8s:validateFalse="T0, string payload" + // +k8s:validateFalse="TMultiple.PS, no flags 2" + // +k8s:validateFalse(flags: "ShortCircuit")="TMultiple.PS, ShortCircuit 2" + PS *string `json:"ps"` + // +k8s:validateFalse="TMultiple.T, no flags 1" + // +k8s:validateFalse(flags: "ShortCircuit")="TMultiple.T, ShortCircuit 1" + // +k8s:validateFalse="T0, string payload" + // +k8s:validateFalse="TMultiple.T, no flags 2" + // +k8s:validateFalse(flags: "ShortCircuit")="TMultiple.T, ShortCircuit 2" + T Tother `json:"t"` + // +k8s:validateFalse="TMultiple.PT, no flags 1" + // +k8s:validateFalse(flags: "ShortCircuit")="TMultiple.PT, ShortCircuit 1" + // +k8s:validateFalse="T0, string payload" + // +k8s:validateFalse="TMultiple.PT, no flags 2" + // +k8s:validateFalse(flags: "ShortCircuit")="TMultiple.PT, ShortCircuit 2" + PT *Tother `json:"pt"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/testdata/validate-false.json new file mode 100644 index 0000000000..f1fd2b1edf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/testdata/validate-false.json @@ -0,0 +1,49 @@ +{ + "*structs.T00": { + "pt.os": [ + "Tother, no flags" + ], + "t.os": [ + "Tother, no flags" + ] + }, + "*structs.T01": { + "": [ + "T01, no flags" + ], + "ps": [ + "T01.PS, no flags" + ], + "pt": [ + "T01.PT, no flags" + ], + "pt.os": [ + "Tother, no flags" + ], + "s": [ + "T01.S, no flags" + ], + "t": [ + "T01.T, no flags" + ], + "t.os": [ + "Tother, no flags" + ] + }, + "*structs.T02": { + "": [ + "T02, ShortCircuit" + ] + }, + "*structs.T03": { + "": [ + "T03, ShortCircuit" + ] + }, + "*structs.TMultiple": { + "": [ + "TMultiple, ShortCircuit 1", + "TMultiple, ShortCircuit 2" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/zz_generated.validations.go new file mode 100644 index 0000000000..24024aa54b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/zz_generated.validations.go @@ -0,0 +1,821 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package structs + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T00 + scheme.AddValidationFunc( + (*T00)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T00( + ctx, op, nil, /* fldPath */ + obj.(*T00), + safe.Cast[*T00](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T01 + scheme.AddValidationFunc( + (*T01)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T01( + ctx, op, nil, /* fldPath */ + obj.(*T01), + safe.Cast[*T01](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T02 + scheme.AddValidationFunc( + (*T02)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T02( + ctx, op, nil, /* fldPath */ + obj.(*T02), + safe.Cast[*T02](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T03 + scheme.AddValidationFunc( + (*T03)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T03( + ctx, op, nil, /* fldPath */ + obj.(*T03), + safe.Cast[*T03](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type TMultiple + scheme.AddValidationFunc( + (*TMultiple)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_TMultiple( + ctx, op, nil, /* fldPath */ + obj.(*TMultiple), + safe.Cast[*TMultiple](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T00 validates an instance of T00 according +// to declarative validation rules in the API schema. +func Validate_T00( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T00) (errs field.ErrorList) { + + // field T00.TypeMeta has no validation + // field T00.S has no validation + // field T00.PS has no validation + + { // field T00.T + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T00) *Tother { + return &oldObj.T + }) + errs = append(errs, fn(fldPath.Child("t"), &obj.T, oldVal, oldObj != nil)...) + } + + { // field T00.PT + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T00) *Tother { + return oldObj.PT + }) + errs = append(errs, fn(fldPath.Child("pt"), obj.PT, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T01 validates an instance of T01 according +// to declarative validation rules in the API schema. +func Validate_T01( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T01) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T01, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + + // field T01.TypeMeta has no validation + + { // field T01.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T01.S, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T01) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T01.PS + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T01.PS, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T01) *string { + return oldObj.PS + }) + errs = append(errs, fn(fldPath.Child("ps"), obj.PS, oldVal, oldObj != nil)...) + } + + { // field T01.T + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T01.T, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T01) *Tother { + return &oldObj.T + }) + errs = append(errs, fn(fldPath.Child("t"), &obj.T, oldVal, oldObj != nil)...) + } + + { // field T01.PT + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T01.PT, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T01) *Tother { + return oldObj.PT + }) + errs = append(errs, fn(fldPath.Child("pt"), obj.PT, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T02 validates an instance of T02 according +// to declarative validation rules in the API schema. +func Validate_T02( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T02) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T02, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + + // field T02.TypeMeta has no validation + + { // field T02.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T02.S, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T02) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T02.PS + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T02.PS, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T02) *string { + return oldObj.PS + }) + errs = append(errs, fn(fldPath.Child("ps"), obj.PS, oldVal, oldObj != nil)...) + } + + { // field T02.T + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T02.T, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T02) *Tother { + return &oldObj.T + }) + errs = append(errs, fn(fldPath.Child("t"), &obj.T, oldVal, oldObj != nil)...) + } + + { // field T02.PT + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T02.PT, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T02) *Tother { + return oldObj.PT + }) + errs = append(errs, fn(fldPath.Child("pt"), obj.PT, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T03 validates an instance of T03 according +// to declarative validation rules in the API schema. +func Validate_T03( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T03) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + + // field T03.TypeMeta has no validation + + { // field T03.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03.S, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03.S, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T03) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T03.PS + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03.PS, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03.PS, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T03) *string { + return oldObj.PS + }) + errs = append(errs, fn(fldPath.Child("ps"), obj.PS, oldVal, oldObj != nil)...) + } + + { // field T03.T + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03.T, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03.T, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T03) *Tother { + return &oldObj.T + }) + errs = append(errs, fn(fldPath.Child("t"), &obj.T, oldVal, oldObj != nil)...) + } + + { // field T03.PT + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03.PT, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T03.PT, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T03) *Tother { + return oldObj.PT + }) + errs = append(errs, fn(fldPath.Child("pt"), obj.PT, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TMultiple validates an instance of TMultiple according +// to declarative validation rules in the API schema. +func Validate_TMultiple( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *TMultiple) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple, ShortCircuit 1").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple, ShortCircuit 2").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple, no flags 1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T0, string payload"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple, no flags 2"); len(e) != 0 { + errs = append(errs, e...) + } + + // field TMultiple.TypeMeta has no validation + + { // field TMultiple.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.S, ShortCircuit 1").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.S, ShortCircuit 2").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.S, no flags 1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T0, string payload"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.S, no flags 2"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TMultiple) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field TMultiple.PS + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.PS, ShortCircuit 1").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.PS, ShortCircuit 2").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.PS, no flags 1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T0, string payload"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.PS, no flags 2"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TMultiple) *string { + return oldObj.PS + }) + errs = append(errs, fn(fldPath.Child("ps"), obj.PS, oldVal, oldObj != nil)...) + } + + { // field TMultiple.T + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.T, ShortCircuit 1").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.T, ShortCircuit 2").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.T, no flags 1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T0, string payload"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.T, no flags 2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TMultiple) *Tother { + return &oldObj.T + }) + errs = append(errs, fn(fldPath.Child("t"), &obj.T, oldVal, oldObj != nil)...) + } + + { // field TMultiple.PT + fn := func( + fldPath *field.Path, + obj, oldObj *Tother, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.PT, ShortCircuit 1").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.PT, ShortCircuit 2").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.PT, no flags 1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "T0, string payload"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "TMultiple.PT, no flags 2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_Tother(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TMultiple) *Tother { + return oldObj.PT + }) + errs = append(errs, fn(fldPath.Child("pt"), obj.PT, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_Tother validates an instance of Tother according +// to declarative validation rules in the API schema. +func Validate_Tother( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Tother) (errs field.ErrorList) { + + { // field Tother.OS + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "Tother, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Tother) *string { + return &oldObj.OS + }) + errs = append(errs, fn(fldPath.Child("os"), &obj.OS, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/zz_generated.validations_test.go new file mode 100644 index 0000000000..9e1134fb4c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/structs/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package structs + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/doc.go new file mode 100644 index 0000000000..84186fd7d5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/doc.go @@ -0,0 +1,52 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package typedefs + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Treat these as 4 bits, and ensure all combinations +// bit 0: no flags +// bit 1: ShortCircuit + +// Note: No validations. +type E00 string + +// +k8s:validateFalse="E01, no flags" +type E01 string + +// +k8s:validateFalse(flags: "ShortCircuit")="E02, ShortCircuit" +type E02 string + +// +k8s:validateFalse="E03, no flags" +// +k8s:validateFalse(flags: "ShortCircuit")="E03, ShortCircuit" +type E03 string + +// Note: these are intentionally in the wrong final order. +// +k8s:validateFalse="EMultiple, no flags 1" +// +k8s:validateFalse(flags: "ShortCircuit")="EMultiple, ShortCircuit 1" +// +k8s:validateFalse="E0, string payload" +// +k8s:validateFalse="EMultiple, no flags 2" +// +k8s:validateFalse(flags: "ShortCircuit")="EMultiple, ShortCircuit 2" +type EMultiple string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/testdata/validate-false.json new file mode 100644 index 0000000000..eb000238a3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/testdata/validate-false.json @@ -0,0 +1,23 @@ +{ + "*typedefs.E01": { + "": [ + "E01, no flags" + ] + }, + "*typedefs.E02": { + "": [ + "E02, ShortCircuit" + ] + }, + "*typedefs.E03": { + "": [ + "E03, ShortCircuit" + ] + }, + "*typedefs.EMultiple": { + "": [ + "EMultiple, ShortCircuit 1", + "EMultiple, ShortCircuit 2" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/zz_generated.validations.go new file mode 100644 index 0000000000..e9bcf409bf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/zz_generated.validations.go @@ -0,0 +1,184 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedefs + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type E01 + scheme.AddValidationFunc( + (*E01)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_E01( + ctx, op, nil, /* fldPath */ + obj.(*E01), + safe.Cast[*E01](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type E02 + scheme.AddValidationFunc( + (*E02)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_E02( + ctx, op, nil, /* fldPath */ + obj.(*E02), + safe.Cast[*E02](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type E03 + scheme.AddValidationFunc( + (*E03)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_E03( + ctx, op, nil, /* fldPath */ + obj.(*E03), + safe.Cast[*E03](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type EMultiple + scheme.AddValidationFunc( + (*EMultiple)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_EMultiple( + ctx, op, nil, /* fldPath */ + obj.(*EMultiple), + safe.Cast[*EMultiple](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_E01 validates an instance of E01 according +// to declarative validation rules in the API schema. +func Validate_E01( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E01) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "E01, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_E02 validates an instance of E02 according +// to declarative validation rules in the API schema. +func Validate_E02( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E02) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "E02, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + + return errs +} + +// Validate_E03 validates an instance of E03 according +// to declarative validation rules in the API schema. +func Validate_E03( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E03) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "E03, ShortCircuit").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "E03, no flags"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_EMultiple validates an instance of EMultiple according +// to declarative validation rules in the API schema. +func Validate_EMultiple( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *EMultiple) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "EMultiple, ShortCircuit 1").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "EMultiple, ShortCircuit 2").MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "EMultiple, no flags 1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "E0, string payload"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "EMultiple, no flags 2"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/zz_generated.validations_test.go new file mode 100644 index 0000000000..13aa49d8a1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ordering/typedefs/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedefs + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/doc.go new file mode 100644 index 0000000000..9e6b34c945 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/doc.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package pointers + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + TypeMeta int + + // +k8s:validateFalse="field T1.PS" + PS *string `json:"ps"` + // +k8s:validateFalse="field T1.PI" + PI *int `json:"pi"` + // +k8s:validateFalse="field T1.PB" + PB *bool `json:"pb"` + // +k8s:validateFalse="field T1.PF" + PF *float64 `json:"pf"` + + // +k8s:validateFalse="field T1.PT2" + PT2 *T2 `json:"pt2"` + + // Duplicate types with no validation. + AnotherPS *string `json:"anotherps"` + AnotherPI *int `json:"anotherpi"` + AnotherPB *bool `json:"anotherpb"` + AnotherPF *float64 `json:"anotherpf"` +} + +// Note: This has validations and is linked into the type-graph of T1. +type T2 struct { + // +k8s:validateFalse="field T2.PS" + PS *string `json:"ps"` + // +k8s:validateFalse="field T2.PI" + PI *int `json:"pi"` + // +k8s:validateFalse="field T2.PB" + PB *bool `json:"pb"` + // +k8s:validateFalse="field T2.PF" + PF *float64 `json:"pf"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/testdata/validate-false.json new file mode 100644 index 0000000000..28909a7e39 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/testdata/validate-false.json @@ -0,0 +1,31 @@ +{ + "*pointers.T1": { + "pb": [ + "field T1.PB" + ], + "pf": [ + "field T1.PF" + ], + "pi": [ + "field T1.PI" + ], + "ps": [ + "field T1.PS" + ], + "pt2": [ + "field T1.PT2" + ], + "pt2.pb": [ + "field T2.PB" + ], + "pt2.pf": [ + "field T2.PF" + ], + "pt2.pi": [ + "field T2.PI" + ], + "pt2.ps": [ + "field T2.PS" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/zz_generated.validations.go new file mode 100644 index 0000000000..ae10feeeb3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/zz_generated.validations.go @@ -0,0 +1,299 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package pointers + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + // field T1.TypeMeta has no validation + + { // field T1.PS + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PS"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return oldObj.PS + }) + errs = append(errs, fn(fldPath.Child("ps"), obj.PS, oldVal, oldObj != nil)...) + } + + { // field T1.PI + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PI"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *int { + return oldObj.PI + }) + errs = append(errs, fn(fldPath.Child("pi"), obj.PI, oldVal, oldObj != nil)...) + } + + { // field T1.PB + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PB"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *bool { + return oldObj.PB + }) + errs = append(errs, fn(fldPath.Child("pb"), obj.PB, oldVal, oldObj != nil)...) + } + + { // field T1.PF + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PF"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *float64 { + return oldObj.PF + }) + errs = append(errs, fn(fldPath.Child("pf"), obj.PF, oldVal, oldObj != nil)...) + } + + { // field T1.PT2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PT2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return oldObj.PT2 + }) + errs = append(errs, fn(fldPath.Child("pt2"), obj.PT2, oldVal, oldObj != nil)...) + } + + // field T1.AnotherPS has no validation + // field T1.AnotherPI has no validation + // field T1.AnotherPB has no validation + // field T1.AnotherPF has no validation + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.PS + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.PS"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *string { + return oldObj.PS + }) + errs = append(errs, fn(fldPath.Child("ps"), obj.PS, oldVal, oldObj != nil)...) + } + + { // field T2.PI + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.PI"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *int { + return oldObj.PI + }) + errs = append(errs, fn(fldPath.Child("pi"), obj.PI, oldVal, oldObj != nil)...) + } + + { // field T2.PB + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.PB"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *bool { + return oldObj.PB + }) + errs = append(errs, fn(fldPath.Child("pb"), obj.PB, oldVal, oldObj != nil)...) + } + + { // field T2.PF + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.PF"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *float64 { + return oldObj.PF + }) + errs = append(errs, fn(fldPath.Child("pf"), obj.PF, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/zz_generated.validations_test.go new file mode 100644 index 0000000000..b1ab618180 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/pointers/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package pointers + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/doc.go new file mode 100644 index 0000000000..329c1c86b3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/doc.go @@ -0,0 +1,93 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package primitives + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + TypeMeta int + + // +k8s:validateFalse="field T1.S" + S string `json:"s"` + // +k8s:validateFalse="field T1.I" + I int `json:"i"` + // +k8s:validateFalse="field T1.B" + B bool `json:"b"` + // +k8s:validateFalse="field T1.F" + F float64 `json:"f"` + + // +k8s:validateFalse="field T1.T2" + T2 T2 `json:"t2"` + + // No internal validations. + T3 T3 `json:"t3"` + + // Duplicate types with no validation. + AnotherS string `json:"anothers"` + AnotherI int `json:"anotheri"` + AnotherB bool `json:"anotherb"` + AnotherF float64 `json:"anotherf"` + AnotherT2 T2 `json:"anothert2"` +} + +// Note: This has validations and is linked into the type-graph of T1. +type T2 struct { + // +k8s:validateFalse="field T2.S" + S string `json:"s"` + // +k8s:validateFalse="field T2.I" + I int `json:"i"` + // +k8s:validateFalse="field T2.B" + B bool `json:"b"` + // +k8s:validateFalse="field T2.F" + F float64 `json:"f"` +} + +// Note: This has no validations and is linked into the type-graph of T1. +type T3 struct { + S string `json:"s"` + I int `json:"i"` + B bool `json:"b"` + F float64 `json:"f"` +} + +// Note: This has validations and is not linked into the type-graph of T1. +type T4 struct { + // +k8s:validateFalse="field T4.S" + S string `json:"s"` + // +k8s:validateFalse="field T4.I" + I int `json:"i"` + // +k8s:validateFalse="field T4.B" + B bool `json:"b"` + // +k8s:validateFalse="field T4.F" + F float64 `json:"f"` +} + +// Note: This has no validations and is not linked into the type-graph of T1. +type T5 struct { + S string `json:"s"` + I int `json:"i"` + B bool `json:"b"` + F float64 `json:"f"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/testdata/validate-false.json new file mode 100644 index 0000000000..0046190d7b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/testdata/validate-false.json @@ -0,0 +1,43 @@ +{ + "*primitives.T1": { + "anothert2.b": [ + "field T2.B" + ], + "anothert2.f": [ + "field T2.F" + ], + "anothert2.i": [ + "field T2.I" + ], + "anothert2.s": [ + "field T2.S" + ], + "b": [ + "field T1.B" + ], + "f": [ + "field T1.F" + ], + "i": [ + "field T1.I" + ], + "s": [ + "field T1.S" + ], + "t2": [ + "field T1.T2" + ], + "t2.b": [ + "field T2.B" + ], + "t2.f": [ + "field T2.F" + ], + "t2.i": [ + "field T2.I" + ], + "t2.s": [ + "field T2.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/zz_generated.validations.go new file mode 100644 index 0000000000..5a6620c4cb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/zz_generated.validations.go @@ -0,0 +1,322 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package primitives + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + // field T1.TypeMeta has no validation + + { // field T1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T1.I + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.I"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *int { + return &oldObj.I + }) + errs = append(errs, fn(fldPath.Child("i"), &obj.I, oldVal, oldObj != nil)...) + } + + { // field T1.B + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.B"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *bool { + return &oldObj.B + }) + errs = append(errs, fn(fldPath.Child("b"), &obj.B, oldVal, oldObj != nil)...) + } + + { // field T1.F + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.F"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *float64 { + return &oldObj.F + }) + errs = append(errs, fn(fldPath.Child("f"), &obj.F, oldVal, oldObj != nil)...) + } + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + // field T1.T3 has no validation + // field T1.AnotherS has no validation + // field T1.AnotherI has no validation + // field T1.AnotherB has no validation + // field T1.AnotherF has no validation + + { // field T1.AnotherT2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.AnotherT2 + }) + errs = append(errs, fn(fldPath.Child("anothert2"), &obj.AnotherT2, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T2.I + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.I"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *int { + return &oldObj.I + }) + errs = append(errs, fn(fldPath.Child("i"), &obj.I, oldVal, oldObj != nil)...) + } + + { // field T2.B + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.B"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *bool { + return &oldObj.B + }) + errs = append(errs, fn(fldPath.Child("b"), &obj.B, oldVal, oldObj != nil)...) + } + + { // field T2.F + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.F"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *float64 { + return &oldObj.F + }) + errs = append(errs, fn(fldPath.Child("f"), &obj.F, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/zz_generated.validations_test.go new file mode 100644 index 0000000000..550334b185 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/primitives/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package primitives + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/doc.go new file mode 100644 index 0000000000..73e0b93122 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/doc.go @@ -0,0 +1,46 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// Package publicprivate is a test package. +// +// +k8s:validation-gen-nolint +// +//nolint:unused,govet,staticcheck // govet disables structtag check, which checks for use of tags on private fields; staticcheck calls out SA5008: unexported struct field cannot have non-ignored `json:"private"` +package publicprivate + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + // +k8s:validateFalse="field T1.Public" + Public string `json:"public"` + + // +k8s:validateFalse="field T1.private" + private string `json:"private"` +} + +type private struct { + // +k8s:validateFalse="field private.Public" + Public string `json:"public"` + + // +k8s:validateFalse="field private.private" + private string `json:"private"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/testdata/validate-false.json new file mode 100644 index 0000000000..1deaa60b1e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/testdata/validate-false.json @@ -0,0 +1,7 @@ +{ + "*publicprivate.T1": { + "public": [ + "field T1.Public" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/zz_generated.validations.go new file mode 100644 index 0000000000..0a00b42c7f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/zz_generated.validations.go @@ -0,0 +1,89 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package publicprivate + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.Public + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.Public"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.Public + }) + errs = append(errs, fn(fldPath.Child("public"), &obj.Public, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/zz_generated.validations_test.go new file mode 100644 index 0000000000..12bc22d0a5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/public_private/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package publicprivate + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc.go new file mode 100644 index 0000000000..74beb7963a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc.go @@ -0,0 +1,54 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-deep-equal-func=k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func.CustomDeepEqual + +// This is a test package. +// +k8s:validation-gen-nolint +package deepequalfunc + +import ( + "reflect" + + "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +var localSchemeBuilder = testscheme.New() + +var CustomDeepEqualCalls int + +func CustomDeepEqual(a, b any) bool { + CustomDeepEqualCalls++ + return reflect.DeepEqual(a, b) +} + +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.NonComparableField" + NonComparableField NonComparableStruct `json:"nonComparableField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]" + MapField map[string]NonComparableStruct `json:"mapField"` +} + +// +k8s:validateFalse="type NonComparableStruct" +type NonComparableStruct struct { + // Ptr makes it not direct-comparable + Ptr *int `json:"ptr"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc_test.go new file mode 100644 index 0000000000..595e71d26c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/doc_test.go @@ -0,0 +1,71 @@ +/* +Copyright 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 deepequalfunc + +import ( + "testing" +) + +func TestDeepEqualFunc(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Create + val1 := 10 + obj := &Struct{ + NonComparableField: NonComparableStruct{Ptr: &val1}, + MapField: map[string]NonComparableStruct{ + "a": {Ptr: &val1}, + }, + } + st.Value(obj).ExpectValidateFalseByPath(map[string][]string{ + "nonComparableField": {"field Struct.NonComparableField", "type NonComparableStruct"}, + "mapField[a]": {"field Struct.MapField[*]", "type NonComparableStruct"}, + }) + + // Update with same values should ratchet (skip validation) using CustomDeepEqual + CustomDeepEqualCalls = 0 + oldObj := &Struct{ + NonComparableField: NonComparableStruct{Ptr: &val1}, + MapField: map[string]NonComparableStruct{ + "a": {Ptr: &val1}, + }, + } + newObj := &Struct{ + NonComparableField: NonComparableStruct{Ptr: &val1}, + MapField: map[string]NonComparableStruct{ + "a": {Ptr: &val1}, + }, + } + st.Value(newObj).OldValue(oldObj).ExpectValid() + if CustomDeepEqualCalls == 0 { + t.Errorf("expected CustomDeepEqual to be called during update ratcheting, got %d calls", CustomDeepEqualCalls) + } + + // Update with a new map key added: old key "a" ratchets via deepEqualImpl_ -> CustomDeepEqual, + // only new key "b" fails. + val2 := 20 + updateObj := &Struct{ + NonComparableField: NonComparableStruct{Ptr: &val1}, + MapField: map[string]NonComparableStruct{ + "a": {Ptr: &val1}, + "b": {Ptr: &val2}, + }, + } + st.Value(updateObj).OldValue(oldObj).ExpectValidateFalseByPath(map[string][]string{ + "mapField[b]": {"field Struct.MapField[*]", "type NonComparableStruct"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/zz_generated.validations.go new file mode 100644 index 0000000000..2ec646bb0e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/deep_equal_func/zz_generated.validations.go @@ -0,0 +1,143 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package deepequalfunc + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_NonComparableStruct validates an instance of NonComparableStruct according +// to declarative validation rules in the API schema. +func Validate_NonComparableStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NonComparableStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type NonComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field NonComparableStruct.Ptr has no validation + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.NonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj *NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if CustomDeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.NonComparableField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_NonComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *NonComparableStruct { + return &oldObj.NonComparableField + }) + errs = append(errs, fn(fldPath.Child("nonComparableField"), &obj.NonComparableField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if CustomDeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_NonComparableStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]NonComparableStruct { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return CustomDeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc.go new file mode 100644 index 0000000000..d42ff19ee4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc.go @@ -0,0 +1,154 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package defaultbehavior + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type StructPrimitive struct { + TypeMeta int + + // +k8s:validateFalse="field intField" + IntField int `json:"intField"` + + // +k8s:optional + // +k8s:validateFalse="field intPtrField" + IntPtrField *int `json:"intPtrField"` +} + +type StructSlice struct { + TypeMeta int + + // +k8s:validateFalse="field sliceField" + SliceField []S `json:"sliceField"` + + // +k8s:validateFalse="field typedefSliceField" + TypeDefSliceField MySlice `json:"typedefSliceField"` +} + +type StructMap struct { + TypeMeta int + + // +k8s:validateFalse="field mapKeyField" + MapKeyField map[S]string `json:"mapKeyField"` + + // +k8s:validateFalse="field mapValueField" + MapValueField map[string]S `json:"mapValueField"` + + // +k8s:validateFalse="field aliasMapKeyTypeField" + AliasMapKeyTypeField AliasMapKeyType `json:"aliasMapKeyTypeField"` + + // +k8s:validateFalse="field aliasMapValueTypeField" + AliasMapValueTypeField AliasMapValueType `json:"aliasMapValueTypeField"` +} + +type StructStruct struct { + TypeMeta int + + // +k8s:validateFalse="field directComparableStructField" + DirectComparableStructField DirectComparableStruct `json:"directComparableStructField"` + + // +k8s:validateFalse="field nonDirectComparableStructField" + NonDirectComparableStructField NonDirectComparableStruct `json:"nonDirectComparableStructField"` + + // +k8s:validateFalse="field directComparableStructPtrField" + DirectComparableStructPtr *DirectComparableStruct `json:"directComparableStructPtrField"` + + // +k8s:validateFalse="field nonDirectComparableStructPtrField" + NonDirectComparableStructPtr *NonDirectComparableStruct `json:"nonDirectComparableStructPtrField"` + + // +k8s:validateFalse="field DirectComparableStruct" + DirectComparableStruct + + // +k8s:validateFalse="field NonDirectComparableStruct" + NonDirectComparableStruct +} + +type StructEmbedded struct { + TypeMeta int + // +k8s:validateFalse="field DirectComparableStruct" + DirectComparableStruct `json:"directComparableStruct"` + + // +k8s:validateFalse="field NonDirectComparableStruct" + NonDirectComparableStruct `json:"nonDirectComparableStruct"` + + // +k8s:validateFalse="field NestedDirectComparableStructField" + NestedDirectComparableStructField NestedDirectComparableStruct `json:"nestedDirectComparableStructField"` + + // +k8s:validateFalse="field NestedNonDirectComparableStructField" + NestedNonDirectComparableStructField NestedNonDirectComparableStruct `json:"nestedNonDirectComparableStructField"` +} + +// +k8s:validateFalse="type TypeDefStruct" +type TypeDefStruct struct{} + +// +k8s:validateFalse="type MySlice" +type MySlice []int + +// +k8s:validateFalse="type S" +type S string + +// +k8s:validateFalse="type MapKeyType" +type AliasMapKeyType MapKeyType + +// +k8s:validateFalse="type MapValueType" +type AliasMapValueType MapValueType + +// no validation +type MapKeyType map[S]string + +// no validation +type MapValueType map[string]S + +// +k8s:validateFalse="type DirectComparableStruct" +type DirectComparableStruct struct { + // +k8s:validateFalse="field intField" + IntField int `json:"intField"` +} + +// +k8s:validateFalse="type NonDirectComparableStruct" +type NonDirectComparableStruct struct { + // +k8s:validateFalse="field intPtrField" + IntPtrField *int `json:"intPtrField"` +} + +// +k8s:validateFalse="type NestedDirectComparableStruct" +type NestedDirectComparableStruct struct { + // +k8s:validateFalse="field directComparableStructField" + DirectComparableStructField DirectComparableStruct `json:"directComparableStructField"` +} + +// +k8s:validateFalse="type NestedNonDirectComparableStruct" +type NestedNonDirectComparableStruct struct { + // +k8s:validateFalse="field nonDirectComparableStructField" + NonDirectComparableStructField NonDirectComparableStruct `json:"nonDirectComparableStructField"` +} + +type MixComparableStruct struct { + TypeMeta int + + Primitive string `json:"Primitive"` + + // +k8s:validateFalse="field NonComparable" + NonComparable []string `json:"NonComparable"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc_test.go new file mode 100644 index 0000000000..c4bdcd2738 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/doc_test.go @@ -0,0 +1,186 @@ +/* +Copyright 2025 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 defaultbehavior + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test_StructPrimitive(t *testing.T) { + mkTest := func() *StructPrimitive { + return &StructPrimitive{ + IntField: 1, + IntPtrField: ptr.To(1), // Different pointers each call, but same value. + } + } + + st := localSchemeBuilder.Test(t) + st.Value(mkTest()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), "", ""), + field.Invalid(field.NewPath("intPtrField"), "", ""), + }) + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() +} + +func Test_StructSlice(t *testing.T) { + mkTest := func() *StructSlice { + return &StructSlice{ + SliceField: []S{""}, + TypeDefSliceField: MySlice{1}, + } + } + + st := localSchemeBuilder.Test(t) + st.Value(mkTest()).ExpectValidateFalseByPath(map[string][]string{ + "sliceField": {"field sliceField"}, + "sliceField[0]": {"type S"}, + "typedefSliceField": {"field typedefSliceField", "type MySlice"}, + }) + + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() +} + +func Test_StructMap(t *testing.T) { + mkTest := func() *StructMap { + return &StructMap{ + MapKeyField: map[S]string{S("k"): "v"}, + MapValueField: map[string]S{"k": "v"}, + AliasMapKeyTypeField: AliasMapKeyType{"k": "v"}, + AliasMapValueTypeField: AliasMapValueType{"k": "v"}, + } + } + + st := localSchemeBuilder.Test(t) + st.Value(mkTest()).ExpectValidateFalseByPath(map[string][]string{ + "aliasMapKeyTypeField": {"field aliasMapKeyTypeField", "type MapKeyType", "type S"}, + "aliasMapValueTypeField": {"field aliasMapValueTypeField", "type MapValueType"}, + "aliasMapValueTypeField[k]": {"type S"}, + "mapKeyField": {"field mapKeyField", "type S"}, + "mapValueField": {"field mapValueField"}, + "mapValueField[k]": {"type S"}, + }) + + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() +} + +func Test_StructStruct(t *testing.T) { + mkTest := func() *StructStruct { + return &StructStruct{ + DirectComparableStructField: DirectComparableStruct{ + IntField: 1, + }, + NonDirectComparableStructField: NonDirectComparableStruct{ + IntPtrField: ptr.To(1), + }, + DirectComparableStructPtr: &DirectComparableStruct{ + IntField: 1, + }, + NonDirectComparableStructPtr: &NonDirectComparableStruct{ + IntPtrField: ptr.To(1), + }, + } + } + + st := localSchemeBuilder.Test(t) + st.Value(mkTest()).ExpectValidateFalseByPath(map[string][]string{ + "directComparableStructField": {"field directComparableStructField", "type DirectComparableStruct"}, + "directComparableStructField.intField": {"field intField"}, + "nonDirectComparableStructField": {"field nonDirectComparableStructField", "type NonDirectComparableStruct"}, + "nonDirectComparableStructField.intPtrField": {"field intPtrField"}, + "directComparableStructPtrField": {"field directComparableStructPtrField", "type DirectComparableStruct"}, + "directComparableStructPtrField.intField": {"field intField"}, + "nonDirectComparableStructPtrField": {"field nonDirectComparableStructPtrField", "type NonDirectComparableStruct"}, + "nonDirectComparableStructPtrField.intPtrField": {"field intPtrField"}, + "DirectComparableStruct": {"field DirectComparableStruct", "type DirectComparableStruct"}, + "DirectComparableStruct.intField": {"field intField"}, + "NonDirectComparableStruct": {"field NonDirectComparableStruct", "type NonDirectComparableStruct"}, + "NonDirectComparableStruct.intPtrField": {"field intPtrField"}, + }) + + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() +} + +func Test_StructEmbedded(t *testing.T) { + mkTest := func() *StructEmbedded { + return &StructEmbedded{ + DirectComparableStruct: DirectComparableStruct{ + IntField: 1, + }, + NonDirectComparableStruct: NonDirectComparableStruct{ + IntPtrField: ptr.To(1), + }, + NestedDirectComparableStructField: NestedDirectComparableStruct{ + DirectComparableStructField: DirectComparableStruct{ + IntField: 1, + }, + }, + NestedNonDirectComparableStructField: NestedNonDirectComparableStruct{ + NonDirectComparableStructField: NonDirectComparableStruct{ + IntPtrField: ptr.To(1), + }, + }, + } + } + + st := localSchemeBuilder.Test(t) + st.Value(mkTest()).ExpectValidateFalseByPath(map[string][]string{ + "directComparableStruct": { + "field DirectComparableStruct", "type DirectComparableStruct", + }, + "directComparableStruct.intField": { + "field intField", + }, + "nonDirectComparableStruct": { + "field NonDirectComparableStruct", "type NonDirectComparableStruct", + }, + "nonDirectComparableStruct.intPtrField": { + "field intPtrField", + }, + "nestedDirectComparableStructField": { + "field NestedDirectComparableStructField", "type NestedDirectComparableStruct", + }, + "nestedDirectComparableStructField.directComparableStructField": { + "field directComparableStructField", "type DirectComparableStruct", + }, + "nestedDirectComparableStructField.directComparableStructField.intField": { + "field intField", + }, + "nestedNonDirectComparableStructField": { + "field NestedNonDirectComparableStructField", "type NestedNonDirectComparableStruct", + }, + "nestedNonDirectComparableStructField.nonDirectComparableStructField": { + "field nonDirectComparableStructField", "type NonDirectComparableStruct", + }, + "nestedNonDirectComparableStructField.nonDirectComparableStructField.intPtrField": { + "field intPtrField", + }, + }) + + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() +} + +func Test_Mix(t *testing.T) { + st := localSchemeBuilder.Test(t) + st.Value(&MixComparableStruct{ + Primitive: "a", + }).OldValue(nil).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Invalid(field.NewPath("NonComparable"), "", ""), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/zz_generated.validations.go new file mode 100644 index 0000000000..87b4a3a8fd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/default_behavior/zz_generated.validations.go @@ -0,0 +1,914 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package defaultbehavior + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type MixComparableStruct + scheme.AddValidationFunc( + (*MixComparableStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MixComparableStruct( + ctx, op, nil, /* fldPath */ + obj.(*MixComparableStruct), + safe.Cast[*MixComparableStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructEmbedded + scheme.AddValidationFunc( + (*StructEmbedded)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructEmbedded( + ctx, op, nil, /* fldPath */ + obj.(*StructEmbedded), + safe.Cast[*StructEmbedded](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructMap + scheme.AddValidationFunc( + (*StructMap)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructMap( + ctx, op, nil, /* fldPath */ + obj.(*StructMap), + safe.Cast[*StructMap](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructPrimitive + scheme.AddValidationFunc( + (*StructPrimitive)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructPrimitive( + ctx, op, nil, /* fldPath */ + obj.(*StructPrimitive), + safe.Cast[*StructPrimitive](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructSlice + scheme.AddValidationFunc( + (*StructSlice)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructSlice( + ctx, op, nil, /* fldPath */ + obj.(*StructSlice), + safe.Cast[*StructSlice](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructStruct + scheme.AddValidationFunc( + (*StructStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructStruct( + ctx, op, nil, /* fldPath */ + obj.(*StructStruct), + safe.Cast[*StructStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_AliasMapKeyType validates an instance of AliasMapKeyType according +// to declarative validation rules in the API schema. +func Validate_AliasMapKeyType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj AliasMapKeyType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapKeyType"); len(e) != 0 { + errs = append(errs, e...) + } + + // iterate the map and call the key type's validation function + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, Validate_S); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_AliasMapValueType validates an instance of AliasMapValueType according +// to declarative validation rules in the API schema. +func Validate_AliasMapValueType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj AliasMapValueType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapValueType"); len(e) != 0 { + errs = append(errs, e...) + } + + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_S); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_DirectComparableStruct validates an instance of DirectComparableStruct according +// to declarative validation rules in the API schema. +func Validate_DirectComparableStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *DirectComparableStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type DirectComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field DirectComparableStruct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field intField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *DirectComparableStruct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_MixComparableStruct validates an instance of MixComparableStruct according +// to declarative validation rules in the API schema. +func Validate_MixComparableStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MixComparableStruct) (errs field.ErrorList) { + + // field MixComparableStruct.TypeMeta has no validation + // field MixComparableStruct.Primitive has no validation + + { // field MixComparableStruct.NonComparable + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field NonComparable"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MixComparableStruct) []string { + return oldObj.NonComparable + }) + errs = append(errs, fn(fldPath.Child("NonComparable"), obj.NonComparable, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_MySlice validates an instance of MySlice according +// to declarative validation rules in the API schema. +func Validate_MySlice( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MySlice) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MySlice"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_NestedDirectComparableStruct validates an instance of NestedDirectComparableStruct according +// to declarative validation rules in the API schema. +func Validate_NestedDirectComparableStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NestedDirectComparableStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type NestedDirectComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field NestedDirectComparableStruct.DirectComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj *DirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field directComparableStructField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_DirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NestedDirectComparableStruct) *DirectComparableStruct { + return &oldObj.DirectComparableStructField + }) + errs = append(errs, fn(fldPath.Child("directComparableStructField"), &obj.DirectComparableStructField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_NestedNonDirectComparableStruct validates an instance of NestedNonDirectComparableStruct according +// to declarative validation rules in the API schema. +func Validate_NestedNonDirectComparableStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NestedNonDirectComparableStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type NestedNonDirectComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field NestedNonDirectComparableStruct.NonDirectComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj *NonDirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field nonDirectComparableStructField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_NonDirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NestedNonDirectComparableStruct) *NonDirectComparableStruct { + return &oldObj.NonDirectComparableStructField + }) + errs = append(errs, fn(fldPath.Child("nonDirectComparableStructField"), &obj.NonDirectComparableStructField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_NonDirectComparableStruct validates an instance of NonDirectComparableStruct according +// to declarative validation rules in the API schema. +func Validate_NonDirectComparableStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NonDirectComparableStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type NonDirectComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field NonDirectComparableStruct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field intPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NonDirectComparableStruct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_S validates an instance of S according +// to declarative validation rules in the API schema. +func Validate_S( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *S) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type S"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_StructEmbedded validates an instance of StructEmbedded according +// to declarative validation rules in the API schema. +func Validate_StructEmbedded( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructEmbedded) (errs field.ErrorList) { + + // field StructEmbedded.TypeMeta has no validation + + { // field StructEmbedded.DirectComparableStruct + fn := func( + fldPath *field.Path, + obj, oldObj *DirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field DirectComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_DirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructEmbedded) *DirectComparableStruct { + return &oldObj.DirectComparableStruct + }) + errs = append(errs, fn(fldPath.Child("directComparableStruct"), &obj.DirectComparableStruct, oldVal, oldObj != nil)...) + } + + { // field StructEmbedded.NonDirectComparableStruct + fn := func( + fldPath *field.Path, + obj, oldObj *NonDirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field NonDirectComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_NonDirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructEmbedded) *NonDirectComparableStruct { + return &oldObj.NonDirectComparableStruct + }) + errs = append(errs, fn(fldPath.Child("nonDirectComparableStruct"), &obj.NonDirectComparableStruct, oldVal, oldObj != nil)...) + } + + { // field StructEmbedded.NestedDirectComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj *NestedDirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field NestedDirectComparableStructField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_NestedDirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructEmbedded) *NestedDirectComparableStruct { + return &oldObj.NestedDirectComparableStructField + }) + errs = append(errs, fn(fldPath.Child("nestedDirectComparableStructField"), &obj.NestedDirectComparableStructField, oldVal, oldObj != nil)...) + } + + { // field StructEmbedded.NestedNonDirectComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj *NestedNonDirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field NestedNonDirectComparableStructField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_NestedNonDirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructEmbedded) *NestedNonDirectComparableStruct { + return &oldObj.NestedNonDirectComparableStructField + }) + errs = append(errs, fn(fldPath.Child("nestedNonDirectComparableStructField"), &obj.NestedNonDirectComparableStructField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_StructMap validates an instance of StructMap according +// to declarative validation rules in the API schema. +func Validate_StructMap( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructMap) (errs field.ErrorList) { + + // field StructMap.TypeMeta has no validation + + { // field StructMap.MapKeyField + fn := func( + fldPath *field.Path, + obj, oldObj map[S]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field mapKeyField"); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the key type's validation function + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, Validate_S); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructMap) map[S]string { + return oldObj.MapKeyField + }) + errs = append(errs, fn(fldPath.Child("mapKeyField"), obj.MapKeyField, oldVal, oldObj != nil)...) + } + + { // field StructMap.MapValueField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]S, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field mapValueField"); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_S); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructMap) map[string]S { + return oldObj.MapValueField + }) + errs = append(errs, fn(fldPath.Child("mapValueField"), obj.MapValueField, oldVal, oldObj != nil)...) + } + + { // field StructMap.AliasMapKeyTypeField + fn := func( + fldPath *field.Path, + obj, oldObj AliasMapKeyType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field aliasMapKeyTypeField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_AliasMapKeyType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructMap) AliasMapKeyType { + return oldObj.AliasMapKeyTypeField + }) + errs = append(errs, fn(fldPath.Child("aliasMapKeyTypeField"), obj.AliasMapKeyTypeField, oldVal, oldObj != nil)...) + } + + { // field StructMap.AliasMapValueTypeField + fn := func( + fldPath *field.Path, + obj, oldObj AliasMapValueType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field aliasMapValueTypeField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_AliasMapValueType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructMap) AliasMapValueType { + return oldObj.AliasMapValueTypeField + }) + errs = append(errs, fn(fldPath.Child("aliasMapValueTypeField"), obj.AliasMapValueTypeField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_StructPrimitive validates an instance of StructPrimitive according +// to declarative validation rules in the API schema. +func Validate_StructPrimitive( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructPrimitive) (errs field.ErrorList) { + + // field StructPrimitive.TypeMeta has no validation + + { // field StructPrimitive.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field intField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructPrimitive) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field StructPrimitive.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field intPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructPrimitive) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_StructSlice validates an instance of StructSlice according +// to declarative validation rules in the API schema. +func Validate_StructSlice( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructSlice) (errs field.ErrorList) { + + // field StructSlice.TypeMeta has no validation + + { // field StructSlice.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []S, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field sliceField"); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_S); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []S { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.TypeDefSliceField + fn := func( + fldPath *field.Path, + obj, oldObj MySlice, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field typedefSliceField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MySlice(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) MySlice { + return oldObj.TypeDefSliceField + }) + errs = append(errs, fn(fldPath.Child("typedefSliceField"), obj.TypeDefSliceField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_StructStruct validates an instance of StructStruct according +// to declarative validation rules in the API schema. +func Validate_StructStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructStruct) (errs field.ErrorList) { + + // field StructStruct.TypeMeta has no validation + + { // field StructStruct.DirectComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj *DirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field directComparableStructField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_DirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructStruct) *DirectComparableStruct { + return &oldObj.DirectComparableStructField + }) + errs = append(errs, fn(fldPath.Child("directComparableStructField"), &obj.DirectComparableStructField, oldVal, oldObj != nil)...) + } + + { // field StructStruct.NonDirectComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj *NonDirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field nonDirectComparableStructField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_NonDirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructStruct) *NonDirectComparableStruct { + return &oldObj.NonDirectComparableStructField + }) + errs = append(errs, fn(fldPath.Child("nonDirectComparableStructField"), &obj.NonDirectComparableStructField, oldVal, oldObj != nil)...) + } + + { // field StructStruct.DirectComparableStructPtr + fn := func( + fldPath *field.Path, + obj, oldObj *DirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field directComparableStructPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_DirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructStruct) *DirectComparableStruct { + return oldObj.DirectComparableStructPtr + }) + errs = append(errs, fn(fldPath.Child("directComparableStructPtrField"), obj.DirectComparableStructPtr, oldVal, oldObj != nil)...) + } + + { // field StructStruct.NonDirectComparableStructPtr + fn := func( + fldPath *field.Path, + obj, oldObj *NonDirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field nonDirectComparableStructPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_NonDirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructStruct) *NonDirectComparableStruct { + return oldObj.NonDirectComparableStructPtr + }) + errs = append(errs, fn(fldPath.Child("nonDirectComparableStructPtrField"), obj.NonDirectComparableStructPtr, oldVal, oldObj != nil)...) + } + + { // field StructStruct.DirectComparableStruct + fn := func( + fldPath *field.Path, + obj, oldObj *DirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field DirectComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_DirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructStruct) *DirectComparableStruct { + return &oldObj.DirectComparableStruct + }) + errs = append(errs, fn(safe.Value(fldPath, func() *field.Path { return fldPath.Child("DirectComparableStruct") }), &obj.DirectComparableStruct, oldVal, oldObj != nil)...) + } + + { // field StructStruct.NonDirectComparableStruct + fn := func( + fldPath *field.Path, + obj, oldObj *NonDirectComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field NonDirectComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_NonDirectComparableStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructStruct) *NonDirectComparableStruct { + return &oldObj.NonDirectComparableStruct + }) + errs = append(errs, fn(safe.Value(fldPath, func() *field.Path { return fldPath.Child("NonDirectComparableStruct") }), &obj.NonDirectComparableStruct, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc.go new file mode 100644 index 0000000000..ffeb0126cc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc.go @@ -0,0 +1,122 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package list + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type StructSlice struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field AtomicSliceStringField[*]" + AtomicSliceStringField []StringType `json:"atomicSliceStringField"` + + // +k8s:eachVal=+k8s:validateFalse="field AtomicSliceTypeField[*]" + AtomicSliceTypeField IntSliceType `json:"atomicSliceTypeField"` + + // +k8s:eachVal=+k8s:validateFalse="field AtomicSliceComparableField[*]" + AtomicSliceComparableField []ComparableStruct `json:"atomicSliceComparableField"` + + // +k8s:eachVal=+k8s:validateFalse="field AtomicSliceNonComparableField[*]" + AtomicSliceNonComparableField []NonComparableStruct `json:"atomicSliceNonComparableField"` + + // +k8s:listType=set + // +k8s:eachVal=+k8s:validateFalse="field SetSliceComparableField[*]" + SetSliceComparableField []ComparableStruct `json:"setSliceComparableField"` + + // +k8s:listType=set + // +k8s:eachVal=+k8s:validateFalse="field SetSliceNonComparableField[*]" + SetSliceNonComparableField []NonComparableStruct `json:"setSliceNonComparableField"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:eachVal=+k8s:validateFalse="field MapSliceComparableField[*]" + MapSliceComparableField []ComparableStructWithKey `json:"mapSliceComparableField"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:eachVal=+k8s:validateFalse="field MapSliceNonComparableField[*]" + MapSliceNonComparableField []NonComparableStructWithKey `json:"mapSliceNonComparableField"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:eachVal=+k8s:validateFalse="field MapSlicePtrKeyField[*]" + MapSlicePtrKeyField []PtrKeyStruct `json:"mapSlicePtrKeyField"` + + // +k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + // +k8s:eachVal=+k8s:validateFalse="field MapSliceMixedKeyField[*]" + MapSliceMixedKeyField []MixedKeyStruct `json:"mapSliceMixedKeyField"` +} + +type StringType string +type IntSliceType []int + +type ComparableStruct struct { + IntField int `json:"intField"` +} + +// +k8s:validateFalse="type NonComparableStruct" +type NonComparableStruct struct { + IntPtrField *int `json:"intPtrField"` +} + +type ComparableStructWithKey struct { + Key string `json:"key"` + IntField int `json:"intField"` +} + +// +k8s:validateFalse="type NonComparableStructWithKey" +type NonComparableStructWithKey struct { + Key string `json:"key"` + IntPtrField *int `json:"intPtrField"` +} + +// +k8s:validateFalse="type PtrKeyStruct" +type PtrKeyStruct struct { + Key *string `json:"key"` + Data string `json:"data"` +} + +// +k8s:validateFalse="type MixedKeyStruct" +type MixedKeyStruct struct { + Key1 *string `json:"key1"` + Key2 string `json:"key2"` + Data string `json:"data"` +} + +type Item struct { + Key string `json:"key"` + + // +k8s:validateFalse="field Data" + Data map[string]string `json:"data"` +} + +type ItemList struct { + TypeMeta int + + // +k8s:listType=map + // +k8s:listMapKey=key + Items []Item `json:"items"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc_test.go new file mode 100644 index 0000000000..2621278ee9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/doc_test.go @@ -0,0 +1,117 @@ +/* +Copyright 2025 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 list + +import ( + "testing" + + "k8s.io/utils/ptr" +) + +func Test_StructSlice(t *testing.T) { + st := localSchemeBuilder.Test(t) + + invalidStructSlice := &StructSlice{ + AtomicSliceStringField: []StringType{""}, + AtomicSliceTypeField: IntSliceType{1}, + AtomicSliceComparableField: []ComparableStruct{{IntField: 1}}, + AtomicSliceNonComparableField: []NonComparableStruct{{IntPtrField: ptr.To(1)}}, + SetSliceComparableField: []ComparableStruct{{IntField: 1}}, + SetSliceNonComparableField: []NonComparableStruct{{IntPtrField: ptr.To(1)}}, + MapSliceComparableField: []ComparableStructWithKey{{Key: "x", IntField: 1}}, + MapSliceNonComparableField: []NonComparableStructWithKey{{Key: "x", IntPtrField: ptr.To(1)}}, + MapSlicePtrKeyField: []PtrKeyStruct{{Key: ptr.To("x"), Data: "y"}}, + MapSliceMixedKeyField: []MixedKeyStruct{{Key1: ptr.To("x"), Key2: "y", Data: "z"}}, + } + st.Value(invalidStructSlice).ExpectValidateFalseByPath(map[string][]string{ + "atomicSliceStringField[0]": {"field AtomicSliceStringField[*]"}, + "atomicSliceTypeField[0]": {"field AtomicSliceTypeField[*]"}, + "atomicSliceComparableField[0]": {"field AtomicSliceComparableField[*]"}, + "atomicSliceNonComparableField[0]": {"field AtomicSliceNonComparableField[*]", "type NonComparableStruct"}, + "setSliceComparableField[0]": {"field SetSliceComparableField[*]"}, + "setSliceNonComparableField[0]": {"field SetSliceNonComparableField[*]", "type NonComparableStruct"}, + "mapSliceComparableField[0]": {"field MapSliceComparableField[*]"}, + "mapSliceNonComparableField[0]": {"field MapSliceNonComparableField[*]", "type NonComparableStructWithKey"}, + "mapSlicePtrKeyField[0]": {"field MapSlicePtrKeyField[*]", "type PtrKeyStruct"}, + "mapSliceMixedKeyField[0]": {"field MapSliceMixedKeyField[*]", "type MixedKeyStruct"}, + }) + + // No changes. + st.Value(&invalidStructSlice).OldValue(&invalidStructSlice).ExpectValid() + + // Removed elements - errors on atomic. + st.Value(&StructSlice{ + AtomicSliceStringField: []StringType{""}, + AtomicSliceTypeField: IntSliceType{1}, + AtomicSliceComparableField: []ComparableStruct{{IntField: 1}}, + AtomicSliceNonComparableField: []NonComparableStruct{{IntPtrField: ptr.To(1)}}, + SetSliceComparableField: []ComparableStruct{{IntField: 1}}, + SetSliceNonComparableField: []NonComparableStruct{{IntPtrField: ptr.To(1)}}, + MapSliceComparableField: []ComparableStructWithKey{{Key: "x", IntField: 1}}, + MapSliceNonComparableField: []NonComparableStructWithKey{{Key: "x", IntPtrField: ptr.To(1)}}, + MapSlicePtrKeyField: []PtrKeyStruct{{Key: ptr.To("x"), Data: "y"}}, + MapSliceMixedKeyField: []MixedKeyStruct{{Key1: ptr.To("x"), Key2: "y", Data: "z"}}, + }).OldValue(&StructSlice{ + AtomicSliceStringField: []StringType{"", "x"}, + AtomicSliceTypeField: IntSliceType{1, 2}, + AtomicSliceComparableField: []ComparableStruct{{IntField: 2}, {IntField: 1}}, + AtomicSliceNonComparableField: []NonComparableStruct{{IntPtrField: ptr.To(2)}, {IntPtrField: ptr.To(1)}}, + SetSliceComparableField: []ComparableStruct{{IntField: 2}, {IntField: 1}}, + SetSliceNonComparableField: []NonComparableStruct{{IntPtrField: ptr.To(2)}, {IntPtrField: ptr.To(1)}}, + MapSliceComparableField: []ComparableStructWithKey{{Key: "y", IntField: 2}, {Key: "x", IntField: 1}}, + MapSliceNonComparableField: []NonComparableStructWithKey{{Key: "y", IntPtrField: ptr.To(2)}, {Key: "x", IntPtrField: ptr.To(1)}}, + MapSlicePtrKeyField: []PtrKeyStruct{{Key: ptr.To("a"), Data: "b"}, {Key: ptr.To("x"), Data: "y"}}, + MapSliceMixedKeyField: []MixedKeyStruct{{Key1: ptr.To("a"), Key2: "b", Data: "c"}, {Key1: ptr.To("x"), Key2: "y", Data: "z"}}, + }).ExpectValidateFalseByPath(map[string][]string{"atomicSliceStringField[0]": {"field AtomicSliceStringField[*]"}, + "atomicSliceTypeField[0]": {"field AtomicSliceTypeField[*]"}, + "atomicSliceComparableField[0]": {"field AtomicSliceComparableField[*]"}, + "atomicSliceNonComparableField[0]": {"field AtomicSliceNonComparableField[*]", "type NonComparableStruct"}, + }) + + // Same data, different order. + st.Value(&StructSlice{ + SetSliceComparableField: []ComparableStruct{{IntField: 2}, {IntField: 1}}, + SetSliceNonComparableField: []NonComparableStruct{{IntPtrField: ptr.To(2)}, {IntPtrField: ptr.To(1)}}, + MapSliceComparableField: []ComparableStructWithKey{{Key: "y", IntField: 2}, {Key: "x", IntField: 1}}, + MapSliceNonComparableField: []NonComparableStructWithKey{{Key: "y", IntPtrField: ptr.To(2)}, {Key: "x", IntPtrField: ptr.To(1)}}, + MapSlicePtrKeyField: []PtrKeyStruct{{Key: ptr.To("b"), Data: "2"}, {Key: ptr.To("a"), Data: "1"}}, + MapSliceMixedKeyField: []MixedKeyStruct{{Key1: ptr.To("b"), Key2: "2", Data: "B"}, {Key1: ptr.To("a"), Key2: "1", Data: "A"}}, + }).OldValue(&StructSlice{ + SetSliceComparableField: []ComparableStruct{{IntField: 1}, {IntField: 2}}, + SetSliceNonComparableField: []NonComparableStruct{{IntPtrField: ptr.To(1)}, {IntPtrField: ptr.To(2)}}, + MapSliceComparableField: []ComparableStructWithKey{{Key: "x", IntField: 1}, {Key: "y", IntField: 2}}, + MapSliceNonComparableField: []NonComparableStructWithKey{{Key: "x", IntPtrField: ptr.To(1)}, {Key: "y", IntPtrField: ptr.To(2)}}, + MapSlicePtrKeyField: []PtrKeyStruct{{Key: ptr.To("a"), Data: "1"}, {Key: ptr.To("b"), Data: "2"}}, + MapSliceMixedKeyField: []MixedKeyStruct{{Key1: ptr.To("a"), Key2: "1", Data: "A"}, {Key1: ptr.To("b"), Key2: "2", Data: "B"}}, + }).ExpectValid() +} + +func Test_Items(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ItemList{ + Items: []Item{ + {Key: "valid2"}, + }, + }).OldValue(&ItemList{ + Items: []Item{ + {Key: "valid1"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + "items[0].data": {"field Data"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/zz_generated.validations.go new file mode 100644 index 0000000000..9b6ecf48a5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/list/zz_generated.validations.go @@ -0,0 +1,562 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package list + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ItemList + scheme.AddValidationFunc( + (*ItemList)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ItemList( + ctx, op, nil, /* fldPath */ + obj.(*ItemList), + safe.Cast[*ItemList](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructSlice + scheme.AddValidationFunc( + (*StructSlice)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructSlice( + ctx, op, nil, /* fldPath */ + obj.(*StructSlice), + safe.Cast[*StructSlice](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Item validates an instance of Item according +// to declarative validation rules in the API schema. +func Validate_Item( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Item) (errs field.ErrorList) { + + // field Item.Key has no validation + + { // field Item.Data + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Data"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Item) map[string]string { + return oldObj.Data + }) + errs = append(errs, fn(fldPath.Child("data"), obj.Data, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ItemList validates an instance of ItemList according +// to declarative validation rules in the API schema. +func Validate_ItemList( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ItemList) (errs field.ErrorList) { + + // field ItemList.TypeMeta has no validation + + { // field ItemList.Items + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }, deepEqualImpl_, Validate_Item); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ItemList) []Item { + return oldObj.Items + }) + errs = append(errs, fn(fldPath.Child("items"), obj.Items, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_MixedKeyStruct validates an instance of MixedKeyStruct according +// to declarative validation rules in the API schema. +func Validate_MixedKeyStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MixedKeyStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MixedKeyStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field MixedKeyStruct.Key1 has no validation + // field MixedKeyStruct.Key2 has no validation + // field MixedKeyStruct.Data has no validation + return errs +} + +// Validate_NonComparableStruct validates an instance of NonComparableStruct according +// to declarative validation rules in the API schema. +func Validate_NonComparableStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NonComparableStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type NonComparableStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field NonComparableStruct.IntPtrField has no validation + return errs +} + +// Validate_NonComparableStructWithKey validates an instance of NonComparableStructWithKey according +// to declarative validation rules in the API schema. +func Validate_NonComparableStructWithKey( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NonComparableStructWithKey) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type NonComparableStructWithKey"); len(e) != 0 { + errs = append(errs, e...) + } + + // field NonComparableStructWithKey.Key has no validation + // field NonComparableStructWithKey.IntPtrField has no validation + return errs +} + +// Validate_PtrKeyStruct validates an instance of PtrKeyStruct according +// to declarative validation rules in the API schema. +func Validate_PtrKeyStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *PtrKeyStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type PtrKeyStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field PtrKeyStruct.Key has no validation + // field PtrKeyStruct.Data has no validation + return errs +} + +// Validate_StructSlice validates an instance of StructSlice according +// to declarative validation rules in the API schema. +func Validate_StructSlice( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructSlice) (errs field.ErrorList) { + + // field StructSlice.TypeMeta has no validation + + { // field StructSlice.AtomicSliceStringField + fn := func( + fldPath *field.Path, + obj, oldObj []StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field AtomicSliceStringField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []StringType { + return oldObj.AtomicSliceStringField + }) + errs = append(errs, fn(fldPath.Child("atomicSliceStringField"), obj.AtomicSliceStringField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.AtomicSliceTypeField + fn := func( + fldPath *field.Path, + obj, oldObj IntSliceType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field AtomicSliceTypeField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) IntSliceType { + return oldObj.AtomicSliceTypeField + }) + errs = append(errs, fn(fldPath.Child("atomicSliceTypeField"), obj.AtomicSliceTypeField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.AtomicSliceComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []ComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field AtomicSliceComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []ComparableStruct { + return oldObj.AtomicSliceComparableField + }) + errs = append(errs, fn(fldPath.Child("atomicSliceComparableField"), obj.AtomicSliceComparableField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.AtomicSliceNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field AtomicSliceNonComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_NonComparableStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []NonComparableStruct { + return oldObj.AtomicSliceNonComparableField + }) + errs = append(errs, fn(fldPath.Child("atomicSliceNonComparableField"), obj.AtomicSliceNonComparableField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.SetSliceComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []ComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field SetSliceComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []ComparableStruct { + return oldObj.SetSliceComparableField + }) + errs = append(errs, fn(fldPath.Child("setSliceComparableField"), obj.SetSliceComparableField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.SetSliceNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field SetSliceNonComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, nil, Validate_NonComparableStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []NonComparableStruct { + return oldObj.SetSliceNonComparableField + }) + errs = append(errs, fn(fldPath.Child("setSliceNonComparableField"), obj.SetSliceNonComparableField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.MapSliceComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []ComparableStructWithKey, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *ComparableStructWithKey, b *ComparableStructWithKey) bool { return a.Key == b.Key }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComparableStructWithKey) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSliceComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *ComparableStructWithKey, b *ComparableStructWithKey) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []ComparableStructWithKey { + return oldObj.MapSliceComparableField + }) + errs = append(errs, fn(fldPath.Child("mapSliceComparableField"), obj.MapSliceComparableField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.MapSliceNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStructWithKey, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *NonComparableStructWithKey, b *NonComparableStructWithKey) bool { return a.Key == b.Key }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStructWithKey) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSliceNonComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *NonComparableStructWithKey, b *NonComparableStructWithKey) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *NonComparableStructWithKey, b *NonComparableStructWithKey) bool { return a.Key == b.Key }, deepEqualImpl_, Validate_NonComparableStructWithKey); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []NonComparableStructWithKey { + return oldObj.MapSliceNonComparableField + }) + errs = append(errs, fn(fldPath.Child("mapSliceNonComparableField"), obj.MapSliceNonComparableField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.MapSlicePtrKeyField + fn := func( + fldPath *field.Path, + obj, oldObj []PtrKeyStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *PtrKeyStruct, b *PtrKeyStruct) bool { + return ((a.Key == nil && b.Key == nil) || (a.Key != nil && b.Key != nil && *a.Key == *b.Key)) + }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *PtrKeyStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSlicePtrKeyField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *PtrKeyStruct, b *PtrKeyStruct) bool { + return ((a.Key == nil && b.Key == nil) || (a.Key != nil && b.Key != nil && *a.Key == *b.Key)) + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *PtrKeyStruct, b *PtrKeyStruct) bool { + return ((a.Key == nil && b.Key == nil) || (a.Key != nil && b.Key != nil && *a.Key == *b.Key)) + }, deepEqualImpl_, Validate_PtrKeyStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []PtrKeyStruct { + return oldObj.MapSlicePtrKeyField + }) + errs = append(errs, fn(fldPath.Child("mapSlicePtrKeyField"), obj.MapSlicePtrKeyField, oldVal, oldObj != nil)...) + } + + { // field StructSlice.MapSliceMixedKeyField + fn := func( + fldPath *field.Path, + obj, oldObj []MixedKeyStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *MixedKeyStruct, b *MixedKeyStruct) bool { + return ((a.Key1 == nil && b.Key1 == nil) || (a.Key1 != nil && b.Key1 != nil && *a.Key1 == *b.Key1)) && a.Key2 == b.Key2 + }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedKeyStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapSliceMixedKeyField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MixedKeyStruct, b *MixedKeyStruct) bool { + return ((a.Key1 == nil && b.Key1 == nil) || (a.Key1 != nil && b.Key1 != nil && *a.Key1 == *b.Key1)) && a.Key2 == b.Key2 + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *MixedKeyStruct, b *MixedKeyStruct) bool { + return ((a.Key1 == nil && b.Key1 == nil) || (a.Key1 != nil && b.Key1 != nil && *a.Key1 == *b.Key1)) && a.Key2 == b.Key2 + }, deepEqualImpl_, Validate_MixedKeyStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructSlice) []MixedKeyStruct { + return oldObj.MapSliceMixedKeyField + }) + errs = append(errs, fn(fldPath.Child("mapSliceMixedKeyField"), obj.MapSliceMixedKeyField, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc.go new file mode 100644 index 0000000000..dc7ea87ee3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc.go @@ -0,0 +1,51 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package eachkey + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapField(keys)" + MapField map[string]string `json:"mapField"` + + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapTypedefField(keys)" + MapTypedefField map[UnvalidatedStringType]string `json:"mapTypedefField"` + + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapValidatedTypedefField(keys)" + MapValidatedTypedefField map[ValidatedStringType]string `json:"mapValidatedTypedefField"` + + // +k8s:eachKey=+k8s:validateFalse="field Struct.ValidatedMapTypeField(keys)" + ValidatedMapTypeField ValidatedMapType `json:"validatedMapTypeField"` +} + +// Note: no validations. +type UnvalidatedStringType string + +// +k8s:validateFalse="ValidatedStringType" +type ValidatedStringType string + +// +k8s:eachKey=+k8s:validateFalse="type ValidatedMapType(keys)" +type ValidatedMapType map[string]string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc_test.go new file mode 100644 index 0000000000..18c8ccc90d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/doc_test.go @@ -0,0 +1,51 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +package eachkey + +import ( + "testing" +) + +func Test_Struct(t *testing.T) { + mkTest := func() *Struct { + return &Struct{ + MapField: map[string]string{"x": "y"}, + MapTypedefField: map[UnvalidatedStringType]string{ + "x": "y", + }, + MapValidatedTypedefField: map[ValidatedStringType]string{ + "x": "y", + }, + ValidatedMapTypeField: map[string]string{ + "x": "y", + }, + } + } + st := localSchemeBuilder.Test(t) + st.Value(mkTest()).ExpectValidateFalseByPath(map[string][]string{ + "mapField": {"field Struct.MapField(keys)"}, + "mapTypedefField": {"field Struct.MapTypedefField(keys)"}, + "mapValidatedTypedefField": {"ValidatedStringType", "field Struct.MapValidatedTypedefField(keys)"}, + "validatedMapTypeField": {"field Struct.ValidatedMapTypeField(keys)", "type ValidatedMapType(keys)"}, + }) + + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/zz_generated.validations.go new file mode 100644 index 0000000000..5405676afc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachkey/zz_generated.validations.go @@ -0,0 +1,211 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package eachkey + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[UnvalidatedStringType]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UnvalidatedStringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[UnvalidatedStringType]string { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[ValidatedStringType]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ValidatedStringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapValidatedTypedefField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the key type's validation function + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, Validate_ValidatedStringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[ValidatedStringType]string { + return oldObj.MapValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapValidatedTypedefField"), obj.MapValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedMapTypeField + fn := func( + fldPath *field.Path, + obj, oldObj ValidatedMapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ValidatedMapTypeField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ValidatedMapType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ValidatedMapType { + return oldObj.ValidatedMapTypeField + }) + errs = append(errs, fn(fldPath.Child("validatedMapTypeField"), obj.ValidatedMapTypeField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ValidatedMapType validates an instance of ValidatedMapType according +// to declarative validation rules in the API schema. +func Validate_ValidatedMapType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ValidatedMapType) (errs field.ErrorList) { + + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ValidatedMapType(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ValidatedStringType validates an instance of ValidatedStringType according +// to declarative validation rules in the API schema. +func Validate_ValidatedStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ValidatedStringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "ValidatedStringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc.go new file mode 100644 index 0000000000..b452c393f2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc.go @@ -0,0 +1,52 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package eachval + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type StructWithMaps struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field MapTest.MapPrimitiveField[*]" + MapPrimitiveField map[string]string `json:"mapPrimitiveField"` + + // +k8s:eachVal=+k8s:validateFalse="field MapTest.MapTypedefField[*]" + MapTypedefField map[string]StringType `json:"mapTypedefField"` + + // +k8s:eachVal=+k8s:validateFalse="field MapTest.MapComparableStructField[*]" + MapComparableStructField map[string]ComparableStruct `json:"mapComparableStructField"` + + // +k8s:eachVal=+k8s:validateFalse="field MapTest.MapNonComparableStructField[*]" + MapNonComparableStructField map[string]NonComparableStruct `json:"mapNonComparableStructField"` +} + +type StringType string + +type ComparableStruct struct { + IntField int `json:"intField"` +} + +type NonComparableStruct struct { + IntPtrField *int `json:"intPtrField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc_test.go new file mode 100644 index 0000000000..554b384d6e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/doc_test.go @@ -0,0 +1,52 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +package eachval + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test_StructWithMaps(t *testing.T) { + mkTest := func() *StructWithMaps { + return &StructWithMaps{ + MapPrimitiveField: map[string]string{"x": "y"}, + MapTypedefField: map[string]StringType{"x": "y"}, + MapComparableStructField: map[string]ComparableStruct{ + "x": {IntField: 1}, + }, + MapNonComparableStructField: map[string]NonComparableStruct{ + "x": {IntPtrField: ptr.To(1)}, + }, + } + } + + st := localSchemeBuilder.Test(t) + st.Value(mkTest()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Invalid(field.NewPath("mapPrimitiveField[x]"), "y", ""), + field.Invalid(field.NewPath("mapTypedefField[x]"), "y", ""), + field.Invalid(field.NewPath("mapComparableStructField[x]"), "", ""), + field.Invalid(field.NewPath("mapNonComparableStructField[x]"), "", ""), + }) + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/zz_generated.validations.go new file mode 100644 index 0000000000..183797afbb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/maps/eachval/zz_generated.validations.go @@ -0,0 +1,181 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package eachval + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type StructWithMaps + scheme.AddValidationFunc( + (*StructWithMaps)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructWithMaps( + ctx, op, nil, /* fldPath */ + obj.(*StructWithMaps), + safe.Cast[*StructWithMaps](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_StructWithMaps validates an instance of StructWithMaps according +// to declarative validation rules in the API schema. +func Validate_StructWithMaps( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructWithMaps) (errs field.ErrorList) { + + // field StructWithMaps.TypeMeta has no validation + + { // field StructWithMaps.MapPrimitiveField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapTest.MapPrimitiveField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructWithMaps) map[string]string { + return oldObj.MapPrimitiveField + }) + errs = append(errs, fn(fldPath.Child("mapPrimitiveField"), obj.MapPrimitiveField, oldVal, oldObj != nil)...) + } + + { // field StructWithMaps.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapTest.MapTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructWithMaps) map[string]StringType { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + { // field StructWithMaps.MapComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]ComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapTest.MapComparableStructField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructWithMaps) map[string]ComparableStruct { + return oldObj.MapComparableStructField + }) + errs = append(errs, fn(fldPath.Child("mapComparableStructField"), obj.MapComparableStructField, oldVal, oldObj != nil)...) + } + + { // field StructWithMaps.MapNonComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field MapTest.MapNonComparableStructField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructWithMaps) map[string]NonComparableStruct { + return oldObj.MapNonComparableStructField + }) + errs = append(errs, fn(fldPath.Child("mapNonComparableStructField"), obj.MapNonComparableStructField, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc.go new file mode 100644 index 0000000000..c8ed5b4503 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc.go @@ -0,0 +1,47 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package subfield + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:subfield(intField)=+k8s:validateFalse="field IntField" + // +k8s:subfield(intPtrField)=+k8s:validateFalse="field IntPtrField" + SubStructField SubStruct `json:"subStructField"` +} + +type SubStruct struct { + IntField int `json:"intField"` + IntPtrField *int `json:"intPtrField"` +} + +// +k8s:subfield(intField)=+k8s:validateFalse="field IntField" +// +k8s:subfield(intPtrField)=+k8s:validateFalse="field IntPtrField" +type StructWithSubfield struct { + TypeMeta int + IntField int `json:"intField"` + IntPtrField *int `json:"intPtrField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc_test.go new file mode 100644 index 0000000000..0b093937d7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/doc_test.go @@ -0,0 +1,70 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +package subfield + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + st.Value(&Struct{ + SubStructField: SubStruct{ + IntField: 1, + IntPtrField: ptr.To(1), + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Invalid(field.NewPath("subStructField").Child("intField"), 1, "field IntField"), + field.Invalid(field.NewPath("subStructField").Child("intPtrField"), 1, "field IntPtrField"), + }) + + st.Value(&StructWithSubfield{ + IntField: 1, + IntPtrField: ptr.To(1), + }).OldValue(&StructWithSubfield{ + IntField: 1, + IntPtrField: ptr.To(1), + }).ExpectValid() +} + +func TestStructWithSubfield(t *testing.T) { + st := localSchemeBuilder.Test(t) + st.Value(&StructWithSubfield{ + IntField: 1, + IntPtrField: ptr.To(1), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), 1, "field IntField"), + field.Invalid(field.NewPath("intPtrField"), 1, "field IntPtrField"), + }) + + st.Value(&StructWithSubfield{ + TypeMeta: 1, + IntField: 1, + IntPtrField: ptr.To(1), + }).OldValue(&StructWithSubfield{ + TypeMeta: 1, + IntField: 1, + IntPtrField: ptr.To(1), + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/zz_generated.validations.go new file mode 100644 index 0000000000..767718e2a2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/ratcheting/subfield/zz_generated.validations.go @@ -0,0 +1,153 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package subfield + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructWithSubfield + scheme.AddValidationFunc( + (*StructWithSubfield)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructWithSubfield( + ctx, op, nil, /* fldPath */ + obj.(*StructWithSubfield), + safe.Cast[*StructWithSubfield](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.SubStructField + fn := func( + fldPath *field.Path, + obj, oldObj *SubStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "intField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "intField", + func(o *SubStruct) *int { return &o.IntField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field IntField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "intPtrField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "intPtrField", + func(o *SubStruct) *int { return o.IntPtrField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field IntPtrField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *SubStruct { + return &oldObj.SubStructField + }) + errs = append(errs, fn(fldPath.Child("subStructField"), &obj.SubStructField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_StructWithSubfield validates an instance of StructWithSubfield according +// to declarative validation rules in the API schema. +func Validate_StructWithSubfield( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructWithSubfield) (errs field.ErrorList) { + + func() { // cohort = "intField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "intField", + func(o *StructWithSubfield) *int { return &o.IntField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field IntField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "intPtrField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "intPtrField", + func(o *StructWithSubfield) *int { return o.IntPtrField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field IntPtrField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + + // field StructWithSubfield.TypeMeta has no validation + // field StructWithSubfield.IntField has no validation + // field StructWithSubfield.IntPtrField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/doc.go new file mode 100644 index 0000000000..ba0aa986b0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/doc.go @@ -0,0 +1,74 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package maps + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// This test case is carefully constructed to test recursion. We don't want to +// add more `validateFalse` tags because the bug that motivated this test +// wasn't looking deep enough into the recursion tree. +// +// Expectations: +// * We should emit validation for T1 because T3 has validation. +// * We should emit validation for T2 because it uses T1, which has validation. +// * We should emit validation for T3 because it has validation. +// * We should emit validation for T4 because it uses T3, which has validation. +// * T1 should call T2 and T3. +// * T2 should call eachVal(T1). +// * T3 should call T4. +// * T4 should call eachVal(T3). +// * T5 and T6 hold a map of themselves, reached via T1 so the map type is +// still being discovered when its own field is processed. + +type T1 struct { + T2 T2 `json:"t2"` + T3 T3 `json:"t3"` + T5 map[string]T5 `json:"t5"` + T6 map[string]T6 `json:"t6"` +} + +type T2 struct { + MT1 map[string]T1 `json:"mt1"` +} + +// +k8s:validateFalse="type T3" +type T3 struct { + T4 T4 `json:"t4"` +} + +type T4 struct { + MT3 map[string]T3 `json:"mt3"` +} + +type T5 struct { + // +k8s:validateFalse="type T5.T5" + T5 map[string]T5 `json:"t5"` +} + +type T6 struct { + // +k8s:validateFalse="type T6.S" + S string `json:"s"` + T6 map[string]T6 `json:"t6"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/testdata/validate-false.json new file mode 100644 index 0000000000..61c61b2d39 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/testdata/validate-false.json @@ -0,0 +1,677 @@ +{ + "*maps.T1": { + "t2.mt1[+].t2.mt1[_;熱_ɬ泌Ʀ妺眏ĮBȖ鯡].t3": [ + "type T3" + ], + "t2.mt1[+].t2.mt1[_;熱_ɬ泌Ʀ妺眏ĮBȖ鯡].t5[].t5": [ + "type T5.T5" + ], + "t2.mt1[+].t2.mt1[_;熱_ɬ泌Ʀ妺眏ĮBȖ鯡].t6[].s": [ + "type T6.S" + ], + "t2.mt1[+].t2.mt1[ʍctƓ晲].t3": [ + "type T3" + ], + "t2.mt1[+].t2.mt1[ʍctƓ晲].t5[].t5": [ + "type T5.T5" + ], + "t2.mt1[+].t2.mt1[ʍctƓ晲].t6[].s": [ + "type T6.S" + ], + "t2.mt1[+].t3": [ + "type T3" + ], + "t2.mt1[+].t3.t4.mt3[ǿ\u003cɀEɡđ]": [ + "type T3" + ], + "t2.mt1[+].t3.t4.mt3[能鷸ȳ琍殪\"g]": [ + "type T3" + ], + "t2.mt1[+].t5[+ʙ堠齦].t5": [ + "type T5.T5" + ], + "t2.mt1[+].t5[+ʙ堠齦].t5[e自侾^x索Ŗb$ž駥xa豘].t5": [ + "type T5.T5" + ], + "t2.mt1[+].t5[+ʙ堠齦].t5[ý譎颻滧ǖvq灊摒嫸yĵȲ梚鿱o].t5": [ + "type T5.T5" + ], + "t2.mt1[+].t5[Z?bư/L卟Ƒ»].t5": [ + "type T5.T5" + ], + "t2.mt1[+].t5[Z?bư/L卟Ƒ»].t5[ũ答B].t5": [ + "type T5.T5" + ], + "t2.mt1[+].t5[Z?bư/L卟Ƒ»].t5[巭蜏恝墜ǔ爡絵/].t5": [ + "type T5.T5" + ], + "t2.mt1[+].t6[q7狭姫].s": [ + "type T6.S" + ], + "t2.mt1[+].t6[q7狭姫].t6[ĩ}賹8秈卅Uʓ].s": [ + "type T6.S" + ], + "t2.mt1[+].t6[q7狭姫].t6[Ƈÿ櫌駘\u0026鮫嫁k掝¦{\"].s": [ + "type T6.S" + ], + "t2.mt1[+].t6[趸鸎|烈膞`蠯].s": [ + "type T6.S" + ], + "t2.mt1[+].t6[趸鸎|烈膞`蠯].t6[uɸ¢\u003c1馺蕰×Oǝ硭ɜ]ƠX,預].s": [ + "type T6.S" + ], + "t2.mt1[+].t6[趸鸎|烈膞`蠯].t6[Ȓ彃\u003erSʐ礕EɌ魆ʚ簬Ķ].s": [ + "type T6.S" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[ǪŰM旰綷罨袢捺悲ȅ].t3": [ + "type T3" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[ǪŰM旰綷罨袢捺悲ȅ].t5[].t5": [ + "type T5.T5" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[ǪŰM旰綷罨袢捺悲ȅ].t6[].s": [ + "type T6.S" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t3": [ + "type T3" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t5[].t5": [ + "type T5.T5" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t6[].s": [ + "type T6.S" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t3": [ + "type T3" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t3.t4.mt3[av$蛷)讻]": [ + "type T3" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t3.t4.mt3[烱ęssĂZ稈V噘¢\u003eóDz岋笨Gń條]": [ + "type T3" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[eYF墄[C_睿ȉǫ蹟t´].t5": [ + "type T5.T5" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[eYF墄[C_睿ȉǫ蹟t´].t5[Ƣ板鋩伸~槱¡rŔ綂2暮斚嬆ʅȀ].t5": [ + "type T5.T5" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[eYF墄[C_睿ȉǫ蹟t´].t5[ș$0đ\u003eƯ]ɹȽ東].t5": [ + "type T5.T5" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[w飃m=Sx].t5": [ + "type T5.T5" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[w飃m=Sx].t5[Gčü櫒}眙+ËQLuVǭz瘯霡豐Ȭ-].t5": [ + "type T5.T5" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[w飃m=Sx].t5[].t5": [ + "type T5.T5" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[`ȝ懿沇].s": [ + "type T6.S" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[`ȝ懿沇].t6[6ɀk諮Ȃ遃ɸ0_榣ǝ祇Fæƭ].s": [ + "type T6.S" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[`ȝ懿沇].t6[].s": [ + "type T6.S" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[跮ɽ溢煮FɀÄf渂硹譯].s": [ + "type T6.S" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[跮ɽ溢煮FɀÄf渂硹譯].t6[uvȭƹ棳ɷk].s": [ + "type T6.S" + ], + "t2.mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[跮ɽ溢煮FɀÄf渂硹譯].t6[蕓ư銩6ij憏].s": [ + "type T6.S" + ], + "t3": [ + "type T3" + ], + "t3.t4.mt3[ǣ侹rC綹l\"Ęłė僫ĿƳ犿]": [ + "type T3" + ], + "t3.t4.mt3[ǣ侹rC綹l\"Ęłė僫ĿƳ犿].t4.mt3[Eɧ]": [ + "type T3" + ], + "t3.t4.mt3[ǣ侹rC綹l\"Ęłė僫ĿƳ犿].t4.mt3[榋:$?ʐiÅ圁椯ĤɹN匛9唟摴aĹƵ]": [ + "type T3" + ], + "t3.t4.mt3[ʁƂ埢1_匘!v5轱苤犄4y]": [ + "type T3" + ], + "t3.t4.mt3[ʁƂ埢1_匘!v5轱苤犄4y].t4.mt3[Ĕ1B]": [ + "type T3" + ], + "t3.t4.mt3[ʁƂ埢1_匘!v5轱苤犄4y].t4.mt3[ɂƚ浻WEȕ]": [ + "type T3" + ], + "t5[I坬繣ʦȜ].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[D6üɁ].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[D6üɁ].t5[5ʠ稑z].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[D6üɁ].t5[5ʠ稑z].t5[].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[D6üɁ].t5[å偔7鉰竡弓Ū駢].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[D6üɁ].t5[å偔7鉰竡弓Ū駢].t5[].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[ɡ鱣ɥʘ].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[ɡ鱣ɥʘ].t5[1i茅飱Ï^ʥ甅俹玄8ñɧű袡].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[ɡ鱣ɥʘ].t5[1i茅飱Ï^ʥ甅俹玄8ñɧű袡].t5[].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[ɡ鱣ɥʘ].t5[皸ąp垊渂ċ0ʦ+椎;ř4臨栱].t5": [ + "type T5.T5" + ], + "t5[I坬繣ʦȜ].t5[ɡ鱣ɥʘ].t5[皸ąp垊渂ċ0ʦ+椎;ř4臨栱].t5[].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[œ鸽=UPIªL2ĉ鯙Ĭ=].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[œ鸽=UPIªL2ĉ鯙Ĭ=].t5[u].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[œ鸽=UPIªL2ĉ鯙Ĭ=].t5[u].t5[].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[œ鸽=UPIªL2ĉ鯙Ĭ=].t5[ʫ嗼ÿá\\+4纵e%Ȕ諜].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[œ鸽=UPIªL2ĉ鯙Ĭ=].t5[ʫ嗼ÿá\\+4纵e%Ȕ諜].t5[].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[臒nj1].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[臒nj1].t5[*'%Ƿɋ媌廜Ś闋UǾ沲胑ǁ唪].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[臒nj1].t5[*'%Ƿɋ媌廜Ś闋UǾ沲胑ǁ唪].t5[].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[臒nj1].t5[ĝ尸Å袕5誄X^疒zP椚ƍ鷾髣蠾Hǣ].t5": [ + "type T5.T5" + ], + "t5[ʔ搪ŷ臹裂fɥǻŎB\u003e嫡g|妯C\u003e揕].t5[臒nj1].t5[ĝ尸Å袕5誄X^疒zP椚ƍ鷾髣蠾Hǣ].t5[].t5": [ + "type T5.T5" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[E嵥~ƆŎƹMVDz甋U慕當鮁].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[E嵥~ƆŎƹMVDz甋U慕當鮁].t6[\\ƱZ\u003cs啘İƊ阼Ŝ¾0Nj].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[E嵥~ƆŎƹMVDz甋U慕當鮁].t6[\\ƱZ\u003cs啘İƊ阼Ŝ¾0Nj].t6[].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[E嵥~ƆŎƹMVDz甋U慕當鮁].t6[Ɛ].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[E嵥~ƆŎƹMVDz甋U慕當鮁].t6[Ɛ].t6[].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[產].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[產].t6[/ƹ)pʖ竰].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[產].t6[/ƹ)pʖ竰].t6[].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[產].t6[燊a戉RɤƯæ榐].s": [ + "type T6.S" + ], + "t6[n9Š5詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[產].t6[燊a戉RɤƯæ榐].t6[].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[;ǜŻğr蜌].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[;ǜŻğr蜌].t6[辛].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[;ǜŻğr蜌].t6[辛].t6[].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[;ǜŻğr蜌].t6[颢c慽wikɮ5Y羁n蛣[鿱挹ñ].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[;ǜŻğr蜌].t6[颢c慽wikɮ5Y羁n蛣[鿱挹ñ].t6[].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[勝ǃ滪Ĉ³œ!閍0ʤ悕A1HMĴ!].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[勝ǃ滪Ĉ³œ!閍0ʤ悕A1HMĴ!].t6[:].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[勝ǃ滪Ĉ³œ!閍0ʤ悕A1HMĴ!].t6[:].t6[].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[勝ǃ滪Ĉ³œ!閍0ʤ悕A1HMĴ!].t6[Ȩ\u003e5咓Yȧâ{Uu缜犌菑ȃ環?钗Ǜ@].s": [ + "type T6.S" + ], + "t6[qÞ滇śĬƬǿ鸀Ĕ耀]ʮ樇軄].t6[勝ǃ滪Ĉ³œ!閍0ʤ悕A1HMĴ!].t6[Ȩ\u003e5咓Yȧâ{Uu缜犌菑ȃ環?钗Ǜ@].t6[].s": [ + "type T6.S" + ] + }, + "*maps.T2": { + "mt1[,預].t2.mt1[şÅ9č傯ŤÑ珒¥ Ş!].t2.mt1[].t3": [ + "type T3" + ], + "mt1[,預].t2.mt1[şÅ9č傯ŤÑ珒¥ Ş!].t3": [ + "type T3" + ], + "mt1[,預].t2.mt1[şÅ9č傯ŤÑ珒¥ Ş!].t5[Fȓ¹謃售ʒȳ倲].t5": [ + "type T5.T5" + ], + "mt1[,預].t2.mt1[şÅ9č傯ŤÑ珒¥ Ş!].t5[烼kƶ坋w剶ŷƇÿ櫌].t5": [ + "type T5.T5" + ], + "mt1[,預].t2.mt1[şÅ9č傯ŤÑ珒¥ Ş!].t6[f].s": [ + "type T6.S" + ], + "mt1[,預].t2.mt1[şÅ9č傯ŤÑ珒¥ Ş!].t6[ĩ}賹8秈卅Uʓ].s": [ + "type T6.S" + ], + "mt1[,預].t2.mt1[侹rC綹l].t2.mt1[].t3": [ + "type T3" + ], + "mt1[,預].t2.mt1[侹rC綹l].t3": [ + "type T3" + ], + "mt1[,預].t2.mt1[侹rC綹l].t5[Eɧ].t5": [ + "type T5.T5" + ], + "mt1[,預].t2.mt1[侹rC綹l].t5[ʭ郲騦Ɉ 炗颺].t5": [ + "type T5.T5" + ], + "mt1[,預].t2.mt1[侹rC綹l].t6[Å圁椯Ĥɹ].s": [ + "type T6.S" + ], + "mt1[,預].t2.mt1[侹rC綹l].t6[莡OĈ½ǝ].s": [ + "type T6.S" + ], + "mt1[,預].t3": [ + "type T3" + ], + "mt1[,預].t3.t4.mt3[匛9唟摴aĹƵ龁Ű慤T.偱袗zŒâ]": [ + "type T3" + ], + "mt1[,預].t3.t4.mt3[苤犄4y±]": [ + "type T3" + ], + "mt1[,預].t5[³ƒIʛŰSƆ檓Ò]0OKs褴ɧ櫎6].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[³ƒIʛŰSƆ檓Ò]0OKs褴ɧ櫎6].t5[Vȯ蓏靍p工ɃɎĨ\u003c愒Īdz橩m峻ĉ椦蘁].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[³ƒIʛŰSƆ檓Ò]0OKs褴ɧ櫎6].t5[Vȯ蓏靍p工ɃɎĨ\u003c愒Īdz橩m峻ĉ椦蘁].t5[].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[³ƒIʛŰSƆ檓Ò]0OKs褴ɧ櫎6].t5[a悦ƪW悩].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[³ƒIʛŰSƆ檓Ò]0OKs褴ɧ櫎6].t5[a悦ƪW悩].t5[].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[ƚ浻WEȕCX鏫oȼ$鹰ŭU4fAČ].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[ƚ浻WEȕCX鏫oȼ$鹰ŭU4fAČ].t5[].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[ƚ浻WEȕCX鏫oȼ$鹰ŭU4fAČ].t5[].t5[].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[ƚ浻WEȕCX鏫oȼ$鹰ŭU4fAČ].t5[Ģ笼[Mu穆期ƫ\\^lɠǂƾɬ].t5": [ + "type T5.T5" + ], + "mt1[,預].t5[ƚ浻WEȕCX鏫oȼ$鹰ŭU4fAČ].t5[Ģ笼[Mu穆期ƫ\\^lɠǂƾɬ].t5[].t5": [ + "type T5.T5" + ], + "mt1[,預].t6[5誄X^疒zP椚].s": [ + "type T6.S" + ], + "mt1[,預].t6[5誄X^疒zP椚].t6[kȿM].s": [ + "type T6.S" + ], + "mt1[,預].t6[5誄X^疒zP椚].t6[kȿM].t6[].s": [ + "type T6.S" + ], + "mt1[,預].t6[5誄X^疒zP椚].t6[ʦȜ吶ɡ鱣ɥ].s": [ + "type T6.S" + ], + "mt1[,預].t6[5誄X^疒zP椚].t6[ʦȜ吶ɡ鱣ɥ].t6[].s": [ + "type T6.S" + ], + "mt1[,預].t6[嶆s\u003eɨ$ʮ秺].s": [ + "type T6.S" + ], + "mt1[,預].t6[嶆s\u003eɨ$ʮ秺].t6[å偔7鉰竡弓Ū駢].s": [ + "type T6.S" + ], + "mt1[,預].t6[嶆s\u003eɨ$ʮ秺].t6[å偔7鉰竡弓Ū駢].t6[].s": [ + "type T6.S" + ], + "mt1[,預].t6[嶆s\u003eɨ$ʮ秺].t6[詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].s": [ + "type T6.S" + ], + "mt1[,預].t6[嶆s\u003eɨ$ʮ秺].t6[詈aJ礵覞蔳磉!訫åqDZ駧ŋ`].t6[].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[F墄[C_睿ȉǫ蹟t´ûș$0đ\u003e].t2.mt1[].t3": [ + "type T3" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[F墄[C_睿ȉǫ蹟t´ûș$0đ\u003e].t3": [ + "type T3" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[F墄[C_睿ȉǫ蹟t´ûș$0đ\u003e].t5[].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[F墄[C_睿ȉǫ蹟t´ûș$0đ\u003e].t5[ɹȽ東潇澍ȣ1ȖɥmeCʊ].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[F墄[C_睿ȉǫ蹟t´ûș$0đ\u003e].t6[炄闌剾溏嶪滢w飃m=Sx蘳Gč].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[F墄[C_睿ȉǫ蹟t´ûș$0đ\u003e].t6[霳鸻ȉĔȹ$Ievɥʈ摨aƈŲɩ].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t2.mt1[].t3": [ + "type T3" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t3": [ + "type T3" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t5[ǪŰM旰綷罨袢捺悲ȅ].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t5[巆Åʛ,^籿].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t6[|2諱4~轱:Ž].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t2.mt1[鎑鎢屃裂ÀYǎ3].t6[濓\u003c鈡鶭ŁPyÌ祆tkƺƠ].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t3": [ + "type T3" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t3.t4.mt3[]": [ + "type T3" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t3.t4.mt3[h婉Ţ飦tD6]": [ + "type T3" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[k].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[k].t5[_榣ǝ祇Fæƭō跮ɽ].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[k].t5[_榣ǝ祇Fæƭō跮ɽ].t5[].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[k].t5[Ȃ遃ɸ].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[k].t5[Ȃ遃ɸ].t5[].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[煮FɀÄf渂硹譯匬襧*].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[煮FɀÄf渂硹譯匬襧*].t5[¤0ȻG炕炎鷖Ʊ腘愫躧ǏȬ¹ʍc].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[煮FɀÄf渂硹譯匬襧*].t5[¤0ȻG炕炎鷖Ʊ腘愫躧ǏȬ¹ʍc].t5[].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[煮FɀÄf渂硹譯匬襧*].t5[机Ȱ晸ʈ劉j蕓ư銩6ij憏歅%].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[煮FɀÄf渂硹譯匬襧*].t5[机Ȱ晸ʈ劉j蕓ư銩6ij憏歅%].t5[].t5": [ + "type T5.T5" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[ʩ@蚿Ī炕杭剛ň=Z?ǿ\u003cɀ].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[ʩ@蚿Ī炕杭剛ň=Z?ǿ\u003cɀ].t6[Ɂɯz剩Ʉö宰岮3瓯].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[ʩ@蚿Ī炕杭剛ň=Z?ǿ\u003cɀ].t6[Ɂɯz剩Ʉö宰岮3瓯].t6[].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[ʩ@蚿Ī炕杭剛ň=Z?ǿ\u003cɀ].t6[ʙ堠].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[ʩ@蚿Ī炕杭剛ň=Z?ǿ\u003cɀ].t6[ʙ堠].t6[].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[轊`JT瘮Q].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[轊`JT瘮Q].t6[Ȳ梚鿱o糛趸鸎|烈膞`蠯)Þ宅p葤Ø/].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[轊`JT瘮Q].t6[Ȳ梚鿱o糛趸鸎|烈膞`蠯)Þ宅p葤Ø/].t6[].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[轊`JT瘮Q].t6[ȿ].s": [ + "type T6.S" + ], + "mt1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t6[轊`JT瘮Q].t6[ȿ].t6[].s": [ + "type T6.S" + ] + }, + "*maps.T3": { + "": [ + "type T3" + ], + "t4.mt3[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ]": [ + "type T3" + ], + "t4.mt3[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t4.mt3[!Ǫ]": [ + "type T3" + ], + "t4.mt3[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t4.mt3[鎑鎢屃裂ÀYǎ3]": [ + "type T3" + ], + "t4.mt3[ɜH偩j0âȰ轷N巆Åʛ]": [ + "type T3" + ], + "t4.mt3[ɜH偩j0âȰ轷N巆Åʛ].t4.mt3[^籿Ź]": [ + "type T3" + ], + "t4.mt3[ɜH偩j0âȰ轷N巆Åʛ].t4.mt3[稈V]": [ + "type T3" + ] + }, + "*maps.T4": { + "mt3[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ]": [ + "type T3" + ], + "mt3[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t4.mt3[成oɜH偩j0âȰ轷N巆Åʛ,^籿]": [ + "type T3" + ], + "mt3[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t4.mt3[成oɜH偩j0âȰ轷N巆Åʛ,^籿].t4.mt3[]": [ + "type T3" + ], + "mt3[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t4.mt3[鎑鎢屃裂ÀYǎ3]": [ + "type T3" + ], + "mt3[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t4.mt3[鎑鎢屃裂ÀYǎ3].t4.mt3[]": [ + "type T3" + ], + "mt3[濓\u003c鈡鶭ŁPyÌ祆tkƺƠ]": [ + "type T3" + ], + "mt3[濓\u003c鈡鶭ŁPyÌ祆tkƺƠ].t4.mt3[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´]": [ + "type T3" + ], + "mt3[濓\u003c鈡鶭ŁPyÌ祆tkƺƠ].t4.mt3[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´].t4.mt3[]": [ + "type T3" + ], + "mt3[濓\u003c鈡鶭ŁPyÌ祆tkƺƠ].t4.mt3[ș$0đ\u003eƯ]ɹȽ東]": [ + "type T3" + ], + "mt3[濓\u003c鈡鶭ŁPyÌ祆tkƺƠ].t4.mt3[ș$0đ\u003eƯ]ɹȽ東].t4.mt3[]": [ + "type T3" + ] + }, + "*maps.T5": { + "t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´].t5[ș$0đ\u003eƯ]ɹȽ東].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´].t5[ș$0đ\u003eƯ]ɹȽ東].t5[].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´].t5[澍ȣ].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[v$蛷)讻IeYF墄[C_睿ȉǫ蹟t´].t5[澍ȣ].t5[].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[鎑鎢屃裂ÀYǎ3].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[鎑鎢屃裂ÀYǎ3].t5[成oɜH偩j0âȰ轷N巆Åʛ,^籿].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[鎑鎢屃裂ÀYǎ3].t5[成oɜH偩j0âȰ轷N巆Åʛ,^籿].t5[].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[鎑鎢屃裂ÀYǎ3].t5[濓\u003c鈡鶭ŁPyÌ祆tkƺƠ].t5": [ + "type T5.T5" + ], + "t5[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].t5[鎑鎢屃裂ÀYǎ3].t5[濓\u003c鈡鶭ŁPyÌ祆tkƺƠ].t5[].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[Sx蘳Gčü櫒}眙+].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[Sx蘳Gčü櫒}眙+].t5[LuVǭz瘯霡].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[Sx蘳Gčü櫒}眙+].t5[LuVǭz瘯霡].t5[].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[Sx蘳Gčü櫒}眙+].t5[].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[Sx蘳Gčü櫒}眙+].t5[].t5[].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[闌剾溏].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[闌剾溏].t5[m].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[闌剾溏].t5[m].t5[].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[闌剾溏].t5[滢w].t5": [ + "type T5.T5" + ], + "t5[ȖɥmeCʊ ].t5[闌剾溏].t5[滢w].t5[].t5": [ + "type T5.T5" + ] + }, + "*maps.T6": { + "s": [ + "type T6.S" + ], + "t6[h婉Ţ飦tD6].s": [ + "type T6.S" + ], + "t6[h婉Ţ飦tD6].t6[].s": [ + "type T6.S" + ], + "t6[h婉Ţ飦tD6].t6[].t6[*Z迖瓼轊`JT瘮Q8ý譎].s": [ + "type T6.S" + ], + "t6[h婉Ţ飦tD6].t6[].t6[*Z迖瓼轊`JT瘮Q8ý譎].t6[].s": [ + "type T6.S" + ], + "t6[h婉Ţ飦tD6].t6[].t6[\u003c巭蜏恝墜ǔ爡].s": [ + "type T6.S" + ], + "t6[h婉Ţ飦tD6].t6[].t6[\u003c巭蜏恝墜ǔ爡].t6[].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[Åʛ,^籿Ź濓].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[Åʛ,^籿Ź濓].t6[2諱4].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[Åʛ,^籿Ź濓].t6[2諱4].t6[].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[Åʛ,^籿Ź濓].t6[睿ȉǫ蹟t´ûș$0].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[Åʛ,^籿Ź濓].t6[睿ȉǫ蹟t´ûș$0].t6[].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[蒫÷K鬣壈gƢ板鋩伸~槱¡r].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[蒫÷K鬣壈gƢ板鋩伸~槱¡r].t6[ 炄闌剾溏嶪滢w].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[蒫÷K鬣壈gƢ板鋩伸~槱¡r].t6[ 炄闌剾溏嶪滢w].t6[].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[蒫÷K鬣壈gƢ板鋩伸~槱¡r].t6[].s": [ + "type T6.S" + ], + "t6[苬ĥəƣ[x飖Ǒp!ǪŰM旰綷罨袢].t6[蒫÷K鬣壈gƢ板鋩伸~槱¡r].t6[].t6[].s": [ + "type T6.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations.go new file mode 100644 index 0000000000..0c9ae1e747 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations.go @@ -0,0 +1,433 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maps + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T2 + scheme.AddValidationFunc( + (*T2)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T2( + ctx, op, nil, /* fldPath */ + obj.(*T2), + safe.Cast[*T2](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T3 + scheme.AddValidationFunc( + (*T3)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T3( + ctx, op, nil, /* fldPath */ + obj.(*T3), + safe.Cast[*T3](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T4 + scheme.AddValidationFunc( + (*T4)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T4( + ctx, op, nil, /* fldPath */ + obj.(*T4), + safe.Cast[*T4](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T5 + scheme.AddValidationFunc( + (*T5)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T5( + ctx, op, nil, /* fldPath */ + obj.(*T5), + safe.Cast[*T5](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T6 + scheme.AddValidationFunc( + (*T6)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T6( + ctx, op, nil, /* fldPath */ + obj.(*T6), + safe.Cast[*T6](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field T1.T3 + fn := func( + fldPath *field.Path, + obj, oldObj *T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T3(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T3 { + return &oldObj.T3 + }) + errs = append(errs, fn(fldPath.Child("t3"), &obj.T3, oldVal, oldObj != nil)...) + } + + { // field T1.T5 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]T5, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T5); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) map[string]T5 { + return oldObj.T5 + }) + errs = append(errs, fn(fldPath.Child("t5"), obj.T5, oldVal, oldObj != nil)...) + } + + { // field T1.T6 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]T6, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T6); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) map[string]T6 { + return oldObj.T6 + }) + errs = append(errs, fn(fldPath.Child("t6"), obj.T6, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.MT1 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) map[string]T1 { + return oldObj.MT1 + }) + errs = append(errs, fn(fldPath.Child("mt1"), obj.MT1, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T3 validates an instance of T3 according +// to declarative validation rules in the API schema. +func Validate_T3( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T3) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T3"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field T3.T4 + fn := func( + fldPath *field.Path, + obj, oldObj *T4, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T4(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T3) *T4 { + return &oldObj.T4 + }) + errs = append(errs, fn(fldPath.Child("t4"), &obj.T4, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T4 validates an instance of T4 according +// to declarative validation rules in the API schema. +func Validate_T4( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T4) (errs field.ErrorList) { + + { // field T4.MT3 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T3); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T4) map[string]T3 { + return oldObj.MT3 + }) + errs = append(errs, fn(fldPath.Child("mt3"), obj.MT3, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T5 validates an instance of T5 according +// to declarative validation rules in the API schema. +func Validate_T5( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T5) (errs field.ErrorList) { + + { // field T5.T5 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]T5, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T5.T5"); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T5); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T5) map[string]T5 { + return oldObj.T5 + }) + errs = append(errs, fn(fldPath.Child("t5"), obj.T5, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T6 validates an instance of T6 according +// to declarative validation rules in the API schema. +func Validate_T6( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T6) (errs field.ErrorList) { + + { // field T6.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T6.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T6) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T6.T6 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]T6, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, Validate_T6); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T6) map[string]T6 { + return oldObj.T6 + }) + errs = append(errs, fn(fldPath.Child("t6"), obj.T6, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations_test.go new file mode 100644 index 0000000000..12393a0ccd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/maps/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maps + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/doc.go new file mode 100644 index 0000000000..a05d714336 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/doc.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package pointers + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// This test case is carefully constructed to test recursion. We don't want to +// add more `validateFalse` tags because we want to test the recursion. +// +// Expectations: +// * We should emit validation for T1 because it uses T2 which uses T3, which has validation. +// * We should emit validation for T2 because it uses T3, which has validation. +// * We should emit validation for T3 because it has validation. +// * We should NOT emit validation for T4. +// * T1 should call optional(T1), T2 and optional(T2). +// * T2 should call optional(T1), optional(T2), and optional(T3). + +type T1 struct { + // +k8s:optional + PT1 *T1 `json:"pt1"` + + T2 T2 `json:"t2"` + + // +k8s:optional + PT2 *T2 `json:"pt2"` +} + +type T2 struct { + // +k8s:optional + PT1 *T1 `json:"pt1"` + + // +k8s:optional + PT2 *T2 `json:"pt2"` + + // +k8s:optional + PT3 *T3 `json:"pt3"` +} + +// +k8s:validateFalse="type T3" +type T3 struct { + I int `json:"i"` +} + +// NOTE: no validations. +type T4 struct { + // NOTE: no validations. + PT4 *T4 `json:"pt4"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/testdata/validate-false.json new file mode 100644 index 0000000000..5b94982940 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/testdata/validate-false.json @@ -0,0 +1,110 @@ +{ + "*pointers.T1": { + "pt1.pt1.pt2.pt3": [ + "type T3" + ], + "pt1.pt1.t2.pt3": [ + "type T3" + ], + "pt1.pt2.pt2.pt3": [ + "type T3" + ], + "pt1.pt2.pt3": [ + "type T3" + ], + "pt1.t2.pt1.t2.pt3": [ + "type T3" + ], + "pt1.t2.pt2.pt3": [ + "type T3" + ], + "pt1.t2.pt3": [ + "type T3" + ], + "pt2.pt1.pt2.pt3": [ + "type T3" + ], + "pt2.pt1.t2.pt3": [ + "type T3" + ], + "pt2.pt2.pt2.pt3": [ + "type T3" + ], + "pt2.pt2.pt3": [ + "type T3" + ], + "pt2.pt3": [ + "type T3" + ], + "t2.pt1.pt1.t2.pt3": [ + "type T3" + ], + "t2.pt1.pt2.pt3": [ + "type T3" + ], + "t2.pt1.t2.pt2.pt3": [ + "type T3" + ], + "t2.pt1.t2.pt3": [ + "type T3" + ], + "t2.pt2.pt1.t2.pt3": [ + "type T3" + ], + "t2.pt2.pt2.pt3": [ + "type T3" + ], + "t2.pt2.pt3": [ + "type T3" + ], + "t2.pt3": [ + "type T3" + ] + }, + "*pointers.T2": { + "pt1.pt1.pt2.pt3": [ + "type T3" + ], + "pt1.pt1.t2.pt3": [ + "type T3" + ], + "pt1.pt2.pt2.pt3": [ + "type T3" + ], + "pt1.pt2.pt3": [ + "type T3" + ], + "pt1.t2.pt1.t2.pt3": [ + "type T3" + ], + "pt1.t2.pt2.pt3": [ + "type T3" + ], + "pt1.t2.pt3": [ + "type T3" + ], + "pt2.pt1.pt2.pt3": [ + "type T3" + ], + "pt2.pt1.t2.pt3": [ + "type T3" + ], + "pt2.pt2.pt2.pt3": [ + "type T3" + ], + "pt2.pt2.pt3": [ + "type T3" + ], + "pt2.pt3": [ + "type T3" + ], + "pt3": [ + "type T3" + ] + }, + "*pointers.T3": { + "": [ + "type T3" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/zz_generated.validations.go new file mode 100644 index 0000000000..4bb115f2b6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/zz_generated.validations.go @@ -0,0 +1,291 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package pointers + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T2 + scheme.AddValidationFunc( + (*T2)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T2( + ctx, op, nil, /* fldPath */ + obj.(*T2), + safe.Cast[*T2](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T3 + scheme.AddValidationFunc( + (*T3)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T3( + ctx, op, nil, /* fldPath */ + obj.(*T3), + safe.Cast[*T3](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.PT1 + fn := func( + fldPath *field.Path, + obj, oldObj *T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_T1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T1 { + return oldObj.PT1 + }) + errs = append(errs, fn(fldPath.Child("pt1"), obj.PT1, oldVal, oldObj != nil)...) + } + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field T1.PT2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return oldObj.PT2 + }) + errs = append(errs, fn(fldPath.Child("pt2"), obj.PT2, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.PT1 + fn := func( + fldPath *field.Path, + obj, oldObj *T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_T1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *T1 { + return oldObj.PT1 + }) + errs = append(errs, fn(fldPath.Child("pt1"), obj.PT1, oldVal, oldObj != nil)...) + } + + { // field T2.PT2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *T2 { + return oldObj.PT2 + }) + errs = append(errs, fn(fldPath.Child("pt2"), obj.PT2, oldVal, oldObj != nil)...) + } + + { // field T2.PT3 + fn := func( + fldPath *field.Path, + obj, oldObj *T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_T3(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *T3 { + return oldObj.PT3 + }) + errs = append(errs, fn(fldPath.Child("pt3"), obj.PT3, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T3 validates an instance of T3 according +// to declarative validation rules in the API schema. +func Validate_T3( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T3) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T3"); len(e) != 0 { + errs = append(errs, e...) + } + + // field T3.I has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/zz_generated.validations_test.go new file mode 100644 index 0000000000..b1ab618180 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/pointers/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package pointers + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/doc.go new file mode 100644 index 0000000000..1059682c6e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/doc.go @@ -0,0 +1,74 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package slices + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// This test case is carefully constructed to test recursion. We don't want to +// add more `validateFalse` tags because the bug that motivated this test +// wasn't looking deep enough into the recursion tree. +// +// Expectations: +// * We should emit validation for T1 because T3 has validation. +// * We should emit validation for T2 because it uses T1, which has validation. +// * We should emit validation for T3 because it has validation. +// * We should emit validation for T4 because it uses T3, which has validation. +// * T1 should call T2 and T3. +// * T2 should call eachVal(T1). +// * T3 should call T4. +// * T4 should call eachVal(T3). +// * T5 and T6 hold a slice of themselves, reached via T1 so the slice type is +// still being discovered when its own field is processed. + +type T1 struct { + T2 T2 `json:"t2"` + T3 T3 `json:"t3"` + T5 []T5 `json:"t5"` + T6 []T6 `json:"t6"` +} + +type T2 struct { + ST1 []T1 `json:"st1"` +} + +// +k8s:validateFalse="type T3" +type T3 struct { + T4 T4 `json:"t4"` +} + +type T4 struct { + ST3 []T3 `json:"st3"` +} + +type T5 struct { + // +k8s:validateFalse="type T5.T5" + T5 []T5 `json:"t5"` +} + +type T6 struct { + // +k8s:validateFalse="type T6.S" + S string `json:"s"` + T6 []T6 `json:"t6"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/testdata/validate-false.json new file mode 100644 index 0000000000..8e3fe9e590 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/testdata/validate-false.json @@ -0,0 +1,884 @@ +{ + "*slices.T1": { + "t2.st1[0].t2.st1[0].t3": [ + "type T3" + ], + "t2.st1[0].t2.st1[0].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[0].t2.st1[0].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[0].t2.st1[0].t6[0].s": [ + "type T6.S" + ], + "t2.st1[0].t2.st1[0].t6[1].s": [ + "type T6.S" + ], + "t2.st1[0].t2.st1[1].t3": [ + "type T3" + ], + "t2.st1[0].t2.st1[1].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[0].t2.st1[1].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[0].t2.st1[1].t6[0].s": [ + "type T6.S" + ], + "t2.st1[0].t2.st1[1].t6[1].s": [ + "type T6.S" + ], + "t2.st1[0].t3": [ + "type T3" + ], + "t2.st1[0].t3.t4.st3[0]": [ + "type T3" + ], + "t2.st1[0].t3.t4.st3[1]": [ + "type T3" + ], + "t2.st1[0].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[0].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[0].t6[0].s": [ + "type T6.S" + ], + "t2.st1[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "t2.st1[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "t2.st1[0].t6[1].s": [ + "type T6.S" + ], + "t2.st1[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "t2.st1[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "t2.st1[1].t2.st1[0].t3": [ + "type T3" + ], + "t2.st1[1].t2.st1[0].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[1].t2.st1[0].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[1].t2.st1[0].t6[0].s": [ + "type T6.S" + ], + "t2.st1[1].t2.st1[0].t6[1].s": [ + "type T6.S" + ], + "t2.st1[1].t2.st1[1].t3": [ + "type T3" + ], + "t2.st1[1].t2.st1[1].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[1].t2.st1[1].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[1].t2.st1[1].t6[0].s": [ + "type T6.S" + ], + "t2.st1[1].t2.st1[1].t6[1].s": [ + "type T6.S" + ], + "t2.st1[1].t3": [ + "type T3" + ], + "t2.st1[1].t3.t4.st3[0]": [ + "type T3" + ], + "t2.st1[1].t3.t4.st3[1]": [ + "type T3" + ], + "t2.st1[1].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[1].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t2.st1[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t2.st1[1].t6[0].s": [ + "type T6.S" + ], + "t2.st1[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "t2.st1[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "t2.st1[1].t6[1].s": [ + "type T6.S" + ], + "t2.st1[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "t2.st1[1].t6[1].t6[1].s": [ + "type T6.S" + ], + "t3": [ + "type T3" + ], + "t3.t4.st3[0]": [ + "type T3" + ], + "t3.t4.st3[0].t4.st3[0]": [ + "type T3" + ], + "t3.t4.st3[0].t4.st3[1]": [ + "type T3" + ], + "t3.t4.st3[1]": [ + "type T3" + ], + "t3.t4.st3[1].t4.st3[0]": [ + "type T3" + ], + "t3.t4.st3[1].t4.st3[1]": [ + "type T3" + ], + "t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[1].t6[1].s": [ + "type T6.S" + ] + }, + "*slices.T2": { + "st1[0].t2.st1[0].t2.st1[0].t3": [ + "type T3" + ], + "st1[0].t2.st1[0].t2.st1[1].t3": [ + "type T3" + ], + "st1[0].t2.st1[0].t3": [ + "type T3" + ], + "st1[0].t2.st1[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t2.st1[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t2.st1[0].t6[0].s": [ + "type T6.S" + ], + "st1[0].t2.st1[0].t6[1].s": [ + "type T6.S" + ], + "st1[0].t2.st1[1].t2.st1[0].t3": [ + "type T3" + ], + "st1[0].t2.st1[1].t2.st1[1].t3": [ + "type T3" + ], + "st1[0].t2.st1[1].t3": [ + "type T3" + ], + "st1[0].t2.st1[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t2.st1[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t2.st1[1].t6[0].s": [ + "type T6.S" + ], + "st1[0].t2.st1[1].t6[1].s": [ + "type T6.S" + ], + "st1[0].t3": [ + "type T3" + ], + "st1[0].t3.t4.st3[0]": [ + "type T3" + ], + "st1[0].t3.t4.st3[1]": [ + "type T3" + ], + "st1[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t5[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t5[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t5[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t5[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t5[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t5[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t5[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[0].t5[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[0].t6[0].s": [ + "type T6.S" + ], + "st1[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "st1[0].t6[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "st1[0].t6[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "st1[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "st1[0].t6[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "st1[0].t6[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "st1[0].t6[1].s": [ + "type T6.S" + ], + "st1[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "st1[0].t6[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "st1[0].t6[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "st1[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "st1[0].t6[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "st1[0].t6[1].t6[1].t6[1].s": [ + "type T6.S" + ], + "st1[1].t2.st1[0].t2.st1[0].t3": [ + "type T3" + ], + "st1[1].t2.st1[0].t2.st1[1].t3": [ + "type T3" + ], + "st1[1].t2.st1[0].t3": [ + "type T3" + ], + "st1[1].t2.st1[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t2.st1[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t2.st1[0].t6[0].s": [ + "type T6.S" + ], + "st1[1].t2.st1[0].t6[1].s": [ + "type T6.S" + ], + "st1[1].t2.st1[1].t2.st1[0].t3": [ + "type T3" + ], + "st1[1].t2.st1[1].t2.st1[1].t3": [ + "type T3" + ], + "st1[1].t2.st1[1].t3": [ + "type T3" + ], + "st1[1].t2.st1[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t2.st1[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t2.st1[1].t6[0].s": [ + "type T6.S" + ], + "st1[1].t2.st1[1].t6[1].s": [ + "type T6.S" + ], + "st1[1].t3": [ + "type T3" + ], + "st1[1].t3.t4.st3[0]": [ + "type T3" + ], + "st1[1].t3.t4.st3[1]": [ + "type T3" + ], + "st1[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t5[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t5[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t5[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t5[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t5[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t5[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t5[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "st1[1].t5[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "st1[1].t6[0].s": [ + "type T6.S" + ], + "st1[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "st1[1].t6[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "st1[1].t6[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "st1[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "st1[1].t6[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "st1[1].t6[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "st1[1].t6[1].s": [ + "type T6.S" + ], + "st1[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "st1[1].t6[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "st1[1].t6[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "st1[1].t6[1].t6[1].s": [ + "type T6.S" + ], + "st1[1].t6[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "st1[1].t6[1].t6[1].t6[1].s": [ + "type T6.S" + ] + }, + "*slices.T3": { + "": [ + "type T3" + ], + "t4.st3[0]": [ + "type T3" + ], + "t4.st3[0].t4.st3[0]": [ + "type T3" + ], + "t4.st3[0].t4.st3[1]": [ + "type T3" + ], + "t4.st3[1]": [ + "type T3" + ], + "t4.st3[1].t4.st3[0]": [ + "type T3" + ], + "t4.st3[1].t4.st3[1]": [ + "type T3" + ] + }, + "*slices.T4": { + "st3[0]": [ + "type T3" + ], + "st3[0].t4.st3[0]": [ + "type T3" + ], + "st3[0].t4.st3[0].t4.st3[0]": [ + "type T3" + ], + "st3[0].t4.st3[0].t4.st3[1]": [ + "type T3" + ], + "st3[0].t4.st3[1]": [ + "type T3" + ], + "st3[0].t4.st3[1].t4.st3[0]": [ + "type T3" + ], + "st3[0].t4.st3[1].t4.st3[1]": [ + "type T3" + ], + "st3[1]": [ + "type T3" + ], + "st3[1].t4.st3[0]": [ + "type T3" + ], + "st3[1].t4.st3[0].t4.st3[0]": [ + "type T3" + ], + "st3[1].t4.st3[0].t4.st3[1]": [ + "type T3" + ], + "st3[1].t4.st3[1]": [ + "type T3" + ], + "st3[1].t4.st3[1].t4.st3[0]": [ + "type T3" + ], + "st3[1].t4.st3[1].t4.st3[1]": [ + "type T3" + ] + }, + "*slices.T5": { + "t5": [ + "type T5.T5" + ], + "t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[0].t5[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[0].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[0].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[0].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[1].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[1].t5[0].t5": [ + "type T5.T5" + ], + "t5[1].t5[1].t5[1].t5[1].t5": [ + "type T5.T5" + ] + }, + "*slices.T6": { + "s": [ + "type T6.S" + ], + "t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[0].t6[1].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[0].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[0].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[0].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[1].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[1].t6[0].s": [ + "type T6.S" + ], + "t6[1].t6[1].t6[1].t6[1].s": [ + "type T6.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/zz_generated.validations.go new file mode 100644 index 0000000000..955e4d94ae --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/zz_generated.validations.go @@ -0,0 +1,428 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package slices + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T2 + scheme.AddValidationFunc( + (*T2)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T2( + ctx, op, nil, /* fldPath */ + obj.(*T2), + safe.Cast[*T2](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T3 + scheme.AddValidationFunc( + (*T3)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T3( + ctx, op, nil, /* fldPath */ + obj.(*T3), + safe.Cast[*T3](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T4 + scheme.AddValidationFunc( + (*T4)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T4( + ctx, op, nil, /* fldPath */ + obj.(*T4), + safe.Cast[*T4](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T5 + scheme.AddValidationFunc( + (*T5)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T5( + ctx, op, nil, /* fldPath */ + obj.(*T5), + safe.Cast[*T5](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T6 + scheme.AddValidationFunc( + (*T6)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T6( + ctx, op, nil, /* fldPath */ + obj.(*T6), + safe.Cast[*T6](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field T1.T3 + fn := func( + fldPath *field.Path, + obj, oldObj *T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T3(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T3 { + return &oldObj.T3 + }) + errs = append(errs, fn(fldPath.Child("t3"), &obj.T3, oldVal, oldObj != nil)...) + } + + { // field T1.T5 + fn := func( + fldPath *field.Path, + obj, oldObj []T5, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_T5); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) []T5 { + return oldObj.T5 + }) + errs = append(errs, fn(fldPath.Child("t5"), obj.T5, oldVal, oldObj != nil)...) + } + + { // field T1.T6 + fn := func( + fldPath *field.Path, + obj, oldObj []T6, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_T6); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) []T6 { + return oldObj.T6 + }) + errs = append(errs, fn(fldPath.Child("t6"), obj.T6, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.ST1 + fn := func( + fldPath *field.Path, + obj, oldObj []T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_T1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) []T1 { + return oldObj.ST1 + }) + errs = append(errs, fn(fldPath.Child("st1"), obj.ST1, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T3 validates an instance of T3 according +// to declarative validation rules in the API schema. +func Validate_T3( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T3) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T3"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field T3.T4 + fn := func( + fldPath *field.Path, + obj, oldObj *T4, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_T4(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T3) *T4 { + return &oldObj.T4 + }) + errs = append(errs, fn(fldPath.Child("t4"), &obj.T4, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T4 validates an instance of T4 according +// to declarative validation rules in the API schema. +func Validate_T4( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T4) (errs field.ErrorList) { + + { // field T4.ST3 + fn := func( + fldPath *field.Path, + obj, oldObj []T3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_T3); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T4) []T3 { + return oldObj.ST3 + }) + errs = append(errs, fn(fldPath.Child("st3"), obj.ST3, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T5 validates an instance of T5 according +// to declarative validation rules in the API schema. +func Validate_T5( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T5) (errs field.ErrorList) { + + { // field T5.T5 + fn := func( + fldPath *field.Path, + obj, oldObj []T5, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T5.T5"); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_T5); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T5) []T5 { + return oldObj.T5 + }) + errs = append(errs, fn(fldPath.Child("t5"), obj.T5, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T6 validates an instance of T6 according +// to declarative validation rules in the API schema. +func Validate_T6( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T6) (errs field.ErrorList) { + + { // field T6.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T6.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T6) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field T6.T6 + fn := func( + fldPath *field.Path, + obj, oldObj []T6, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_T6); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T6) []T6 { + return oldObj.T6 + }) + errs = append(errs, fn(fldPath.Child("t6"), obj.T6, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/zz_generated.validations_test.go new file mode 100644 index 0000000000..231d9cd808 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/recursive/slices/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package slices + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/doc.go new file mode 100644 index 0000000000..89dac1835e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/doc.go @@ -0,0 +1,41 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package multiplevalidations + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type Struct #1" +// +k8s:validateFalse="type Struct #2" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.ListField #1" + // +k8s:validateFalse="field Struct.ListField #2" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*] #1" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*] #2" + ListField []string `json:"listField"` + + UnvalidatedListField []string `json:"UnvalidatedListField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/testdata/validate-false.json new file mode 100644 index 0000000000..4748ab66de --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/testdata/validate-false.json @@ -0,0 +1,20 @@ +{ + "*multiplevalidations.Struct": { + "": [ + "type Struct #1", + "type Struct #2" + ], + "listField": [ + "field Struct.ListField #1", + "field Struct.ListField #2" + ], + "listField[0]": [ + "field Struct.ListField[*] #1", + "field Struct.ListField[*] #2" + ], + "listField[1]": [ + "field Struct.ListField[*] #1", + "field Struct.ListField[*] #2" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/zz_generated.validations.go new file mode 100644 index 0000000000..f11b354e2b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/zz_generated.validations.go @@ -0,0 +1,115 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiplevalidations + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct #1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct #2"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField #1"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField #2"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*] #1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*] #2") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedListField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/zz_generated.validations_test.go new file mode 100644 index 0000000000..d30e8f8db8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/multiple_validations/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiplevalidations + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/doc.go new file mode 100644 index 0000000000..a9326f0939 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/doc.go @@ -0,0 +1,55 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofprimitive + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.ListField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*]" + ListField []string `json:"listField"` + + // +k8s:validateFalse="field Struct.ListTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListTypedefField[*]" + ListTypedefField []StringType `json:"listTypedefField"` + + UnvalidatedListField []string `json:"UnvalidatedListField"` + + // +k8s:validateFalse="field Struct.ListPtrField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListPtrField[*]" + ListPtrField []*string `json:"listPtrField"` + + // +k8s:validateFalse="field Struct.ListPtrTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListPtrTypedefField[*]" + ListPtrTypedefField []*StringType `json:"listPtrTypedefField"` + + UnvalidatedListPtrField []*string `json:"UnvalidatedListPtrField"` +} + +// +k8s:validateFalse="type StringType" +type StringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/testdata/validate-false.json new file mode 100644 index 0000000000..87074928f3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/testdata/validate-false.json @@ -0,0 +1,47 @@ +{ + "*sliceofprimitive.Struct": { + "": [ + "type Struct" + ], + "listField": [ + "field Struct.ListField" + ], + "listField[0]": [ + "field Struct.ListField[*]" + ], + "listField[1]": [ + "field Struct.ListField[*]" + ], + "listPtrField": [ + "field Struct.ListPtrField" + ], + "listPtrField[0]": [ + "field Struct.ListPtrField[*]" + ], + "listPtrField[1]": [ + "field Struct.ListPtrField[*]" + ], + "listPtrTypedefField": [ + "field Struct.ListPtrTypedefField" + ], + "listPtrTypedefField[0]": [ + "field Struct.ListPtrTypedefField[*]", + "type StringType" + ], + "listPtrTypedefField[1]": [ + "field Struct.ListPtrTypedefField[*]", + "type StringType" + ], + "listTypedefField": [ + "field Struct.ListTypedefField" + ], + "listTypedefField[0]": [ + "field Struct.ListTypedefField[*]", + "type StringType" + ], + "listTypedefField[1]": [ + "field Struct.ListTypedefField[*]", + "type StringType" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/zz_generated.validations.go new file mode 100644 index 0000000000..c7493222be --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/zz_generated.validations.go @@ -0,0 +1,232 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofprimitive + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type StringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []StringType { + return oldObj.ListTypedefField + }) + errs = append(errs, fn(fldPath.Child("listTypedefField"), obj.ListTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedListField has no validation + + { // field Struct.ListPtrField + fn := func( + fldPath *field.Path, + obj, oldObj []*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*string { + return oldObj.ListPtrField + }) + errs = append(errs, fn(fldPath.Child("listPtrField"), obj.ListPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListPtrTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []*StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[StringType](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*StringType { + return oldObj.ListPtrTypedefField + }) + errs = append(errs, fn(fldPath.Child("listPtrTypedefField"), obj.ListPtrTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedListPtrField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/zz_generated.validations_test.go new file mode 100644 index 0000000000..71ff7b5fb6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_primitive/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofprimitive + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/doc.go new file mode 100644 index 0000000000..553928bb6e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/doc.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofstruct + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.ListField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*]" + ListField []OtherStruct `json:"listField"` + + // +k8s:validateFalse="field Struct.ListTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListTypedefField[*]" + ListTypedefField []OtherTypedefStruct `json:"listTypedefField"` + + UnvalidatedListField []UnvalidatedStruct `json:"UnvalidatedListField"` + + // +k8s:validateFalse="field Struct.ListPtrField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListPtrField[*]" + ListPtrField []*OtherStruct `json:"listPtrField"` + + // +k8s:validateFalse="field Struct.ListPtrTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListPtrTypedefField[*]" + ListPtrTypedefField []*OtherTypedefStruct `json:"listPtrTypedefField"` + + UnvalidatedListPtrField []*UnvalidatedStruct `json:"UnvalidatedListPtrField"` +} + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct{} + +// +k8s:validateFalse="type OtherTypedefStruct" +type OtherTypedefStruct OtherStruct + +type UnvalidatedStruct struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/testdata/validate-false.json new file mode 100644 index 0000000000..40abecfbd0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/testdata/validate-false.json @@ -0,0 +1,51 @@ +{ + "*sliceofstruct.Struct": { + "": [ + "type Struct" + ], + "listField": [ + "field Struct.ListField" + ], + "listField[0]": [ + "field Struct.ListField[*]", + "type OtherStruct" + ], + "listField[1]": [ + "field Struct.ListField[*]", + "type OtherStruct" + ], + "listPtrField": [ + "field Struct.ListPtrField" + ], + "listPtrField[0]": [ + "field Struct.ListPtrField[*]", + "type OtherStruct" + ], + "listPtrField[1]": [ + "field Struct.ListPtrField[*]", + "type OtherStruct" + ], + "listPtrTypedefField": [ + "field Struct.ListPtrTypedefField" + ], + "listPtrTypedefField[0]": [ + "field Struct.ListPtrTypedefField[*]", + "type OtherTypedefStruct" + ], + "listPtrTypedefField[1]": [ + "field Struct.ListPtrTypedefField[*]", + "type OtherTypedefStruct" + ], + "listTypedefField": [ + "field Struct.ListTypedefField" + ], + "listTypedefField[0]": [ + "field Struct.ListTypedefField[*]", + "type OtherTypedefStruct" + ], + "listTypedefField[1]": [ + "field Struct.ListTypedefField[*]", + "type OtherTypedefStruct" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/zz_generated.validations.go new file mode 100644 index 0000000000..165b63c999 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/zz_generated.validations.go @@ -0,0 +1,253 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofstruct + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_OtherTypedefStruct validates an instance of OtherTypedefStruct according +// to declarative validation rules in the API schema. +func Validate_OtherTypedefStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherTypedefStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherTypedefStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherTypedefStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherTypedefStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherTypedefStruct { + return oldObj.ListTypedefField + }) + errs = append(errs, fn(fldPath.Child("listTypedefField"), obj.ListTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedListField has no validation + + { // field Struct.ListPtrField + fn := func( + fldPath *field.Path, + obj, oldObj []*OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[OtherStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*OtherStruct { + return oldObj.ListPtrField + }) + errs = append(errs, fn(fldPath.Child("listPtrField"), obj.ListPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListPtrTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []*OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[OtherTypedefStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherTypedefStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherTypedefStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*OtherTypedefStruct { + return oldObj.ListPtrTypedefField + }) + errs = append(errs, fn(fldPath.Child("listPtrTypedefField"), obj.ListPtrTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedListPtrField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/zz_generated.validations_test.go new file mode 100644 index 0000000000..afb01f7e9d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/slice_of_struct/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofstruct + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/doc.go new file mode 100644 index 0000000000..180fac50d1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/doc.go @@ -0,0 +1,77 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package typedeftoslice + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: no validation here +type UnvalidatedType []string + +// Note: no validation here +type UnvalidatedPtrType []*string + +// +k8s:validateFalse="type ListType" +// +k8s:eachVal=+k8s:validateFalse="type ListType[*]" +type ListType []string + +// +k8s:validateFalse="type ListPtrType" +// +k8s:eachVal=+k8s:validateFalse="type ListPtrType[*]" +type ListPtrType []*string + +// +k8s:validateFalse="type ListTypedefType" +// +k8s:eachVal=+k8s:validateFalse="type ListTypedefType[*]" +type ListTypedefType []StringType + +// +k8s:validateFalse="type ListPtrTypedefType" +// +k8s:eachVal=+k8s:validateFalse="type ListPtrTypedefType[*]" +type ListPtrTypedefType []*StringType + +// +k8s:validateFalse="type StringType" +type StringType string + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.ListField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*]" + ListField ListType `json:"listField"` + + // +k8s:validateFalse="field Struct.ListTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListTypedefField[*]" + ListTypedefField ListTypedefType `json:"listTypedefField"` + + UnvalidatedListField UnvalidatedType `json:"UnvalidatedListField"` + + // +k8s:validateFalse="field Struct.ListPtrField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListPtrField[*]" + ListPtrField ListPtrType `json:"listPtrField"` + + // +k8s:validateFalse="field Struct.ListPtrTypedefField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListPtrTypedefField[*]" + ListPtrTypedefField ListPtrTypedefType `json:"listPtrTypedefField"` + + UnvalidatedListPtrField UnvalidatedPtrType `json:"UnvalidatedListPtrField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/testdata/validate-false.json new file mode 100644 index 0000000000..4900cb15e7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/testdata/validate-false.json @@ -0,0 +1,59 @@ +{ + "*typedeftoslice.Struct": { + "": [ + "type Struct" + ], + "listField": [ + "field Struct.ListField", + "type ListType" + ], + "listField[0]": [ + "field Struct.ListField[*]", + "type ListType[*]" + ], + "listField[1]": [ + "field Struct.ListField[*]", + "type ListType[*]" + ], + "listPtrField": [ + "field Struct.ListPtrField", + "type ListPtrType" + ], + "listPtrField[0]": [ + "field Struct.ListPtrField[*]", + "type ListPtrType[*]" + ], + "listPtrField[1]": [ + "field Struct.ListPtrField[*]", + "type ListPtrType[*]" + ], + "listPtrTypedefField": [ + "field Struct.ListPtrTypedefField", + "type ListPtrTypedefType" + ], + "listPtrTypedefField[0]": [ + "field Struct.ListPtrTypedefField[*]", + "type ListPtrTypedefType[*]", + "type StringType" + ], + "listPtrTypedefField[1]": [ + "field Struct.ListPtrTypedefField[*]", + "type ListPtrTypedefType[*]", + "type StringType" + ], + "listTypedefField": [ + "field Struct.ListTypedefField", + "type ListTypedefType" + ], + "listTypedefField[0]": [ + "field Struct.ListTypedefField[*]", + "type ListTypedefType[*]", + "type StringType" + ], + "listTypedefField[1]": [ + "field Struct.ListTypedefField[*]", + "type ListTypedefType[*]", + "type StringType" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/zz_generated.validations.go new file mode 100644 index 0000000000..55243d6c16 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/zz_generated.validations.go @@ -0,0 +1,318 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftoslice + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ListPtrType validates an instance of ListPtrType according +// to declarative validation rules in the API schema. +func Validate_ListPtrType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ListPtrType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListPtrType"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListPtrType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ListPtrTypedefType validates an instance of ListPtrTypedefType according +// to declarative validation rules in the API schema. +func Validate_ListPtrTypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ListPtrTypedefType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListPtrTypedefType"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListPtrTypedefType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + // iterate the list and call the type's validation function + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ListType validates an instance of ListType according +// to declarative validation rules in the API schema. +func Validate_ListType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ListType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListType"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ListTypedefType validates an instance of ListTypedefType according +// to declarative validation rules in the API schema. +func Validate_ListTypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ListTypedefType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListTypedefType"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListTypedefType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type StringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj ListType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ListType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ListType { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj ListTypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ListTypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ListTypedefType { + return oldObj.ListTypedefField + }) + errs = append(errs, fn(fldPath.Child("listTypedefField"), obj.ListTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedListField has no validation + + { // field Struct.ListPtrField + fn := func( + fldPath *field.Path, + obj, oldObj ListPtrType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ListPtrType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ListPtrType { + return oldObj.ListPtrField + }) + errs = append(errs, fn(fldPath.Child("listPtrField"), obj.ListPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListPtrTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj ListPtrTypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[StringType](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPtrTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ListPtrTypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ListPtrTypedefType { + return oldObj.ListPtrTypedefField + }) + errs = append(errs, fn(fldPath.Child("listPtrTypedefField"), obj.ListPtrTypedefField, oldVal, oldObj != nil)...) + } + + // field Struct.UnvalidatedListPtrField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/zz_generated.validations_test.go new file mode 100644 index 0000000000..4ae2887d51 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/slices/typedef_to_slice/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftoslice + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/README.md b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/README.md new file mode 100644 index 0000000000..d1b6b0a309 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/README.md @@ -0,0 +1,7 @@ +# Tag tests + +Tests in this directory are intended to validate specific tags, rather than +general behavior of the code generator. Some tags are deeply integrated into +the code-generation and will end up with similar tests elsewhere. + +These test cases should be as focused as possible. diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/doc.go new file mode 100644 index 0000000000..9e1b73af03 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/doc.go @@ -0,0 +1,72 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package customvalidation + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:customValidation +type Struct struct { + TypeMeta int + + // +k8s:customValidation + StringField string `json:"stringField"` + + // Combined with a declarative tag: both validations run on this field. + // +k8s:maxLength=3 + // +k8s:customValidation + MaxLengthField string `json:"maxLengthField"` + + // StringType is custom-validated wherever it appears via traversal. + TypedefField StringType `json:"typedefField"` + TypedefPtrField *StringType `json:"typedefPtrField,omitempty"` + TypedefSliceField []StringType `json:"typedefSliceField,omitempty"` + TypedefMapField map[string]StringType `json:"typedefMapField,omitempty"` + + StructField OtherStruct `json:"structField"` +} + +// +k8s:customValidation +type StringType string + +type OtherStruct struct { + // +k8s:customValidation + StringField string `json:"stringField"` +} + +// OptionStruct demonstrates custom validation gated behind a feature option. +type OptionStruct struct { + TypeMeta int + + // +k8s:ifEnabled(FeatureX)=+k8s:customValidation + StringField string `json:"stringField"` +} + +// EachStruct demonstrates custom validation (via the StringType element type) +// coexisting with a declarative per-element check applied by eachVal. +type EachStruct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:maxLength=3 + SliceField []StringType `json:"sliceField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/doc_test.go new file mode 100644 index 0000000000..7b8c9b650a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/doc_test.go @@ -0,0 +1,87 @@ +/* +Copyright 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 customvalidation + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + matcher := field.ErrorMatcher{}.ByType().ByField() + + mk := func() *Struct { + return &Struct{ + StringField: "s", + MaxLengthField: "toolong", // longer than maxLength=3 + TypedefField: "t", + TypedefPtrField: new(StringType("p")), + TypedefSliceField: []StringType{"e"}, + TypedefMapField: map[string]StringType{"k": "m"}, + StructField: OtherStruct{StringField: "n"}, + } + } + + // On create, custom validation runs at every scope/shape: root type, a field, + // a field combined with maxLength, the reusable type (field/pointer/list + // element), and a nested-struct field. + st.Value(mk()).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(nil, nil, ""), + field.Invalid(field.NewPath("stringField"), nil, ""), + field.Invalid(field.NewPath("maxLengthField"), nil, ""), + field.TooLongCharacters(field.NewPath("maxLengthField"), "", 3), + field.Invalid(field.NewPath("typedefField"), nil, ""), + field.Invalid(field.NewPath("typedefPtrField"), nil, ""), + field.Invalid(field.NewPath("typedefSliceField").Index(0), nil, ""), + field.Invalid(field.NewPath("typedefMapField").Key("k"), nil, ""), + field.Invalid(field.NewPath("structField", "stringField"), nil, ""), + }) + + // On a no-op update, field and embedded calls are skipped (value unchanged); + // the root type-scoped call still runs (the framework does not skip it). + st.Value(mk()).OldValue(mk()).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(nil, nil, ""), + }) +} + +// TestIfEnabled covers custom validation gated by a feature option via ifEnabled. +func TestIfEnabled(t *testing.T) { + st := localSchemeBuilder.Test(t) + matcher := field.ErrorMatcher{}.ByType().ByField() + + // Option disabled: the gated custom validation does not run. + st.Value(&OptionStruct{}).Opts(map[string]bool{"FeatureX": false}).ExpectValid() + + // Option enabled: the custom validation runs. + st.Value(&OptionStruct{}).Opts(map[string]bool{"FeatureX": true}).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("stringField"), nil, ""), + }) +} + +// TestEachVal covers custom validation (via the element type) coexisting with a +// declarative per-element check applied by eachVal. +func TestEachVal(t *testing.T) { + st := localSchemeBuilder.Test(t) + matcher := field.ErrorMatcher{}.ByType().ByField() + + st.Value(&EachStruct{SliceField: []StringType{"toolong"}}).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("sliceField").Index(0), nil, ""), // custom, via StringType + field.TooLongCharacters(field.NewPath("sliceField").Index(0), "", 3), // eachVal maxLength + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/validations.go new file mode 100644 index 0000000000..a6cebee217 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/validations.go @@ -0,0 +1,52 @@ +/* +Copyright 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 customvalidation + +import ( + "context" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// These functions are structural markers, not real rules: each emits one error +// at the path it is called with, so tests can assert where the generated +// traversal invokes custom validation. + +func ValidateCustom_Struct(_ context.Context, _ operation.Operation, fldPath *field.Path, _, _ *Struct) field.ErrorList { + return field.ErrorList{field.Invalid(fldPath, nil, "ValidateCustom_Struct")} +} + +func ValidateCustom_Struct_StringField(_ context.Context, _ operation.Operation, fldPath *field.Path, _, _ *string) field.ErrorList { + return field.ErrorList{field.Invalid(fldPath, nil, "ValidateCustom_Struct_StringField")} +} + +func ValidateCustom_Struct_MaxLengthField(_ context.Context, _ operation.Operation, fldPath *field.Path, _, _ *string) field.ErrorList { + return field.ErrorList{field.Invalid(fldPath, nil, "ValidateCustom_Struct_MaxLengthField")} +} + +func ValidateCustom_StringType(_ context.Context, _ operation.Operation, fldPath *field.Path, _, _ *StringType) field.ErrorList { + return field.ErrorList{field.Invalid(fldPath, nil, "ValidateCustom_StringType")} +} + +func ValidateCustom_OtherStruct_StringField(_ context.Context, _ operation.Operation, fldPath *field.Path, _, _ *string) field.ErrorList { + return field.ErrorList{field.Invalid(fldPath, nil, "ValidateCustom_OtherStruct_StringField")} +} + +func ValidateCustom_OptionStruct_StringField(_ context.Context, _ operation.Operation, fldPath *field.Path, _, _ *string) field.ErrorList { + return field.ErrorList{field.Invalid(fldPath, nil, "ValidateCustom_OptionStruct_StringField")} +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/zz_generated.validations.go new file mode 100644 index 0000000000..028fb87da0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/customvalidation/zz_generated.validations.go @@ -0,0 +1,396 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package customvalidation + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type EachStruct + scheme.AddValidationFunc( + (*EachStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_EachStruct( + ctx, op, nil, /* fldPath */ + obj.(*EachStruct), + safe.Cast[*EachStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type OptionStruct + scheme.AddValidationFunc( + (*OptionStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_OptionStruct( + ctx, op, nil, /* fldPath */ + obj.(*OptionStruct), + safe.Cast[*OptionStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_EachStruct validates an instance of EachStruct according +// to declarative validation rules in the API schema. +func Validate_EachStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *EachStruct) (errs field.ErrorList) { + + // field EachStruct.TypeMeta has no validation + + { // field EachStruct.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 3) + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *EachStruct) []StringType { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_OptionStruct validates an instance of OptionStruct according +// to declarative validation rules in the API schema. +func Validate_OptionStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OptionStruct) (errs field.ErrorList) { + + // field OptionStruct.TypeMeta has no validation + + { // field OptionStruct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, // custom validation + ValidateCustom_OptionStruct_StringField); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *OptionStruct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + { // field OtherStruct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + // custom validation + if e := ValidateCustom_OtherStruct_StringField(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *OtherStruct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StringType) (errs field.ErrorList) { + + // custom validation + if e := ValidateCustom_StringType(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // custom validation + if e := ValidateCustom_Struct(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + // custom validation + if e := ValidateCustom_Struct_StringField(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.MaxLengthField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + // custom validation + if e := ValidateCustom_Struct_MaxLengthField(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 3); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.MaxLengthField + }) + errs = append(errs, fn(fldPath.Child("maxLengthField"), &obj.MaxLengthField, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_StringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return &oldObj.TypedefField + }) + errs = append(errs, fn(fldPath.Child("typedefField"), &obj.TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_StringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return oldObj.TypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("typedefPtrField"), obj.TypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefSliceField + fn := func( + fldPath *field.Path, + obj, oldObj []StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []StringType { + return oldObj.TypedefSliceField + }) + errs = append(errs, fn(fldPath.Child("typedefSliceField"), obj.TypedefSliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefMapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]StringType { + return oldObj.TypedefMapField + }) + errs = append(errs, fn(fldPath.Child("typedefMapField"), obj.TypedefMapField, oldVal, oldObj != nil)...) + } + + { // field Struct.StructField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_OtherStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return &oldObj.StructField + }) + errs = append(errs, fn(fldPath.Child("structField"), &obj.StructField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/doc.go new file mode 100644 index 0000000000..3cdbc060f9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/doc.go @@ -0,0 +1,106 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package dependentforbidden + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Basic one-to-one dependency. +type Struct struct { + TypeMeta int + + // +k8s:optional + // +k8s:dependentForbidden("dependent") + Trigger *string `json:"trigger"` + + // +k8s:optional + Dependent *string `json:"dependent"` + + // +k8s:optional + OtherField *string `json:"otherField"` +} + +// One trigger, many dependents (repeated tags). +type MultiDependent struct { + TypeMeta int + + // +k8s:optional + // +k8s:dependentForbidden("dependentA") + // +k8s:dependentForbidden("dependentB") + Trigger *string `json:"trigger"` + + // +k8s:optional + DependentA *string `json:"dependentA"` + + // +k8s:optional + DependentB *string `json:"dependentB"` +} + +// Many triggers, one dependent. +type MultiTrigger struct { + TypeMeta int + + // +k8s:optional + // +k8s:dependentForbidden("dependent") + TriggerA *string `json:"triggerA"` + + // +k8s:optional + // +k8s:dependentForbidden("dependent") + TriggerB *string `json:"triggerB"` + + // +k8s:optional + Dependent *string `json:"dependent"` +} + +// All four "is set" extractor kinds. +type AllKinds struct { + TypeMeta int + + // +k8s:optional + // +k8s:dependentForbidden("ptrDep") + PtrTrigger *string `json:"ptrTrigger"` + + // +k8s:optional + PtrDep *string `json:"ptrDep"` + + // +k8s:optional + // +k8s:dependentForbidden("sliceDep") + SliceTrigger []string `json:"sliceTrigger"` + + // +k8s:optional + SliceDep []string `json:"sliceDep"` + + // +k8s:optional + // +k8s:dependentForbidden("mapDep") + MapTrigger map[string]string `json:"mapTrigger"` + + // +k8s:optional + MapDep map[string]string `json:"mapDep"` + + // +k8s:optional + // +k8s:dependentForbidden("intDep") + IntTrigger int `json:"intTrigger"` + + // +k8s:optional + IntDep int `json:"intDep"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/doc_test.go new file mode 100644 index 0000000000..97ed55641b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/doc_test.go @@ -0,0 +1,122 @@ +/* +Copyright 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 dependentforbidden + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // One-directional: dependent alone is fine. + st.Value(&Struct{Dependent: new("d")}).ExpectValid() + + // Trigger alone is fine. + st.Value(&Struct{Trigger: new("t")}).ExpectValid() + + // Both set → forbidden. + st.Value(&Struct{Trigger: new("t"), Dependent: new("d")}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Forbidden(field.NewPath("dependent"), "").WithOrigin("dependentForbidden"), + }, + ) + + // Ratchet: unrelated field changed, trigger and dependent set-ness unchanged → skip. + st.Value(&Struct{Trigger: new("t"), Dependent: new("d"), OtherField: new("new")}). + OldValue(&Struct{Trigger: new("t"), Dependent: new("d"), OtherField: new("old")}). + ExpectValid() + + // Ratchet: trigger value changed but set-ness unchanged → skip. + st.Value(&Struct{Trigger: new("t"), Dependent: new("d")}). + OldValue(&Struct{Trigger: new("old"), Dependent: new("d")}). + ExpectValid() + + // Newly set dependent → fire. + st.Value(&Struct{Trigger: new("t"), Dependent: new("d")}). + OldValue(&Struct{Trigger: new("t")}). + ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Forbidden(field.NewPath("dependent"), "").WithOrigin("dependentForbidden"), + }, + ) +} + +func TestMultiDependent(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Repeated tags → independent implications, each at its own dependent path. + st.Value(&MultiDependent{Trigger: new("t"), DependentA: new("a"), DependentB: new("b")}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Forbidden(field.NewPath("dependentA"), "").WithOrigin("dependentForbidden"), + field.Forbidden(field.NewPath("dependentB"), "").WithOrigin("dependentForbidden"), + }, + ) +} + +func TestMultiTrigger(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Distinct triggers → independent implications at the same path. + // ByOrigin absorbs both actuals. + st.Value(&MultiTrigger{TriggerA: new("a"), TriggerB: new("b"), Dependent: new("d")}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Forbidden(field.NewPath("dependent"), "").WithOrigin("dependentForbidden"), + }, + ) +} + +func TestAllKinds(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Each kind's extractor fires when both trigger and dependent are set. + st.Value(&AllKinds{ + PtrTrigger: new("t"), + PtrDep: new("d"), + SliceTrigger: []string{"x"}, + SliceDep: []string{"y"}, + MapTrigger: map[string]string{"k": "v"}, + MapDep: map[string]string{"k": "v"}, + IntTrigger: 1, + IntDep: 1, + }).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Forbidden(field.NewPath("ptrDep"), "").WithOrigin("dependentForbidden"), + field.Forbidden(field.NewPath("sliceDep"), "").WithOrigin("dependentForbidden"), + field.Forbidden(field.NewPath("mapDep"), "").WithOrigin("dependentForbidden"), + field.Forbidden(field.NewPath("intDep"), "").WithOrigin("dependentForbidden"), + }, + ) + + // Triggers set but dependents "not set" (empty slice/map, zero int, nil ptr) → valid. + st.Value(&AllKinds{ + PtrTrigger: new("t"), + SliceTrigger: []string{"x"}, + MapTrigger: map[string]string{"k": "v"}, + IntTrigger: 1, + SliceDep: []string{}, + MapDep: map[string]string{}, + IntDep: 0, + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/zz_generated.validations.go new file mode 100644 index 0000000000..c241db1947 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentforbidden/zz_generated.validations.go @@ -0,0 +1,763 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package dependentforbidden + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type AllKinds + scheme.AddValidationFunc( + (*AllKinds)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_AllKinds( + ctx, op, nil, /* fldPath */ + obj.(*AllKinds), + safe.Cast[*AllKinds](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MultiDependent + scheme.AddValidationFunc( + (*MultiDependent)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MultiDependent( + ctx, op, nil, /* fldPath */ + obj.(*MultiDependent), + safe.Cast[*MultiDependent](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MultiTrigger + scheme.AddValidationFunc( + (*MultiTrigger)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MultiTrigger( + ctx, op, nil, /* fldPath */ + obj.(*MultiTrigger), + safe.Cast[*MultiTrigger](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_AllKinds validates an instance of AllKinds according +// to declarative validation rules in the API schema. +func Validate_AllKinds( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *AllKinds) (errs field.ErrorList) { + + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "ptrTrigger", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return obj.PtrTrigger != nil + }, "ptrDep", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return obj.PtrDep != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "sliceTrigger", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return len(obj.SliceTrigger) != 0 + }, "sliceDep", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return len(obj.SliceDep) != 0 + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "mapTrigger", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return len(obj.MapTrigger) != 0 + }, "mapDep", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return len(obj.MapDep) != 0 + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "intTrigger", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + var z int + return obj.IntTrigger != z + }, "intDep", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + var z int + return obj.IntDep != z + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field AllKinds.TypeMeta has no validation + + { // field AllKinds.PtrTrigger + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) *string { + return oldObj.PtrTrigger + }) + errs = append(errs, fn(fldPath.Child("ptrTrigger"), obj.PtrTrigger, oldVal, oldObj != nil)...) + } + + { // field AllKinds.PtrDep + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) *string { + return oldObj.PtrDep + }) + errs = append(errs, fn(fldPath.Child("ptrDep"), obj.PtrDep, oldVal, oldObj != nil)...) + } + + { // field AllKinds.SliceTrigger + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) []string { + return oldObj.SliceTrigger + }) + errs = append(errs, fn(fldPath.Child("sliceTrigger"), obj.SliceTrigger, oldVal, oldObj != nil)...) + } + + { // field AllKinds.SliceDep + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) []string { + return oldObj.SliceDep + }) + errs = append(errs, fn(fldPath.Child("sliceDep"), obj.SliceDep, oldVal, oldObj != nil)...) + } + + { // field AllKinds.MapTrigger + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) map[string]string { + return oldObj.MapTrigger + }) + errs = append(errs, fn(fldPath.Child("mapTrigger"), obj.MapTrigger, oldVal, oldObj != nil)...) + } + + { // field AllKinds.MapDep + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) map[string]string { + return oldObj.MapDep + }) + errs = append(errs, fn(fldPath.Child("mapDep"), obj.MapDep, oldVal, oldObj != nil)...) + } + + { // field AllKinds.IntTrigger + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) *int { + return &oldObj.IntTrigger + }) + errs = append(errs, fn(fldPath.Child("intTrigger"), &obj.IntTrigger, oldVal, oldObj != nil)...) + } + + { // field AllKinds.IntDep + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) *int { + return &oldObj.IntDep + }) + errs = append(errs, fn(fldPath.Child("intDep"), &obj.IntDep, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_MultiDependent validates an instance of MultiDependent according +// to declarative validation rules in the API schema. +func Validate_MultiDependent( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MultiDependent) (errs field.ErrorList) { + + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "trigger", + func(obj *MultiDependent) bool { + if obj == nil { + return false + } + return obj.Trigger != nil + }, "dependentA", + func(obj *MultiDependent) bool { + if obj == nil { + return false + } + return obj.DependentA != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "trigger", + func(obj *MultiDependent) bool { + if obj == nil { + return false + } + return obj.Trigger != nil + }, "dependentB", + func(obj *MultiDependent) bool { + if obj == nil { + return false + } + return obj.DependentB != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field MultiDependent.TypeMeta has no validation + + { // field MultiDependent.Trigger + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiDependent) *string { + return oldObj.Trigger + }) + errs = append(errs, fn(fldPath.Child("trigger"), obj.Trigger, oldVal, oldObj != nil)...) + } + + { // field MultiDependent.DependentA + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiDependent) *string { + return oldObj.DependentA + }) + errs = append(errs, fn(fldPath.Child("dependentA"), obj.DependentA, oldVal, oldObj != nil)...) + } + + { // field MultiDependent.DependentB + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiDependent) *string { + return oldObj.DependentB + }) + errs = append(errs, fn(fldPath.Child("dependentB"), obj.DependentB, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_MultiTrigger validates an instance of MultiTrigger according +// to declarative validation rules in the API schema. +func Validate_MultiTrigger( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MultiTrigger) (errs field.ErrorList) { + + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "triggerA", + func(obj *MultiTrigger) bool { + if obj == nil { + return false + } + return obj.TriggerA != nil + }, "dependent", + func(obj *MultiTrigger) bool { + if obj == nil { + return false + } + return obj.Dependent != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "triggerB", + func(obj *MultiTrigger) bool { + if obj == nil { + return false + } + return obj.TriggerB != nil + }, "dependent", + func(obj *MultiTrigger) bool { + if obj == nil { + return false + } + return obj.Dependent != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field MultiTrigger.TypeMeta has no validation + + { // field MultiTrigger.TriggerA + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiTrigger) *string { + return oldObj.TriggerA + }) + errs = append(errs, fn(fldPath.Child("triggerA"), obj.TriggerA, oldVal, oldObj != nil)...) + } + + { // field MultiTrigger.TriggerB + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiTrigger) *string { + return oldObj.TriggerB + }) + errs = append(errs, fn(fldPath.Child("triggerB"), obj.TriggerB, oldVal, oldObj != nil)...) + } + + { // field MultiTrigger.Dependent + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiTrigger) *string { + return oldObj.Dependent + }) + errs = append(errs, fn(fldPath.Child("dependent"), obj.Dependent, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.DependentForbidden(ctx, op, fldPath, obj, oldObj, "trigger", + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.Trigger != nil + }, "dependent", + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.Dependent != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.Trigger + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Trigger + }) + errs = append(errs, fn(fldPath.Child("trigger"), obj.Trigger, oldVal, oldObj != nil)...) + } + + { // field Struct.Dependent + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Dependent + }) + errs = append(errs, fn(fldPath.Child("dependent"), obj.Dependent, oldVal, oldObj != nil)...) + } + + { // field Struct.OtherField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.OtherField + }) + errs = append(errs, fn(fldPath.Child("otherField"), obj.OtherField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/doc.go new file mode 100644 index 0000000000..598c99baaa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/doc.go @@ -0,0 +1,106 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package dependentrequired + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Basic one-to-one dependency. +type Struct struct { + TypeMeta int + + // +k8s:optional + // +k8s:dependentRequired("dependent") + Trigger *string `json:"trigger"` + + // +k8s:optional + Dependent *string `json:"dependent"` + + // +k8s:optional + OtherField *string `json:"otherField"` +} + +// One trigger, many dependents (repeated tags). +type MultiDependent struct { + TypeMeta int + + // +k8s:optional + // +k8s:dependentRequired("dependentA") + // +k8s:dependentRequired("dependentB") + Trigger *string `json:"trigger"` + + // +k8s:optional + DependentA *string `json:"dependentA"` + + // +k8s:optional + DependentB *string `json:"dependentB"` +} + +// Many triggers, one dependent. +type MultiTrigger struct { + TypeMeta int + + // +k8s:optional + // +k8s:dependentRequired("dependent") + TriggerA *string `json:"triggerA"` + + // +k8s:optional + // +k8s:dependentRequired("dependent") + TriggerB *string `json:"triggerB"` + + // +k8s:optional + Dependent *string `json:"dependent"` +} + +// All four "is set" extractor kinds. +type AllKinds struct { + TypeMeta int + + // +k8s:optional + // +k8s:dependentRequired("ptrDep") + PtrTrigger *string `json:"ptrTrigger"` + + // +k8s:optional + PtrDep *string `json:"ptrDep"` + + // +k8s:optional + // +k8s:dependentRequired("sliceDep") + SliceTrigger []string `json:"sliceTrigger"` + + // +k8s:optional + SliceDep []string `json:"sliceDep"` + + // +k8s:optional + // +k8s:dependentRequired("mapDep") + MapTrigger map[string]string `json:"mapTrigger"` + + // +k8s:optional + MapDep map[string]string `json:"mapDep"` + + // +k8s:optional + // +k8s:dependentRequired("intDep") + IntTrigger int `json:"intTrigger"` + + // +k8s:optional + IntDep int `json:"intDep"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/doc_test.go new file mode 100644 index 0000000000..8b4bc8e3ff --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/doc_test.go @@ -0,0 +1,113 @@ +/* +Copyright 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 dependentrequired + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // One-directional: dependent alone is fine. + st.Value(&Struct{Dependent: ptr.To("d")}).ExpectValid() + + st.Value(&Struct{Trigger: ptr.To("t"), Dependent: ptr.To("d")}).ExpectValid() + + st.Value(&Struct{Trigger: ptr.To("t")}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Required(field.NewPath("dependent"), "").WithOrigin("dependentRequired"), + }, + ) + + // Ratchet: unrelated field changed, trigger and dependent set-ness unchanged → skip. + st.Value(&Struct{Trigger: ptr.To("t"), OtherField: ptr.To("new")}). + OldValue(&Struct{Trigger: ptr.To("t"), OtherField: ptr.To("old")}). + ExpectValid() + + // Ratchet: trigger value changed but set-ness unchanged → skip (same rationale as union). + st.Value(&Struct{Trigger: ptr.To("t")}). + OldValue(&Struct{Trigger: ptr.To("old")}). + ExpectValid() + + // Newly cleared dependent → fire. + st.Value(&Struct{Trigger: ptr.To("t")}). + OldValue(&Struct{Trigger: ptr.To("t"), Dependent: ptr.To("d")}). + ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Required(field.NewPath("dependent"), "").WithOrigin("dependentRequired"), + }, + ) +} + +func TestMultiDependent(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Repeated tags → independent implications, each at its own dependent path. + st.Value(&MultiDependent{Trigger: ptr.To("t")}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Required(field.NewPath("dependentA"), "").WithOrigin("dependentRequired"), + field.Required(field.NewPath("dependentB"), "").WithOrigin("dependentRequired"), + }, + ) +} + +func TestMultiTrigger(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Distinct triggers → independent implications at the same path. + // ByOrigin absorbs both actuals. + st.Value(&MultiTrigger{TriggerA: ptr.To("a"), TriggerB: ptr.To("b")}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Required(field.NewPath("dependent"), "").WithOrigin("dependentRequired"), + }, + ) +} + +func TestAllKinds(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Each kind's extractor fires. + st.Value(&AllKinds{ + PtrTrigger: ptr.To("t"), + SliceTrigger: []string{"x"}, + MapTrigger: map[string]string{"k": "v"}, + IntTrigger: 1, + }).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Required(field.NewPath("ptrDep"), "").WithOrigin("dependentRequired"), + field.Required(field.NewPath("sliceDep"), "").WithOrigin("dependentRequired"), + field.Required(field.NewPath("mapDep"), "").WithOrigin("dependentRequired"), + field.Required(field.NewPath("intDep"), "").WithOrigin("dependentRequired"), + }, + ) + + // Empty slice/map and zero int = "not set". + st.Value(&AllKinds{ + SliceTrigger: []string{}, + MapTrigger: map[string]string{}, + IntTrigger: 0, + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/zz_generated.validations.go new file mode 100644 index 0000000000..b9306e3f49 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/dependentrequired/zz_generated.validations.go @@ -0,0 +1,763 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package dependentrequired + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type AllKinds + scheme.AddValidationFunc( + (*AllKinds)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_AllKinds( + ctx, op, nil, /* fldPath */ + obj.(*AllKinds), + safe.Cast[*AllKinds](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MultiDependent + scheme.AddValidationFunc( + (*MultiDependent)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MultiDependent( + ctx, op, nil, /* fldPath */ + obj.(*MultiDependent), + safe.Cast[*MultiDependent](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MultiTrigger + scheme.AddValidationFunc( + (*MultiTrigger)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MultiTrigger( + ctx, op, nil, /* fldPath */ + obj.(*MultiTrigger), + safe.Cast[*MultiTrigger](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_AllKinds validates an instance of AllKinds according +// to declarative validation rules in the API schema. +func Validate_AllKinds( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *AllKinds) (errs field.ErrorList) { + + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "ptrTrigger", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return obj.PtrTrigger != nil + }, "ptrDep", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return obj.PtrDep != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "sliceTrigger", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return len(obj.SliceTrigger) != 0 + }, "sliceDep", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return len(obj.SliceDep) != 0 + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "mapTrigger", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return len(obj.MapTrigger) != 0 + }, "mapDep", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + return len(obj.MapDep) != 0 + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "intTrigger", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + var z int + return obj.IntTrigger != z + }, "intDep", + func(obj *AllKinds) bool { + if obj == nil { + return false + } + var z int + return obj.IntDep != z + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field AllKinds.TypeMeta has no validation + + { // field AllKinds.PtrTrigger + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) *string { + return oldObj.PtrTrigger + }) + errs = append(errs, fn(fldPath.Child("ptrTrigger"), obj.PtrTrigger, oldVal, oldObj != nil)...) + } + + { // field AllKinds.PtrDep + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) *string { + return oldObj.PtrDep + }) + errs = append(errs, fn(fldPath.Child("ptrDep"), obj.PtrDep, oldVal, oldObj != nil)...) + } + + { // field AllKinds.SliceTrigger + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) []string { + return oldObj.SliceTrigger + }) + errs = append(errs, fn(fldPath.Child("sliceTrigger"), obj.SliceTrigger, oldVal, oldObj != nil)...) + } + + { // field AllKinds.SliceDep + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) []string { + return oldObj.SliceDep + }) + errs = append(errs, fn(fldPath.Child("sliceDep"), obj.SliceDep, oldVal, oldObj != nil)...) + } + + { // field AllKinds.MapTrigger + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) map[string]string { + return oldObj.MapTrigger + }) + errs = append(errs, fn(fldPath.Child("mapTrigger"), obj.MapTrigger, oldVal, oldObj != nil)...) + } + + { // field AllKinds.MapDep + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) map[string]string { + return oldObj.MapDep + }) + errs = append(errs, fn(fldPath.Child("mapDep"), obj.MapDep, oldVal, oldObj != nil)...) + } + + { // field AllKinds.IntTrigger + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) *int { + return &oldObj.IntTrigger + }) + errs = append(errs, fn(fldPath.Child("intTrigger"), &obj.IntTrigger, oldVal, oldObj != nil)...) + } + + { // field AllKinds.IntDep + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AllKinds) *int { + return &oldObj.IntDep + }) + errs = append(errs, fn(fldPath.Child("intDep"), &obj.IntDep, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_MultiDependent validates an instance of MultiDependent according +// to declarative validation rules in the API schema. +func Validate_MultiDependent( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MultiDependent) (errs field.ErrorList) { + + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "trigger", + func(obj *MultiDependent) bool { + if obj == nil { + return false + } + return obj.Trigger != nil + }, "dependentA", + func(obj *MultiDependent) bool { + if obj == nil { + return false + } + return obj.DependentA != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "trigger", + func(obj *MultiDependent) bool { + if obj == nil { + return false + } + return obj.Trigger != nil + }, "dependentB", + func(obj *MultiDependent) bool { + if obj == nil { + return false + } + return obj.DependentB != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field MultiDependent.TypeMeta has no validation + + { // field MultiDependent.Trigger + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiDependent) *string { + return oldObj.Trigger + }) + errs = append(errs, fn(fldPath.Child("trigger"), obj.Trigger, oldVal, oldObj != nil)...) + } + + { // field MultiDependent.DependentA + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiDependent) *string { + return oldObj.DependentA + }) + errs = append(errs, fn(fldPath.Child("dependentA"), obj.DependentA, oldVal, oldObj != nil)...) + } + + { // field MultiDependent.DependentB + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiDependent) *string { + return oldObj.DependentB + }) + errs = append(errs, fn(fldPath.Child("dependentB"), obj.DependentB, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_MultiTrigger validates an instance of MultiTrigger according +// to declarative validation rules in the API schema. +func Validate_MultiTrigger( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MultiTrigger) (errs field.ErrorList) { + + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "triggerA", + func(obj *MultiTrigger) bool { + if obj == nil { + return false + } + return obj.TriggerA != nil + }, "dependent", + func(obj *MultiTrigger) bool { + if obj == nil { + return false + } + return obj.Dependent != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "triggerB", + func(obj *MultiTrigger) bool { + if obj == nil { + return false + } + return obj.TriggerB != nil + }, "dependent", + func(obj *MultiTrigger) bool { + if obj == nil { + return false + } + return obj.Dependent != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field MultiTrigger.TypeMeta has no validation + + { // field MultiTrigger.TriggerA + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiTrigger) *string { + return oldObj.TriggerA + }) + errs = append(errs, fn(fldPath.Child("triggerA"), obj.TriggerA, oldVal, oldObj != nil)...) + } + + { // field MultiTrigger.TriggerB + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiTrigger) *string { + return oldObj.TriggerB + }) + errs = append(errs, fn(fldPath.Child("triggerB"), obj.TriggerB, oldVal, oldObj != nil)...) + } + + { // field MultiTrigger.Dependent + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MultiTrigger) *string { + return oldObj.Dependent + }) + errs = append(errs, fn(fldPath.Child("dependent"), obj.Dependent, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.DependentRequired(ctx, op, fldPath, obj, oldObj, "trigger", + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.Trigger != nil + }, "dependent", + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.Dependent != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.Trigger + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Trigger + }) + errs = append(errs, fn(fldPath.Child("trigger"), obj.Trigger, oldVal, oldObj != nil)...) + } + + { // field Struct.Dependent + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Dependent + }) + errs = append(errs, fn(fldPath.Child("dependent"), obj.Dependent, oldVal, oldObj != nil)...) + } + + { // field Struct.OtherField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.OtherField + }) + errs = append(errs, fn(fldPath.Child("otherField"), obj.OtherField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc.go new file mode 100644 index 0000000000..aa630a2623 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc.go @@ -0,0 +1,57 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package eachkey + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:eachKey=+k8s:validateFalse="Struct.MapField(keys)" + MapField map[string]string `json:"mapField"` + + // +k8s:eachKey=+k8s:validateFalse="Struct.MapTypedefField(keys)" + MapTypedefField map[UnvalidatedStringType]string `json:"mapTypedefField"` + + // +k8s:eachKey=+k8s:validateFalse="Struct.MapValidatedTypedefField(keys)" + MapValidatedTypedefField map[ValidatedStringType]string `json:"mapValidatedTypedefField"` + + // +k8s:eachKey=+k8s:validateFalse="Struct.MapTypeField(keys)" + MapTypeField UnvalidatedMapType `json:"mapTypeField"` + + // +k8s:eachKey=+k8s:validateFalse="Struct.ValidatedMapTypeField(keys)" + ValidatedMapTypeField ValidatedMapType `json:"validatedMapTypeField"` +} + +// Note: no validations. +type UnvalidatedStringType string + +// +k8s:validateFalse="ValidatedStringType" +type ValidatedStringType string + +// Note: no validations. +type UnvalidatedMapType map[string]string + +// +k8s:eachKey=+k8s:validateFalse="ValidatedMapType(keys)" +type ValidatedMapType map[string]string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc_test.go new file mode 100644 index 0000000000..fa9a48ae34 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/doc_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2024 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 eachkey + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values. + }).ExpectValid() + + st.Value(&Struct{ + MapField: map[string]string{"a": "A", "b": "B"}, + MapTypedefField: map[UnvalidatedStringType]string{"a": "A", "b": "B"}, + MapValidatedTypedefField: map[ValidatedStringType]string{"a": "A", "b": "B"}, + MapTypeField: UnvalidatedMapType{"a": "A", "b": "B"}, + ValidatedMapTypeField: ValidatedMapType{"a": "A", "b": "B"}, + }).ExpectValidateFalseByPath(map[string][]string{ + "mapField": { + "Struct.MapField(keys)", + "Struct.MapField(keys)", + }, + "mapTypedefField": { + "Struct.MapTypedefField(keys)", + "Struct.MapTypedefField(keys)", + }, + "mapValidatedTypedefField": { + "Struct.MapValidatedTypedefField(keys)", "ValidatedStringType", + "Struct.MapValidatedTypedefField(keys)", "ValidatedStringType", + }, + "mapTypeField": { + "Struct.MapTypeField(keys)", + "Struct.MapTypeField(keys)", + }, + "validatedMapTypeField": { + "Struct.ValidatedMapTypeField(keys)", "ValidatedMapType(keys)", + "Struct.ValidatedMapTypeField(keys)", "ValidatedMapType(keys)", + }, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/zz_generated.validations.go new file mode 100644 index 0000000000..8a25f46579 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachkey/zz_generated.validations.go @@ -0,0 +1,238 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package eachkey + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "Struct.MapField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[UnvalidatedStringType]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UnvalidatedStringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "Struct.MapTypedefField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[UnvalidatedStringType]string { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[ValidatedStringType]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ValidatedStringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "Struct.MapValidatedTypedefField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the key type's validation function + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, Validate_ValidatedStringType); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[ValidatedStringType]string { + return oldObj.MapValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapValidatedTypedefField"), obj.MapValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypeField + fn := func( + fldPath *field.Path, + obj, oldObj UnvalidatedMapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "Struct.MapTypeField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) UnvalidatedMapType { + return oldObj.MapTypeField + }) + errs = append(errs, fn(fldPath.Child("mapTypeField"), obj.MapTypeField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedMapTypeField + fn := func( + fldPath *field.Path, + obj, oldObj ValidatedMapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "Struct.ValidatedMapTypeField(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ValidatedMapType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ValidatedMapType { + return oldObj.ValidatedMapTypeField + }) + errs = append(errs, fn(fldPath.Child("validatedMapTypeField"), obj.ValidatedMapTypeField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ValidatedMapType validates an instance of ValidatedMapType according +// to declarative validation rules in the API schema. +func Validate_ValidatedMapType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ValidatedMapType) (errs field.ErrorList) { + + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "ValidatedMapType(keys)") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ValidatedStringType validates an instance of ValidatedStringType according +// to declarative validation rules in the API schema. +func Validate_ValidatedStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ValidatedStringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "ValidatedStringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc.go new file mode 100644 index 0000000000..dfa903970c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc.go @@ -0,0 +1,41 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package mapofpointers + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]" + MapField map[string]*OtherStruct `json:"mapField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapPrimitiveField[*]" + MapPrimitiveField map[string]*string `json:"mapPrimitiveField"` +} + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct { + Value string `json:"value"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc_test.go new file mode 100644 index 0000000000..b9e93fe566 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/doc_test.go @@ -0,0 +1,52 @@ +/* +Copyright 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 mapofpointers + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // 1. Zero values should be valid + st.Value(&Struct{ + // All zero values. + }).ExpectValid() + + // 2. Non-nil elements trigger validation errors + st.Value(&Struct{ + MapField: map[string]*OtherStruct{"k1": {}, "k2": {}}, + MapPrimitiveField: map[string]*string{"k1": new("a"), "k2": new("b")}, + }).ExpectValidateFalseByPath(map[string][]string{ + "mapField[k1]": {"field Struct.MapField[*]", "type OtherStruct"}, + "mapField[k2]": {"field Struct.MapField[*]", "type OtherStruct"}, + "mapPrimitiveField[k1]": {"field Struct.MapPrimitiveField[*]"}, + "mapPrimitiveField[k2]": {"field Struct.MapPrimitiveField[*]"}, + }) + + // 3. Nil elements trigger Required errors from PtrMapNoNils + st.Value(&Struct{ + MapField: map[string]*OtherStruct{"k1": nil}, + MapPrimitiveField: map[string]*string{"k1": nil}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("mapField").Key("k1"), ""), + field.Required(field.NewPath("mapPrimitiveField").Key("k1"), ""), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/zz_generated.validations.go new file mode 100644 index 0000000000..641eb0e3fc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_pointers/zz_generated.validations.go @@ -0,0 +1,156 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapofpointers + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field OtherStruct.Value has no validation + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, OtherStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]*OtherStruct { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapPrimitiveField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.EachPtrMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapPrimitiveField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]*string { + return oldObj.MapPrimitiveField + }) + errs = append(errs, fn(fldPath.Child("mapPrimitiveField"), obj.MapPrimitiveField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc.go new file mode 100644 index 0000000000..d9739bb1dd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc.go @@ -0,0 +1,38 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package mapofprimitive + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]" + MapField map[string]string `json:"mapField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapTypedefField[*]" + MapTypedefField map[string]StringType `json:"mapTypedefField"` +} + +type StringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc_test.go new file mode 100644 index 0000000000..05f4678f70 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/doc_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 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 mapofprimitive + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values. + }).ExpectValid() + + st.Value(&Struct{ + MapField: map[string]string{"a": "A", "b": "B"}, + MapTypedefField: map[string]StringType{"a": "A", "b": "B"}, + }).ExpectValidateFalseByPath(map[string][]string{ + "mapField[a]": {"field Struct.MapField[*]"}, + "mapField[b]": {"field Struct.MapField[*]"}, + "mapTypedefField[a]": {"field Struct.MapTypedefField[*]"}, + "mapTypedefField[b]": {"field Struct.MapTypedefField[*]"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/zz_generated.validations.go new file mode 100644 index 0000000000..6fbbf43955 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_primitive/zz_generated.validations.go @@ -0,0 +1,122 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapofprimitive + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]StringType { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc.go new file mode 100644 index 0000000000..3c65b1566e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc.go @@ -0,0 +1,40 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package mapofstruct + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]" + MapField map[string]OtherStruct `json:"mapField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapTypedefField[*]" + MapTypedefField map[string]OtherTypedefStruct `json:"mapTypedefField"` +} + +type OtherStruct struct{} + +type OtherTypedefStruct OtherStruct diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc_test.go new file mode 100644 index 0000000000..155826edcb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/doc_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 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 mapofstruct + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values. + }).ExpectValid() + + st.Value(&Struct{ + MapField: map[string]OtherStruct{"a": {}, "b": {}}, + MapTypedefField: map[string]OtherTypedefStruct{"a": {}, "b": {}}, + }).ExpectValidateFalseByPath(map[string][]string{ + "mapField[a]": {"field Struct.MapField[*]"}, + "mapField[b]": {"field Struct.MapField[*]"}, + "mapTypedefField[a]": {"field Struct.MapTypedefField[*]"}, + "mapTypedefField[b]": {"field Struct.MapTypedefField[*]"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/zz_generated.validations.go new file mode 100644 index 0000000000..db212ac2c1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/map_of_struct/zz_generated.validations.go @@ -0,0 +1,122 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapofstruct + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]OtherStruct { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherTypedefStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]OtherTypedefStruct { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/doc.go new file mode 100644 index 0000000000..d3de3f36eb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/doc.go @@ -0,0 +1,41 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofpointers + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*]" + ListField []*OtherStruct `json:"listField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListPrimitiveField[*]" + ListPrimitiveField []*string `json:"listPrimitiveField"` +} + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct { + Value string `json:"value"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/doc_test.go new file mode 100644 index 0000000000..f0d0af6c69 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/doc_test.go @@ -0,0 +1,52 @@ +/* +Copyright 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 sliceofpointers + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // 1. Zero values should be valid + st.Value(&Struct{ + // All zero values. + }).ExpectValid() + + // 2. Non-nil elements trigger validation errors + st.Value(&Struct{ + ListField: []*OtherStruct{{}, {}}, + ListPrimitiveField: []*string{new("a"), new("b")}, + }).ExpectValidateFalseByPath(map[string][]string{ + "listField[0]": {"field Struct.ListField[*]", "type OtherStruct"}, + "listField[1]": {"field Struct.ListField[*]", "type OtherStruct"}, + "listPrimitiveField[0]": {"field Struct.ListPrimitiveField[*]"}, + "listPrimitiveField[1]": {"field Struct.ListPrimitiveField[*]"}, + }) + + // 3. Nil elements trigger Required errors from EachPtrSliceVal + st.Value(&Struct{ + ListField: []*OtherStruct{nil}, + ListPrimitiveField: []*string{nil}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("listField").Index(0), ""), + field.Required(field.NewPath("listPrimitiveField").Index(0), ""), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/zz_generated.validations.go new file mode 100644 index 0000000000..457639f3e2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_pointers/zz_generated.validations.go @@ -0,0 +1,156 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofpointers + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field OtherStruct.Value has no validation + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []*OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[OtherStruct](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*OtherStruct { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListPrimitiveField + fn := func( + fldPath *field.Path, + obj, oldObj []*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListPrimitiveField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*string { + return oldObj.ListPrimitiveField + }) + errs = append(errs, fn(fldPath.Child("listPrimitiveField"), obj.ListPrimitiveField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc.go new file mode 100644 index 0000000000..0746127c9b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc.go @@ -0,0 +1,38 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofprimitive + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*]" + ListField []string `json:"listField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListTypedefField[*]" + ListTypedefField []StringType `json:"listTypedefField"` +} + +type StringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc_test.go new file mode 100644 index 0000000000..cd226444ea --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/doc_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2024 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 sliceofprimitive + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values. + }).ExpectValid() + + st.Value(&Struct{ + ListField: []string{"zero", "one"}, + ListTypedefField: []StringType{StringType("zero"), StringType("one")}, + }).ExpectValidateFalseByPath(map[string][]string{ + "listField[0]": {"field Struct.ListField[*]"}, + "listField[1]": {"field Struct.ListField[*]"}, + "listTypedefField[0]": {"field Struct.ListTypedefField[*]"}, + "listTypedefField[1]": {"field Struct.ListTypedefField[*]"}, + }) + + // Test validation ratcheting. + st.Value(&Struct{ + ListField: []string{"zero", "one"}, + ListTypedefField: []StringType{StringType("zero"), StringType("one")}, + }).OldValue(&Struct{ + // Same data, different order - should still fail. + ListField: []string{"one", "zero"}, + ListTypedefField: []StringType{StringType("one"), StringType("zero")}, + }).ExpectValidateFalseByPath(map[string][]string{ + "listField[0]": {"field Struct.ListField[*]"}, + "listField[1]": {"field Struct.ListField[*]"}, + "listTypedefField[0]": {"field Struct.ListTypedefField[*]"}, + "listTypedefField[1]": {"field Struct.ListTypedefField[*]"}, + }) + + st.Value(&Struct{ + ListField: []string{"zero", "one"}, + ListTypedefField: []StringType{StringType("zero"), StringType("one")}, + }).OldValue(&Struct{ + ListField: []string{"zero", "one"}, + ListTypedefField: []StringType{StringType("zero"), StringType("one")}, + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/zz_generated.validations.go new file mode 100644 index 0000000000..03b166441b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_primitive/zz_generated.validations.go @@ -0,0 +1,122 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofprimitive + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []StringType { + return oldObj.ListTypedefField + }) + errs = append(errs, fn(fldPath.Child("listTypedefField"), obj.ListTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc.go new file mode 100644 index 0000000000..2a0cfa867c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc.go @@ -0,0 +1,47 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofstruct + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*]" + ListField []OtherStruct `json:"listField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListTypedefField[*]" + ListTypedefField []OtherTypedefStruct `json:"listTypedefField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListNonComparableField[*]" + ListNonComparableField []NonComparableStruct `json:"listNonComparableField"` +} + +type OtherStruct struct{} + +type OtherTypedefStruct OtherStruct + +type NonComparableStruct struct { + SliceField []string `json:"sliceField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc_test.go new file mode 100644 index 0000000000..0dde83d4f4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/doc_test.go @@ -0,0 +1,63 @@ +/* +Copyright 2024 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 sliceofstruct + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values. + }).ExpectValid() + + st.Value(&Struct{ + ListField: []OtherStruct{{}, {}}, + ListTypedefField: []OtherTypedefStruct{{}, {}}, + }).ExpectValidateFalseByPath(map[string][]string{ + "listField[0]": {"field Struct.ListField[*]"}, + "listField[1]": {"field Struct.ListField[*]"}, + "listTypedefField[0]": {"field Struct.ListTypedefField[*]"}, + "listTypedefField[1]": {"field Struct.ListTypedefField[*]"}, + }) + st.Value(&Struct{ + ListNonComparableField: []NonComparableStruct{{SliceField: []string{"zero", "one"}}, {SliceField: []string{"three", "four"}}}, + }).ExpectValidateFalseByPath(map[string][]string{ + "listNonComparableField[0]": {"field Struct.ListNonComparableField[*]"}, + "listNonComparableField[1]": {"field Struct.ListNonComparableField[*]"}, + }) + + // Test validation ratcheting. + st.Value(&Struct{ + ListField: []OtherStruct{{}, {}}, + ListTypedefField: []OtherTypedefStruct{{}, {}}, + }).OldValue(&Struct{ + ListField: []OtherStruct{{}, {}}, + ListTypedefField: []OtherTypedefStruct{{}, {}}, + }).ExpectValid() + + st.Value(&Struct{ + // New element exists in old value, but this is not a set. + ListNonComparableField: []NonComparableStruct{{SliceField: []string{"three", "four"}}}, + }).OldValue(&Struct{ + ListNonComparableField: []NonComparableStruct{{SliceField: []string{"zero", "one"}}, {SliceField: []string{"three", "four"}}}, + }).ExpectValidateFalseByPath(map[string][]string{ + "listNonComparableField[0]": {"field Struct.ListNonComparableField[*]"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/zz_generated.validations.go new file mode 100644 index 0000000000..921e605e4b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/slice_of_struct/zz_generated.validations.go @@ -0,0 +1,149 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofstruct + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherTypedefStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherTypedefStruct { + return oldObj.ListTypedefField + }) + errs = append(errs, fn(fldPath.Child("listTypedefField"), obj.ListTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListNonComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []NonComparableStruct { + return oldObj.ListNonComparableField + }) + errs = append(errs, fn(fldPath.Child("listNonComparableField"), obj.ListNonComparableField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc.go new file mode 100644 index 0000000000..c66f9cedbd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc.go @@ -0,0 +1,52 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package typedeftomap + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: no validation here +type UnvalidatedType map[string]string + +// +k8s:eachVal=+k8s:validateFalse="type MapType[*]" +type MapType map[string]string + +// Note: no validation here +type UnvalidatedPtrType map[string]*string + +// +k8s:validateFalse="type StringType" +type StringType string + +// +k8s:eachVal=+k8s:validateFalse="type MapTypedefType[*]" +type MapTypedefType map[string]StringType + +// +k8s:validateFalse="type Struct" +type Struct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapField[*]" + MapField MapType `json:"mapField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapTypedefField[*]" + MapTypedefField MapTypedefType `json:"mapTypedefField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc_test.go new file mode 100644 index 0000000000..a2dce9633b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/doc_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 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 typedeftomap + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values. + }).ExpectValidateFalseByPath(map[string][]string{ + "": {"type Struct"}, + }) + + st.Value(&Struct{ + MapField: MapType{"a": "A", "b": "B"}, + MapTypedefField: MapTypedefType{"a": StringType("A"), "b": StringType("B")}, + }).ExpectValidateFalseByPath(map[string][]string{ + "": {"type Struct"}, + "mapField[a]": {"type MapType[*]", "field Struct.MapField[*]"}, + "mapField[b]": {"type MapType[*]", "field Struct.MapField[*]"}, + "mapTypedefField[a]": {"type MapTypedefType[*]", "field Struct.MapTypedefField[*]", "type StringType"}, + "mapTypedefField[b]": {"type MapTypedefType[*]", "field Struct.MapTypedefField[*]", "type StringType"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/zz_generated.validations.go new file mode 100644 index 0000000000..a1ecc1812c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_map/zz_generated.validations.go @@ -0,0 +1,180 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftomap + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_MapType validates an instance of MapType according +// to declarative validation rules in the API schema. +func Validate_MapType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MapType) (errs field.ErrorList) { + + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_MapTypedefType validates an instance of MapTypedefType according +// to declarative validation rules in the API schema. +func Validate_MapTypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MapTypedefType) (errs field.ErrorList) { + + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapTypedefType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_StringType); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type StringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type Struct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj MapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MapType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapType { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj MapTypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MapTypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapTypedefType { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc.go new file mode 100644 index 0000000000..91870d7eec --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc.go @@ -0,0 +1,50 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package typedeftoslice + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: no validation here +type UnvalidatedType []string + +// +k8s:eachVal=+k8s:validateFalse="type ListType[*]" +type ListType []string + +// Note: no validation here +type UnvalidatedPtrType []*string + +type StringType string + +// +k8s:eachVal=+k8s:validateFalse="type ListTypedefType[*]" +type ListTypedefType []StringType + +type Struct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListField[*]" + ListField ListType `json:"listField"` + + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListTypedefField[*]" + ListTypedefField ListTypedefType `json:"listTypedefField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc_test.go new file mode 100644 index 0000000000..6978d1155f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/doc_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 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 typedeftoslice + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values. + }).ExpectValid() + + st.Value(&Struct{ + ListField: ListType{"zero", "one"}, + ListTypedefField: ListTypedefType{StringType("zero"), StringType("one")}, + }).ExpectValidateFalseByPath(map[string][]string{ + "listField[0]": {"type ListType[*]", "field Struct.ListField[*]"}, + "listField[1]": {"type ListType[*]", "field Struct.ListField[*]"}, + "listTypedefField[0]": {"type ListTypedefType[*]", "field Struct.ListTypedefField[*]"}, + "listTypedefField[1]": {"type ListTypedefType[*]", "field Struct.ListTypedefField[*]"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/zz_generated.validations.go new file mode 100644 index 0000000000..3d3bc63e3e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/eachval/typedef_to_slice/zz_generated.validations.go @@ -0,0 +1,158 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftoslice + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ListType validates an instance of ListType according +// to declarative validation rules in the API schema. +func Validate_ListType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ListType) (errs field.ErrorList) { + + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ListTypedefType validates an instance of ListTypedefType according +// to declarative validation rules in the API schema. +func Validate_ListTypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ListTypedefType) (errs field.ErrorList) { + + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type ListTypedefType[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj ListType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ListType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ListType { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj ListTypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StringType) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListTypedefField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_ListTypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ListTypedefType { + return oldObj.ListTypedefField + }) + errs = append(errs, fn(fldPath.Child("listTypedefField"), obj.ListTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc.go new file mode 100644 index 0000000000..85cbc0fd81 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package enum + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + Enum0Field Enum0 `json:"enum0Field"` + Enum0PtrField *Enum0 `json:"enum0PtrField"` + + Enum1Field Enum1 `json:"enum1Field"` + Enum1PtrField *Enum1 `json:"enum1PtrField"` + + Enum2Field Enum2 `json:"enum2Field"` + Enum2PtrField *Enum2 `json:"enum2PtrField"` + + NotEnumField NotEnum `json:"notEnumField"` + NotEnumPtrField *NotEnum `json:"notEnumPtrField"` +} + +// +k8s:enum +type Enum0 string // Note: this enum has no values + +// +k8s:enum +type Enum1 string // Note: this enum has 1 value + +const ( + E1V1 Enum1 = "e1v1" +) + +// +k8s:enum +type Enum2 string // Note: this enum has 2 values + +const ( + E2V1 Enum2 = "e2v1" + E2V2 Enum2 = "e2v2" +) + +// Note: this is not an enum because the const values are of type Enum2, and +// because go elides intermediate typedefs (this is modelled as "NotEnum" -> +// "string" in the AST). +type NotEnum Enum2 diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc_test.go new file mode 100644 index 0000000000..3c558371c8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/doc_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2024 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 enum + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero vals + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("enum0Field"), Enum0(""), []Enum0{}), + field.NotSupported(field.NewPath("enum1Field"), Enum1(""), []Enum1{E1V1}), + field.NotSupported(field.NewPath("enum2Field"), Enum2(""), []Enum2{E2V1, E2V2}), + }) + + st.Value(&Struct{ + Enum0Field: "", // no valid value exists + Enum0PtrField: ptr.To(Enum0("")), // no valid value exists + Enum1Field: E1V1, + Enum1PtrField: ptr.To(E1V1), + Enum2Field: E2V1, + Enum2PtrField: ptr.To(E2V1), + NotEnumField: "x", + NotEnumPtrField: ptr.To(NotEnum("x")), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("enum0Field"), Enum0(""), []Enum0{}), + field.NotSupported(field.NewPath("enum0PtrField"), Enum0(""), []Enum0{}), + }) + + st.Value(&Struct{ + Enum0Field: "x", // no valid value exists + Enum0PtrField: ptr.To(Enum0("x")), // no valid value exists + Enum1Field: "x", + Enum1PtrField: ptr.To(Enum1("x")), + Enum2Field: "x", + Enum2PtrField: ptr.To(Enum2("x")), + NotEnumField: "x", + NotEnumPtrField: ptr.To(NotEnum("x")), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("enum0Field"), Enum0("x"), []Enum0{}), + field.NotSupported(field.NewPath("enum0PtrField"), Enum0("x"), []Enum0{}), + field.NotSupported(field.NewPath("enum1Field"), Enum1("x"), []Enum1{E1V1}), + field.NotSupported(field.NewPath("enum1PtrField"), Enum1("x"), []Enum1{E1V1}), + field.NotSupported(field.NewPath("enum2Field"), Enum2("x"), []Enum2{E2V1, E2V2}), + field.NotSupported(field.NewPath("enum2PtrField"), Enum2("x"), []Enum2{E2V1, E2V2}), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc.go new file mode 100644 index 0000000000..67484e8d5a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc.go @@ -0,0 +1,111 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package options + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + Enum0Field Enum0 `json:"enum0Field"` + Enum0PtrField *Enum0 `json:"enum0PtrField"` + + Enum1Field Enum1 `json:"enum1Field"` + Enum1PtrField *Enum1 `json:"enum1PtrField"` + + Enum2Field Enum2 `json:"enum2Field"` + Enum2PtrField *Enum2 `json:"enum2PtrField"` + + NotEnumField NotEnum `json:"notEnumField"` + NotEnumPtrField *NotEnum `json:"notEnumPtrField"` + + EnumWithExcludeField EnumWithExclude `json:"enumWithExcludeField"` + EnumWithExcludePtrField *EnumWithExclude `json:"enumWithExcludePtrField"` +} + +type ConditionalStruct struct { + TypeMeta int + + ConditionalEnumField ConditionalEnum `json:"conditionalEnumField"` + ConditionalEnumPtrField *ConditionalEnum `json:"conditionalEnumPtrField"` +} + +// +k8s:enum +type Enum0 string // Note: this enum has no values + +// +k8s:enum +type Enum1 string // Note: this enum has 1 value + +const ( + E1V1 Enum1 = "e1v1" +) + +// +k8s:enum +type Enum2 string // Note: this enum has 2 values + +const ( + E2V1 Enum2 = "e2v1" + E2V2 Enum2 = "e2v2" +) + +// Note: this is not an enum because the const values are of type Enum2, and +// because go elides intermediate typedefs (this is modelled as "NotEnum" -> +// "string" in the AST). +type NotEnum Enum2 + +// +k8s:enum +type EnumWithExclude string + +const ( + EnumWithExclude1 EnumWithExclude = "enumWithExclude1" + + // +k8s:enumExclude + EnumWithExclude2 EnumWithExclude = "enumWithExclude2" +) + +// +k8s:enum +type ConditionalEnum string + +const ( + // +k8s:ifEnabled(FeatureA)=+k8s:enumExclude + ConditionalA ConditionalEnum = "A" + + // +k8s:ifDisabled(FeatureB)=+k8s:enumExclude + ConditionalB ConditionalEnum = "B" + + // This value is always included. + ConditionalC ConditionalEnum = "C" + + // +k8s:ifEnabled(FeatureA)=+k8s:enumExclude + // +k8s:ifEnabled(FeatureB)=+k8s:enumExclude + ConditionalD ConditionalEnum = "D" + + // +k8s:ifDisabled(FeatureC)=+k8s:enumExclude + // +k8s:ifDisabled(FeatureD)=+k8s:enumExclude + ConditionalE ConditionalEnum = "E" + + // +k8s:ifDisabled(FeatureC)=+k8s:enumExclude + // +k8s:ifEnabled(FeatureD)=+k8s:enumExclude + ConditionalF ConditionalEnum = "F" +) diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc_test.go new file mode 100644 index 0000000000..88d15c8a63 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/doc_test.go @@ -0,0 +1,206 @@ +/* +Copyright 2024 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 options + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // opts declares all options this type references, enabling the named ones. + opts := func(enabled ...string) map[string]bool { + m := map[string]bool{"FeatureA": false, "FeatureB": false, "FeatureC": false, "FeatureD": false} + for _, e := range enabled { + m[e] = true + } + return m + } + + st.Value(&ConditionalStruct{ + ConditionalEnumField: "", + }).Opts(opts()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum(""), []ConditionalEnum{ConditionalA, ConditionalC, ConditionalD}), + }) + + // Scenario 1: No options (default) + // Valid values: A, C, D + st.Value(&ConditionalStruct{ + ConditionalEnumField: "B", + }).Opts(opts()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("B"), []ConditionalEnum{ConditionalA, ConditionalC, ConditionalD}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "E", + }).Opts(opts()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("E"), []ConditionalEnum{ConditionalA, ConditionalC, ConditionalD}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "F", + }).Opts(opts()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("F"), []ConditionalEnum{ConditionalA, ConditionalC, ConditionalD}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "A", + }).Opts(opts()).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "C", + }).Opts(opts()).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "D", + }).Opts(opts()).ExpectValid() + + // Scenario 2: FeatureA enabled + // Valid values: C + st.Value(&ConditionalStruct{ + ConditionalEnumField: "A", + }).Opts(opts("FeatureA")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("A"), []ConditionalEnum{ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "B", + }).Opts(opts("FeatureA")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("B"), []ConditionalEnum{ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "D", + }).Opts(opts("FeatureA")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("D"), []ConditionalEnum{ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "E", + }).Opts(opts("FeatureA")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("E"), []ConditionalEnum{ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "F", + }).Opts(opts("FeatureA")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("F"), []ConditionalEnum{ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "C", + }).Opts(opts("FeatureA")).ExpectValid() + + // Scenario 3: FeatureB enabled + // Valid values: A, B, C + st.Value(&ConditionalStruct{ + ConditionalEnumField: "D", + }).Opts(opts("FeatureB")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("D"), []ConditionalEnum{ConditionalA, ConditionalB, ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "E", + }).Opts(opts("FeatureB")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("E"), []ConditionalEnum{ConditionalA, ConditionalB, ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "F", + }).Opts(opts("FeatureB")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("F"), []ConditionalEnum{ConditionalA, ConditionalB, ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "A", + }).Opts(opts("FeatureB")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "B", + }).Opts(opts("FeatureB")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "C", + }).Opts(opts("FeatureB")).ExpectValid() + + // Scenario 4: FeatureA and FeatureB enabled + // Valid values: B, C + st.Value(&ConditionalStruct{ + ConditionalEnumField: "A", + }).Opts(opts("FeatureA", "FeatureB")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("A"), []ConditionalEnum{ConditionalB, ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "D", + }).Opts(opts("FeatureA", "FeatureB")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("D"), []ConditionalEnum{ConditionalB, ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "E", + }).Opts(opts("FeatureA", "FeatureB")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("E"), []ConditionalEnum{ConditionalB, ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "F", + }).Opts(opts("FeatureA", "FeatureB")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("F"), []ConditionalEnum{ConditionalB, ConditionalC}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "B", + }).Opts(opts("FeatureA", "FeatureB")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "C", + }).Opts(opts("FeatureA", "FeatureB")).ExpectValid() + + // Scenario 5: FeatureC and FeatureD enabled + // Valid values: A, C, D, E + st.Value(&ConditionalStruct{ + ConditionalEnumField: "B", + }).Opts(opts("FeatureC", "FeatureD")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("B"), []ConditionalEnum{ConditionalA, ConditionalC, ConditionalD, ConditionalE}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "F", + }).Opts(opts("FeatureC", "FeatureD")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("F"), []ConditionalEnum{ConditionalA, ConditionalC, ConditionalD, ConditionalE}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "A", + }).Opts(opts("FeatureC", "FeatureD")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "C", + }).Opts(opts("FeatureC", "FeatureD")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "D", + }).Opts(opts("FeatureC", "FeatureD")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "E", + }).Opts(opts("FeatureC", "FeatureD")).ExpectValid() + + // Scenario 6: FeatureB and FeatureC enabled + // Valid values: A, B, C, F + st.Value(&ConditionalStruct{ + ConditionalEnumField: "D", + }).Opts(opts("FeatureB", "FeatureC")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("D"), []ConditionalEnum{ConditionalA, ConditionalB, ConditionalC, ConditionalF}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "E", + }).Opts(opts("FeatureB", "FeatureC")).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.NotSupported(field.NewPath("conditionalEnumField"), ConditionalEnum("E"), []ConditionalEnum{ConditionalA, ConditionalB, ConditionalC, ConditionalF}), + }) + st.Value(&ConditionalStruct{ + ConditionalEnumField: "A", + }).Opts(opts("FeatureB", "FeatureC")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "B", + }).Opts(opts("FeatureB", "FeatureC")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "C", + }).Opts(opts("FeatureB", "FeatureC")).ExpectValid() + st.Value(&ConditionalStruct{ + ConditionalEnumField: "F", + }).Opts(opts("FeatureB", "FeatureC")).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/zz_generated.validations.go new file mode 100644 index 0000000000..65ae23c9a6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/options/zz_generated.validations.go @@ -0,0 +1,442 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package options + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ConditionalStruct + scheme.AddValidationFunc( + (*ConditionalStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ConditionalStruct( + ctx, op, nil, /* fldPath */ + obj.(*ConditionalStruct), + safe.Cast[*ConditionalStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var exclusionsForConditionalEnum = []validate.EnumExclusion[ConditionalEnum]{ + + { + Value: ConditionalA, + Option: "FeatureA", + ExcludeWhen: true, + }, + + { + Value: ConditionalB, + Option: "FeatureB", + ExcludeWhen: false, + }, + + { + Value: ConditionalD, + Option: "FeatureA", + ExcludeWhen: true, + }, + + { + Value: ConditionalD, + Option: "FeatureB", + ExcludeWhen: true, + }, + + { + Value: ConditionalE, + Option: "FeatureC", + ExcludeWhen: false, + }, + + { + Value: ConditionalE, + Option: "FeatureD", + ExcludeWhen: false, + }, + + { + Value: ConditionalF, + Option: "FeatureC", + ExcludeWhen: false, + }, + + { + Value: ConditionalF, + Option: "FeatureD", + ExcludeWhen: true, + }, +} +var symbolsForConditionalEnum = sets.New(ConditionalA, ConditionalB, ConditionalC, ConditionalD, ConditionalE, ConditionalF) + +// Validate_ConditionalEnum validates an instance of ConditionalEnum according +// to declarative validation rules in the API schema. +func Validate_ConditionalEnum( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ConditionalEnum) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForConditionalEnum, exclusionsForConditionalEnum); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_ConditionalStruct validates an instance of ConditionalStruct according +// to declarative validation rules in the API schema. +func Validate_ConditionalStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ConditionalStruct) (errs field.ErrorList) { + + // field ConditionalStruct.TypeMeta has no validation + + { // field ConditionalStruct.ConditionalEnumField + fn := func( + fldPath *field.Path, + obj, oldObj *ConditionalEnum, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ConditionalEnum(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ConditionalStruct) *ConditionalEnum { + return &oldObj.ConditionalEnumField + }) + errs = append(errs, fn(fldPath.Child("conditionalEnumField"), &obj.ConditionalEnumField, oldVal, oldObj != nil)...) + } + + { // field ConditionalStruct.ConditionalEnumPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *ConditionalEnum, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ConditionalEnum(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ConditionalStruct) *ConditionalEnum { + return oldObj.ConditionalEnumPtrField + }) + errs = append(errs, fn(fldPath.Child("conditionalEnumPtrField"), obj.ConditionalEnumPtrField, oldVal, oldObj != nil)...) + } + + return errs +} + +var symbolsForEnum0 = sets.New[Enum0]() + +// Validate_Enum0 validates an instance of Enum0 according +// to declarative validation rules in the API schema. +func Validate_Enum0( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Enum0) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum0, nil); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +var symbolsForEnum1 = sets.New(E1V1) + +// Validate_Enum1 validates an instance of Enum1 according +// to declarative validation rules in the API schema. +func Validate_Enum1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Enum1) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum1, nil); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +var symbolsForEnum2 = sets.New(E2V1, E2V2) + +// Validate_Enum2 validates an instance of Enum2 according +// to declarative validation rules in the API schema. +func Validate_Enum2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Enum2) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum2, nil); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +var symbolsForEnumWithExclude = sets.New(EnumWithExclude1) + +// Validate_EnumWithExclude validates an instance of EnumWithExclude according +// to declarative validation rules in the API schema. +func Validate_EnumWithExclude( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *EnumWithExclude) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnumWithExclude, nil); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Enum0Field + fn := func( + fldPath *field.Path, + obj, oldObj *Enum0, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum0(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum0 { + return &oldObj.Enum0Field + }) + errs = append(errs, fn(fldPath.Child("enum0Field"), &obj.Enum0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum0PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Enum0, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum0(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum0 { + return oldObj.Enum0PtrField + }) + errs = append(errs, fn(fldPath.Child("enum0PtrField"), obj.Enum0PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum1Field + fn := func( + fldPath *field.Path, + obj, oldObj *Enum1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum1 { + return &oldObj.Enum1Field + }) + errs = append(errs, fn(fldPath.Child("enum1Field"), &obj.Enum1Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum1PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Enum1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum1 { + return oldObj.Enum1PtrField + }) + errs = append(errs, fn(fldPath.Child("enum1PtrField"), obj.Enum1PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum2Field + fn := func( + fldPath *field.Path, + obj, oldObj *Enum2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum2 { + return &oldObj.Enum2Field + }) + errs = append(errs, fn(fldPath.Child("enum2Field"), &obj.Enum2Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum2PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Enum2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum2 { + return oldObj.Enum2PtrField + }) + errs = append(errs, fn(fldPath.Child("enum2PtrField"), obj.Enum2PtrField, oldVal, oldObj != nil)...) + } + + // field Struct.NotEnumField has no validation + // field Struct.NotEnumPtrField has no validation + + { // field Struct.EnumWithExcludeField + fn := func( + fldPath *field.Path, + obj, oldObj *EnumWithExclude, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_EnumWithExclude(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *EnumWithExclude { + return &oldObj.EnumWithExcludeField + }) + errs = append(errs, fn(fldPath.Child("enumWithExcludeField"), &obj.EnumWithExcludeField, oldVal, oldObj != nil)...) + } + + { // field Struct.EnumWithExcludePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *EnumWithExclude, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_EnumWithExclude(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *EnumWithExclude { + return oldObj.EnumWithExcludePtrField + }) + errs = append(errs, fn(fldPath.Child("enumWithExcludePtrField"), obj.EnumWithExcludePtrField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/zz_generated.validations.go new file mode 100644 index 0000000000..2cfeb7c5a0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/zz_generated.validations.go @@ -0,0 +1,247 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package enum + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var symbolsForEnum0 = sets.New[Enum0]() + +// Validate_Enum0 validates an instance of Enum0 according +// to declarative validation rules in the API schema. +func Validate_Enum0( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Enum0) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum0, nil); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +var symbolsForEnum1 = sets.New(E1V1) + +// Validate_Enum1 validates an instance of Enum1 according +// to declarative validation rules in the API schema. +func Validate_Enum1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Enum1) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum1, nil); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +var symbolsForEnum2 = sets.New(E2V1, E2V2) + +// Validate_Enum2 validates an instance of Enum2 according +// to declarative validation rules in the API schema. +func Validate_Enum2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Enum2) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum2, nil); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Enum0Field + fn := func( + fldPath *field.Path, + obj, oldObj *Enum0, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum0(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum0 { + return &oldObj.Enum0Field + }) + errs = append(errs, fn(fldPath.Child("enum0Field"), &obj.Enum0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum0PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Enum0, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum0(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum0 { + return oldObj.Enum0PtrField + }) + errs = append(errs, fn(fldPath.Child("enum0PtrField"), obj.Enum0PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum1Field + fn := func( + fldPath *field.Path, + obj, oldObj *Enum1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum1 { + return &oldObj.Enum1Field + }) + errs = append(errs, fn(fldPath.Child("enum1Field"), &obj.Enum1Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum1PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Enum1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum1 { + return oldObj.Enum1PtrField + }) + errs = append(errs, fn(fldPath.Child("enum1PtrField"), obj.Enum1PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum2Field + fn := func( + fldPath *field.Path, + obj, oldObj *Enum2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum2 { + return &oldObj.Enum2Field + }) + errs = append(errs, fn(fldPath.Child("enum2Field"), &obj.Enum2Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Enum2PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Enum2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum2 { + return oldObj.Enum2PtrField + }) + errs = append(errs, fn(fldPath.Child("enum2PtrField"), obj.Enum2PtrField, oldVal, oldObj != nil)...) + } + + // field Struct.NotEnumField has no validation + // field Struct.NotEnumPtrField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc.go new file mode 100644 index 0000000000..b859772d11 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc.go @@ -0,0 +1,91 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package forbidden + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:forbidden + StringField string `json:"stringField"` + + // +k8s:forbidden + StringPtrField *string `json:"stringPtrField"` + + // +k8s:forbidden + StringTypedefField StringType `json:"stringTypedefField"` + + // +k8s:forbidden + StringTypedefPtrField *StringType `json:"stringTypedefPtrField"` + + // +k8s:forbidden + IntField int `json:"intField"` + + // +k8s:forbidden + IntPtrField *int `json:"intPtrField"` + + // +k8s:forbidden + IntTypedefField IntType `json:"intTypedefField"` + + // +k8s:forbidden + IntTypedefPtrField *IntType `json:"intTypedefPtrField"` + + // +k8s:forbidden + BoolField bool `json:"boolField"` + + // +k8s:forbidden + FloatField float64 `json:"floatField"` + + // +k8s:forbidden + ByteField byte `json:"byteField"` + + // +k8s:forbidden + OtherStructPtrField *OtherStruct `json:"otherStructPtrField"` + + // +k8s:forbidden + SliceField []string `json:"sliceField"` + + // +k8s:forbidden + SliceTypedefField SliceType `json:"sliceTypedefField"` + + // +k8s:forbidden + ByteArrayField []byte `json:"byteArrayField"` + + // +k8s:forbidden + MapField map[string]string `json:"mapField"` + + // +k8s:forbidden + MapTypedefField MapType `json:"mapTypedefField"` +} + +type StringType string + +type IntType int + +type OtherStruct struct{} + +type SliceType []string + +type MapType map[string]string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc_test.go new file mode 100644 index 0000000000..dba35b5865 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/doc_test.go @@ -0,0 +1,91 @@ +/* +Copyright 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 forbidden + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values (nil slices/maps). + }).ExpectValid() + + st.Value(&Struct{ + // Explicit zero-values and empty slices/maps. + StringField: "", + StringPtrField: nil, + StringTypedefField: "", + StringTypedefPtrField: nil, + IntField: 0, + IntPtrField: nil, + IntTypedefField: 0, + IntTypedefPtrField: nil, + BoolField: false, + FloatField: 0.0, + ByteField: 0, + OtherStructPtrField: nil, + SliceField: []string{}, + SliceTypedefField: SliceType{}, + ByteArrayField: []byte{}, + MapField: map[string]string{}, + MapTypedefField: MapType{}, + }).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + StringPtrField: ptr.To("xyz"), + StringTypedefField: StringType("abc"), + StringTypedefPtrField: ptr.To(StringType("xyz")), + IntField: 123, + IntPtrField: ptr.To(456), + IntTypedefField: IntType(123), + IntTypedefPtrField: ptr.To(IntType(456)), + BoolField: true, + FloatField: 1.23, + ByteField: 'a', + OtherStructPtrField: &OtherStruct{}, + SliceField: []string{"a", "b"}, + SliceTypedefField: SliceType([]string{"a", "b"}), + ByteArrayField: []byte("abc"), + MapField: map[string]string{"a": "b", "c": "d"}, + MapTypedefField: MapType(map[string]string{"a": "b", "c": "d"}), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("stringField"), ""), + field.Forbidden(field.NewPath("stringPtrField"), ""), + field.Forbidden(field.NewPath("stringTypedefField"), ""), + field.Forbidden(field.NewPath("stringTypedefPtrField"), ""), + field.Forbidden(field.NewPath("intField"), ""), + field.Forbidden(field.NewPath("intPtrField"), ""), + field.Forbidden(field.NewPath("intTypedefField"), ""), + field.Forbidden(field.NewPath("intTypedefPtrField"), ""), + field.Forbidden(field.NewPath("boolField"), ""), + field.Forbidden(field.NewPath("floatField"), ""), + field.Forbidden(field.NewPath("byteField"), ""), + field.Forbidden(field.NewPath("otherStructPtrField"), ""), + field.Forbidden(field.NewPath("sliceField"), ""), + field.Forbidden(field.NewPath("sliceTypedefField"), ""), + field.Forbidden(field.NewPath("byteArrayField"), ""), + field.Forbidden(field.NewPath("mapField"), ""), + field.Forbidden(field.NewPath("mapTypedefField"), ""), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/zz_generated.validations.go new file mode 100644 index 0000000000..c17760fb4d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/forbidden/zz_generated.validations.go @@ -0,0 +1,612 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package forbidden + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.StringPtrField + }) + errs = append(errs, fn(fldPath.Child("stringPtrField"), obj.StringPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return &oldObj.StringTypedefField + }) + errs = append(errs, fn(fldPath.Child("stringTypedefField"), &obj.StringTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return oldObj.StringTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("stringTypedefPtrField"), obj.StringTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return &oldObj.IntTypedefField + }) + errs = append(errs, fn(fldPath.Child("intTypedefField"), &obj.IntTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return oldObj.IntTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("intTypedefPtrField"), obj.IntTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.BoolField + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return &oldObj.BoolField + }) + errs = append(errs, fn(fldPath.Child("boolField"), &obj.BoolField, oldVal, oldObj != nil)...) + } + + { // field Struct.FloatField + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *float64 { + return &oldObj.FloatField + }) + errs = append(errs, fn(fldPath.Child("floatField"), &obj.FloatField, oldVal, oldObj != nil)...) + } + + { // field Struct.ByteField + fn := func( + fldPath *field.Path, + obj, oldObj *byte, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *byte { + return &oldObj.ByteField + }) + errs = append(errs, fn(fldPath.Child("byteField"), &obj.ByteField, oldVal, oldObj != nil)...) + } + + { // field Struct.OtherStructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return oldObj.OtherStructPtrField + }) + errs = append(errs, fn(fldPath.Child("otherStructPtrField"), obj.OtherStructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj SliceType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) SliceType { + return oldObj.SliceTypedefField + }) + errs = append(errs, fn(fldPath.Child("sliceTypedefField"), obj.SliceTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.ByteArrayField + fn := func( + fldPath *field.Path, + obj, oldObj []byte, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []byte { + return oldObj.ByteArrayField + }) + errs = append(errs, fn(fldPath.Child("byteArrayField"), obj.ByteArrayField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj MapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapType { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc.go new file mode 100644 index 0000000000..feb9464f90 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package k8sextendedresourcename + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// MyType is a struct that contains a field with the extended-resource-name format. +// +k8s:validation:Required +type MyType struct { + TypeMeta int + // +k8s:optional + // +k8s:format=k8s-extended-resource-name + NameField string `json:"nameField"` + // +k8s:optional + // +k8s:format=k8s-extended-resource-name + NamePtrField *string `json:"namePtrField"` + // Note: no validation here + NameTypedefField NameStringType `json:"nameTypedefField"` +} + +// +k8s:format=k8s-extended-resource-name +type NameStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc_test.go new file mode 100644 index 0000000000..5fc2b07b3d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/doc_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2025 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 k8sextendedresourcename + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestK8sExtendedResourceName(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&MyType{ + NameField: "example.com/my-resource", + NamePtrField: ptr.To("my-domain.org/foo"), + NameTypedefField: "example.com/another-resource", + }).ExpectValid() + + st.Value(&MyType{ + NameField: "example.com/my_resource", + NamePtrField: ptr.To("example.com/My-Resource"), + NameTypedefField: "example.com/my.resource", + }).ExpectValid() + + invalidStruct := &MyType{ + NameField: "kubernetes.io/my-resource", + NamePtrField: ptr.To("requests.example.com/my-resource"), + NameTypedefField: "my-resource", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("nameField"), invalidStruct.NameField, "a qualified name must not be a reserved name").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("namePtrField"), *invalidStruct.NamePtrField, "a qualified name must not have a reserved prefix").WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("nameTypedefField"), invalidStruct.NameTypedefField, "a qualified name must be a valid domain prefix and a name separated by a slash").WithOrigin("format=k8s-extended-resource-name"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() + + const commonDetail = "a valid extended resource name must consist of a domain name prefix and a path segment separated by a slash, where the path segment consists of alphanumeric characters, '-', and must start and end with an alphanumeric character, and the domain name prefix is a valid DNS subdomain name, with the exception that 'requests' is a valid domain name prefix" + invalidStruct = &MyType{ + NameField: "example.com/my-resource-", + NamePtrField: ptr.To("example.com/-my-resource"), + NameTypedefField: "example.com/", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("nameField"), invalidStruct.NameField, commonDetail).WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("namePtrField"), *invalidStruct.NamePtrField, commonDetail).WithOrigin("format=k8s-extended-resource-name"), + field.Invalid(field.NewPath("nameTypedefField"), invalidStruct.NameTypedefField, commonDetail).WithOrigin("format=k8s-extended-resource-name"), + }) + + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/zz_generated.validations.go new file mode 100644 index 0000000000..24f4676422 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-extended-resource-name/zz_generated.validations.go @@ -0,0 +1,164 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package k8sextendedresourcename + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type MyType + scheme.AddValidationFunc( + (*MyType)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyType( + ctx, op, nil, /* fldPath */ + obj.(*MyType), + safe.Cast[*MyType](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_MyType validates an instance of MyType according +// to declarative validation rules in the API schema. +func Validate_MyType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MyType) (errs field.ErrorList) { + + // field MyType.TypeMeta has no validation + + { // field MyType.NameField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyType) *string { + return &oldObj.NameField + }) + errs = append(errs, fn(fldPath.Child("nameField"), &obj.NameField, oldVal, oldObj != nil)...) + } + + { // field MyType.NamePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyType) *string { + return oldObj.NamePtrField + }) + errs = append(errs, fn(fldPath.Child("namePtrField"), obj.NamePtrField, oldVal, oldObj != nil)...) + } + + { // field MyType.NameTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *NameStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_NameStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyType) *NameStringType { + return &oldObj.NameTypedefField + }) + errs = append(errs, fn(fldPath.Child("nameTypedefField"), &obj.NameTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_NameStringType validates an instance of NameStringType according +// to declarative validation rules in the API schema. +func Validate_NameStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NameStringType) (errs field.ErrorList) { + + if e := validate.ExtendedResourceName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc.go new file mode 100644 index 0000000000..ab98664e30 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package format is the internal version of the API. +// +k8s:validation:internal +// +k8s:validation-gen-nolint +package format + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:format=k8s-label-key + LabelKeyField string `json:"labelKeyField"` + + // +k8s:format=k8s-label-key + LabelKeyPtrField *string `json:"labelKeyPtrField"` + + // Note: no validation here + LabelKeyTypedefField LabelKeyStringType `json:"labelKeyTypedefField"` +} + +// +k8s:format=k8s-label-key +type LabelKeyStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc_test.go new file mode 100644 index 0000000000..a5e2012c3d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/doc_test.go @@ -0,0 +1,81 @@ +/* +Copyright 2025 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 format + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + validCases := []string{ + "simple", + "now-with-dashes", + "1-starts-with-num", + "1234", + "simple.com/simple", + "now-with-dashes.com/simple", + "now.with.dots.com/simple", + "now-with.dashes-and.dots.com/simple", + "1-num.2-num.com/3-num", + "1234.com/5678", + "1.2.3.4/5678", + "Uppercase_Is_OK_123", + "example.com/Uppercase_Is_OK_123", + "requests.storage-foo", + strings.Repeat("a", 63), + strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63), + } + + for _, s := range validCases { + st.Value(&Struct{ + LabelKeyField: s, + LabelKeyPtrField: ptr.To(s), + LabelKeyTypedefField: LabelKeyStringType(s), + }).ExpectValid() + } + + invalidCases := []string{ + "nospecialchars%^=@", + "cantendwithadash-", + "-cantstartwithadash-", + "only/one/slash", + "Example.com/abc", + "example_com/abc", + "example.com/", + "/simple", + strings.Repeat("a", 64), + strings.Repeat("a", 254) + "/abc", + } + + for _, s := range invalidCases { + st.Value(&Struct{ + LabelKeyField: s, + LabelKeyPtrField: ptr.To(s), + LabelKeyTypedefField: LabelKeyStringType(s), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("labelKeyField"), nil, "").WithOrigin("format=k8s-label-key"), + field.Invalid(field.NewPath("labelKeyPtrField"), nil, "").WithOrigin("format=k8s-label-key"), + field.Invalid(field.NewPath("labelKeyTypedefField"), nil, "").WithOrigin("format=k8s-label-key"), + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/zz_generated.validations.go new file mode 100644 index 0000000000..5f117fd460 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-key/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package format + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_LabelKeyStringType validates an instance of LabelKeyStringType according +// to declarative validation rules in the API schema. +func Validate_LabelKeyStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *LabelKeyStringType) (errs field.ErrorList) { + + if e := validate.LabelKey(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.LabelKeyField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.LabelKey(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.LabelKeyField + }) + errs = append(errs, fn(fldPath.Child("labelKeyField"), &obj.LabelKeyField, oldVal, oldObj != nil)...) + } + + { // field Struct.LabelKeyPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.LabelKey(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.LabelKeyPtrField + }) + errs = append(errs, fn(fldPath.Child("labelKeyPtrField"), obj.LabelKeyPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.LabelKeyTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *LabelKeyStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_LabelKeyStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *LabelKeyStringType { + return &oldObj.LabelKeyTypedefField + }) + errs = append(errs, fn(fldPath.Child("labelKeyTypedefField"), &obj.LabelKeyTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc.go new file mode 100644 index 0000000000..23644363c1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc.go @@ -0,0 +1,42 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package format + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:format=k8s-label-value + LabelValueField string `json:"labelValueField"` + + // +k8s:format=k8s-label-value + LabelValuePtrField *string `json:"labelValuePtrField"` + + // Note: no validation here + LabelValueTypedefField LabelValueStringType `json:"labelValueTypedefField"` +} + +// +k8s:format=k8s-label-value +type LabelValueStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc_test.go new file mode 100644 index 0000000000..ec92bc7f84 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/doc_test.go @@ -0,0 +1,85 @@ +/* +Copyright 2025 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 format + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + validCases := []struct { + name string + value string + }{ + {"valid value", "valid-value"}, + {"valid value with dots", "valid.value"}, + {"valid value with underscores", "valid_value"}, + {"valid single character value", "a"}, + {"valid value with numbers", "123-abc"}, + {"valid uppercase characters", "Valid-Value"}, + {"valid: max length", "a" + strings.Repeat("b", 61) + "c"}, // 63 characters + {"valid: empty string", ""}, + } + + for _, tc := range validCases { + t.Run(tc.name, func(t *testing.T) { + st.Value(&Struct{ + LabelValueField: tc.value, + LabelValuePtrField: ptr.To(tc.value), + LabelValueTypedefField: LabelValueStringType(tc.value), + }).ExpectValid() + }) + } + + invalidCases := []struct { + name string + value string + }{ + {"invalid: starts with dash", "-invalid-value"}, + {"invalid: ends with dash", "invalid-value-"}, + {"invalid: starts with dot", ".invalid.value"}, + {"invalid: ends with dot", "invalid.value."}, + {"invalid: starts with underscore", "_invalid_value"}, + {"invalid: ends with underscore", "invalid_value_"}, + {"invalid: contains special characters", "invalid@value"}, + {"invalid: contains spaces", "Not a LabelValue"}, + {"invalid: too long", "a" + strings.Repeat("b", 62) + "c"}, // 64 characters + } + + for _, tc := range invalidCases { + t.Run(tc.name, func(t *testing.T) { + invalidStruct := &Struct{ + LabelValueField: tc.value, + LabelValuePtrField: ptr.To(tc.value), + LabelValueTypedefField: LabelValueStringType(tc.value), + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("labelValueField"), nil, "").WithOrigin("format=k8s-label-value"), + field.Invalid(field.NewPath("labelValuePtrField"), nil, "").WithOrigin("format=k8s-label-value"), + field.Invalid(field.NewPath("labelValueTypedefField"), nil, "").WithOrigin("format=k8s-label-value"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/zz_generated.validations.go new file mode 100644 index 0000000000..d877b74f22 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-label-value/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package format + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_LabelValueStringType validates an instance of LabelValueStringType according +// to declarative validation rules in the API schema. +func Validate_LabelValueStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *LabelValueStringType) (errs field.ErrorList) { + + if e := validate.LabelValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.LabelValueField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.LabelValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.LabelValueField + }) + errs = append(errs, fn(fldPath.Child("labelValueField"), &obj.LabelValueField, oldVal, oldObj != nil)...) + } + + { // field Struct.LabelValuePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.LabelValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.LabelValuePtrField + }) + errs = append(errs, fn(fldPath.Child("labelValuePtrField"), obj.LabelValuePtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.LabelValueTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *LabelValueStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_LabelValueStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *LabelValueStringType { + return &oldObj.LabelValueTypedefField + }) + errs = append(errs, fn(fldPath.Child("labelValueTypedefField"), &obj.LabelValueTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc.go new file mode 100644 index 0000000000..8100a756d9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc.go @@ -0,0 +1,42 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package format + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:format=k8s-long-name-caseless + LongNameField string `json:"longNameField"` + + // +k8s:format=k8s-long-name-caseless + LongNamePtrField *string `json:"longNamePtrField"` + + // Note: no validation here + LongNameTypedefField LongNameStringType `json:"longNameTypedefField"` +} + +// +k8s:format=k8s-long-name-caseless +type LongNameStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc_test.go new file mode 100644 index 0000000000..eafa6eaa81 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/doc_test.go @@ -0,0 +1,72 @@ +/* +Copyright 2025 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 format + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestCaseless(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + LongNameField: "foo.bar", + LongNamePtrField: ptr.To("foo.bar"), + LongNameTypedefField: "foo.bar", + }).ExpectValid() + + st.Value(&Struct{ + LongNameField: "1.2.3.4", + LongNamePtrField: ptr.To("1.2.3.4"), + LongNameTypedefField: "1.2.3.4", + }).ExpectValid() + + st.Value(&Struct{ + LongNameField: "Foo.Bar", + LongNamePtrField: ptr.To("Foo.Bar"), + LongNameTypedefField: "Foo.Bar", + }).ExpectValid() + + invalidStruct := &Struct{ + LongNameField: "", + LongNamePtrField: ptr.To(""), + LongNameTypedefField: "", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("longNameField"), nil, "").WithOrigin("format=k8s-long-name-caseless"), + field.Invalid(field.NewPath("longNamePtrField"), nil, "").WithOrigin("format=k8s-long-name-caseless"), + field.Invalid(field.NewPath("longNameTypedefField"), nil, "").WithOrigin("format=k8s-long-name-caseless"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() + + invalidStruct = &Struct{ + LongNameField: "Not a LongName", + LongNamePtrField: ptr.To("Not a LongName"), + LongNameTypedefField: "Not a LongName", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("longNameField"), nil, "").WithOrigin("format=k8s-long-name-caseless"), + field.Invalid(field.NewPath("longNamePtrField"), nil, "").WithOrigin("format=k8s-long-name-caseless"), + field.Invalid(field.NewPath("longNameTypedefField"), nil, "").WithOrigin("format=k8s-long-name-caseless"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/zz_generated.validations.go new file mode 100644 index 0000000000..07cbe34e2d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name-caseless/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package format + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_LongNameStringType validates an instance of LongNameStringType according +// to declarative validation rules in the API schema. +func Validate_LongNameStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *LongNameStringType) (errs field.ErrorList) { + + if e := validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.LongNameField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.LongNameField + }) + errs = append(errs, fn(fldPath.Child("longNameField"), &obj.LongNameField, oldVal, oldObj != nil)...) + } + + { // field Struct.LongNamePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.LongNameCaseless(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.LongNamePtrField + }) + errs = append(errs, fn(fldPath.Child("longNamePtrField"), obj.LongNamePtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.LongNameTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *LongNameStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_LongNameStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *LongNameStringType { + return &oldObj.LongNameTypedefField + }) + errs = append(errs, fn(fldPath.Child("longNameTypedefField"), &obj.LongNameTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc.go new file mode 100644 index 0000000000..0c8fe2f74f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc.go @@ -0,0 +1,42 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package format + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:format=k8s-long-name + LongNameField string `json:"longNameField"` + + // +k8s:format=k8s-long-name + LongNamePtrField *string `json:"longNamePtrField"` + + // Note: no validation here + LongNameTypedefField LongNameStringType `json:"longNameTypedefField"` +} + +// +k8s:format=k8s-long-name +type LongNameStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc_test.go new file mode 100644 index 0000000000..b0edb4da8d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/doc_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2025 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 format + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + LongNameField: "foo.bar", + LongNamePtrField: ptr.To("foo.bar"), + LongNameTypedefField: "foo.bar", + }).ExpectValid() + + st.Value(&Struct{ + LongNameField: "1.2.3.4", + LongNamePtrField: ptr.To("1.2.3.4"), + LongNameTypedefField: "1.2.3.4", + }).ExpectValid() + + invalidStruct := &Struct{ + LongNameField: "", + LongNamePtrField: ptr.To(""), + LongNameTypedefField: "", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("longNameField"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("longNamePtrField"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("longNameTypedefField"), nil, "").WithOrigin("format=k8s-long-name"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() + + invalidStruct = &Struct{ + LongNameField: "Not a LongName", + LongNamePtrField: ptr.To("Not a LongName"), + LongNameTypedefField: "Not a LongName", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("longNameField"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("longNamePtrField"), nil, "").WithOrigin("format=k8s-long-name"), + field.Invalid(field.NewPath("longNameTypedefField"), nil, "").WithOrigin("format=k8s-long-name"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/zz_generated.validations.go new file mode 100644 index 0000000000..d7171e86a8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-long-name/k8s-long-name/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package format + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_LongNameStringType validates an instance of LongNameStringType according +// to declarative validation rules in the API schema. +func Validate_LongNameStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *LongNameStringType) (errs field.ErrorList) { + + if e := validate.LongName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.LongNameField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.LongName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.LongNameField + }) + errs = append(errs, fn(fldPath.Child("longNameField"), &obj.LongNameField, oldVal, oldObj != nil)...) + } + + { // field Struct.LongNamePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.LongName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.LongNamePtrField + }) + errs = append(errs, fn(fldPath.Child("longNamePtrField"), obj.LongNamePtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.LongNameTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *LongNameStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_LongNameStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *LongNameStringType { + return &oldObj.LongNameTypedefField + }) + errs = append(errs, fn(fldPath.Child("longNameTypedefField"), &obj.LongNameTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/doc.go new file mode 100644 index 0000000000..3b4e2609f8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package format is the internal version of the API. +// +k8s:validation:internal +// +k8s:validation-gen-nolint +package format + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:format=k8s-prefixed-label-key + PrefixedLabelKeyField string `json:"prefixedLabelKeyField"` + + // +k8s:format=k8s-prefixed-label-key + PrefixedLabelKeyPtrField *string `json:"prefixedLabelKeyPtrField"` + + // Note: no validation here + PrefixedLabelKeyTypedefField PrefixedLabelKeyStringType `json:"prefixedLabelKeyTypedefField"` +} + +// +k8s:format=k8s-prefixed-label-key +type PrefixedLabelKeyStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/doc_test.go new file mode 100644 index 0000000000..85d9322f6b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/doc_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2025 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 format + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + validCases := []string{ + "simple.com/simple", + "now-with-dashes.com/simple", + "now.with.dots.com/simple", + "now-with.dashes-and.dots.com/simple", + "1-num.2-num.com/3-num", + "1234.com/5678", + "1.2.3.4/5678", + "example.com/Uppercase_Is_OK_123", + strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63), + } + + for _, s := range validCases { + st.Value(&Struct{ + PrefixedLabelKeyField: s, + PrefixedLabelKeyPtrField: new(s), + PrefixedLabelKeyTypedefField: PrefixedLabelKeyStringType(s), + }).ExpectValid() + } + + invalidCases := []string{ + "simple", + "now-with-dashes", + "1-starts-with-num", + "1234", + "Uppercase_Is_OK_123", + "requests.storage-foo", + strings.Repeat("a", 63), + "nospecialchars%^=@", + "cantendwithadash-", + "-cantstartwithadash-", + "only/one/slash", + "Example.com/abc", + "example_com/abc", + "example.com/", + "/simple", + strings.Repeat("a", 64), + strings.Repeat("a", 254) + "/abc", + } + + for _, s := range invalidCases { + st.Value(&Struct{ + PrefixedLabelKeyField: s, + PrefixedLabelKeyPtrField: new(s), + PrefixedLabelKeyTypedefField: PrefixedLabelKeyStringType(s), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("prefixedLabelKeyField"), nil, "").WithOrigin("format=k8s-prefixed-label-key"), + field.Invalid(field.NewPath("prefixedLabelKeyPtrField"), nil, "").WithOrigin("format=k8s-prefixed-label-key"), + field.Invalid(field.NewPath("prefixedLabelKeyTypedefField"), nil, "").WithOrigin("format=k8s-prefixed-label-key"), + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/zz_generated.validations.go new file mode 100644 index 0000000000..77b7255fa4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-prefixed-label-key/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package format + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_PrefixedLabelKeyStringType validates an instance of PrefixedLabelKeyStringType according +// to declarative validation rules in the API schema. +func Validate_PrefixedLabelKeyStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *PrefixedLabelKeyStringType) (errs field.ErrorList) { + + if e := validate.PrefixedLabelKey(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.PrefixedLabelKeyField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.PrefixedLabelKey(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.PrefixedLabelKeyField + }) + errs = append(errs, fn(fldPath.Child("prefixedLabelKeyField"), &obj.PrefixedLabelKeyField, oldVal, oldObj != nil)...) + } + + { // field Struct.PrefixedLabelKeyPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.PrefixedLabelKey(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.PrefixedLabelKeyPtrField + }) + errs = append(errs, fn(fldPath.Child("prefixedLabelKeyPtrField"), obj.PrefixedLabelKeyPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.PrefixedLabelKeyTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *PrefixedLabelKeyStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_PrefixedLabelKeyStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *PrefixedLabelKeyStringType { + return &oldObj.PrefixedLabelKeyTypedefField + }) + errs = append(errs, fn(fldPath.Child("prefixedLabelKeyTypedefField"), &obj.PrefixedLabelKeyTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc.go new file mode 100644 index 0000000000..4297d4a018 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc.go @@ -0,0 +1,42 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package fullyqualifiedname + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:format=k8s-resource-fully-qualified-name + FullyQualifiedNameField string `json:"fullyQualifiedNameField"` + + // +k8s:format=k8s-resource-fully-qualified-name + FullyQualifiedNamePtrField *string `json:"fullyQualifiedNamePtrField"` + + // Note: no validation here + FullyQualifiedNameTypedefField FullyQualifiedNameStringType `json:"fullyQualifiedNameTypedefField"` +} + +// +k8s:format=k8s-resource-fully-qualified-name +type FullyQualifiedNameStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc_test.go new file mode 100644 index 0000000000..6d4768b74f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/doc_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2025 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 fullyqualifiedname + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestFullyQualifiedName(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + FullyQualifiedNameField: "my-prefix/my_name", + FullyQualifiedNamePtrField: ptr.To("my-prefix/my_name"), + FullyQualifiedNameTypedefField: "my-prefix/my_name", + }).ExpectValid() + + invalidStruct := &Struct{ + FullyQualifiedNameField: "my_name", + FullyQualifiedNamePtrField: ptr.To(""), + FullyQualifiedNameTypedefField: "my-prefix/", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByOrigin().ByField(), field.ErrorList{ + field.Invalid(field.NewPath("fullyQualifiedNameField"), "my_name", "a fully qualified name must be a domain and a name separated by a slash").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("fullyQualifiedNamePtrField"), "", "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'").WithOrigin("format=k8s-resource-fully-qualified-name"), + field.Invalid(field.NewPath("fullyQualifiedNameTypedefField"), "my-prefix/", "name must not be empty").WithOrigin("format=k8s-resource-fully-qualified-name"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/zz_generated.validations.go new file mode 100644 index 0000000000..3ac7fe8dde --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-fully-qualified-name/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package fullyqualifiedname + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_FullyQualifiedNameStringType validates an instance of FullyQualifiedNameStringType according +// to declarative validation rules in the API schema. +func Validate_FullyQualifiedNameStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *FullyQualifiedNameStringType) (errs field.ErrorList) { + + if e := validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.FullyQualifiedNameField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.FullyQualifiedNameField + }) + errs = append(errs, fn(fldPath.Child("fullyQualifiedNameField"), &obj.FullyQualifiedNameField, oldVal, oldObj != nil)...) + } + + { // field Struct.FullyQualifiedNamePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ResourceFullyQualifiedName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.FullyQualifiedNamePtrField + }) + errs = append(errs, fn(fldPath.Child("fullyQualifiedNamePtrField"), obj.FullyQualifiedNamePtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.FullyQualifiedNameTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *FullyQualifiedNameStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_FullyQualifiedNameStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *FullyQualifiedNameStringType { + return &oldObj.FullyQualifiedNameTypedefField + }) + errs = append(errs, fn(fldPath.Child("fullyQualifiedNameTypedefField"), &obj.FullyQualifiedNameTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc.go new file mode 100644 index 0000000000..0513cda543 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package format + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:format=k8s-resource-pool-name + ResourcePoolNameField string `json:"resourcePoolNameField"` + + // +k8s:format=k8s-resource-pool-name + ResourcePoolNamePtrField *string `json:"resourcePoolNamePtrField"` + + // Note: no validation here + ResourcePoolNameTypedefField ResourcePoolNameStringType `json:"resourcePoolNameTypedefField"` +} + +// +k8s:format=k8s-resource-pool-name +type ResourcePoolNameStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc_test.go new file mode 100644 index 0000000000..eeab15e530 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/doc_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2024 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. +*/ + +// +output_tests +package format + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + ResourcePoolNameField: "foo.bar", + ResourcePoolNamePtrField: ptr.To("foo.bar"), + ResourcePoolNameTypedefField: "foo.bar", + }).ExpectValid() + + st.Value(&Struct{ + ResourcePoolNameField: "1.2.3.4", + ResourcePoolNamePtrField: ptr.To("1.2.3.4"), + ResourcePoolNameTypedefField: "1.2.3.4", + }).ExpectValid() + + invalidStruct := &Struct{ + ResourcePoolNameField: "", + ResourcePoolNamePtrField: ptr.To(""), + ResourcePoolNameTypedefField: "", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("resourcePoolNameField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(field.NewPath("resourcePoolNamePtrField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(field.NewPath("resourcePoolNameTypedefField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() + + invalidStruct = &Struct{ + ResourcePoolNameField: "Not a ResourcePoolName", + ResourcePoolNamePtrField: ptr.To("Not a ResourcePoolName"), + ResourcePoolNameTypedefField: "Not a ResourcePoolName", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("resourcePoolNameField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(field.NewPath("resourcePoolNamePtrField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(field.NewPath("resourcePoolNameTypedefField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() + + invalidStruct = &Struct{ + ResourcePoolNameField: "a..b", + ResourcePoolNamePtrField: ptr.To("a..b"), + ResourcePoolNameTypedefField: "a..b", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("resourcePoolNameField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(field.NewPath("resourcePoolNamePtrField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + field.Invalid(field.NewPath("resourcePoolNameTypedefField"), nil, "").WithOrigin("format=k8s-resource-pool-name"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/zz_generated.validations.go new file mode 100644 index 0000000000..4e210e06ae --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-resource-pool-name/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package format + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ResourcePoolNameStringType validates an instance of ResourcePoolNameStringType according +// to declarative validation rules in the API schema. +func Validate_ResourcePoolNameStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ResourcePoolNameStringType) (errs field.ErrorList) { + + if e := validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ResourcePoolNameField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.ResourcePoolNameField + }) + errs = append(errs, fn(fldPath.Child("resourcePoolNameField"), &obj.ResourcePoolNameField, oldVal, oldObj != nil)...) + } + + { // field Struct.ResourcePoolNamePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ResourcePoolName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.ResourcePoolNamePtrField + }) + errs = append(errs, fn(fldPath.Child("resourcePoolNamePtrField"), obj.ResourcePoolNamePtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.ResourcePoolNameTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *ResourcePoolNameStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ResourcePoolNameStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ResourcePoolNameStringType { + return &oldObj.ResourcePoolNameTypedefField + }) + errs = append(errs, fn(fldPath.Child("resourcePoolNameTypedefField"), &obj.ResourcePoolNameTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc.go new file mode 100644 index 0000000000..5ef598fff6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc.go @@ -0,0 +1,42 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package format + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:format=k8s-short-name + ShortNameField string `json:"shortNameField"` + + // +k8s:format=k8s-short-name + ShortNamePtrField *string `json:"shortNamePtrField"` + + // Note: no validation here + ShortNameTypedefField ShortNameStringType `json:"shortNameTypedefField"` +} + +// +k8s:format=k8s-short-name +type ShortNameStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc_test.go new file mode 100644 index 0000000000..bd37ba61f4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/doc_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2025 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 format + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + ShortNameField: "foo-bar", + ShortNamePtrField: ptr.To("foo-bar"), + ShortNameTypedefField: "foo-bar", + }).ExpectValid() + + st.Value(&Struct{ + ShortNameField: "1234", + ShortNamePtrField: ptr.To("1234"), + ShortNameTypedefField: "1234", + }).ExpectValid() + + st.Value(&Struct{ + ShortNameField: "", + ShortNamePtrField: ptr.To(""), + ShortNameTypedefField: "", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("shortNameField"), nil, "").WithOrigin("format=k8s-short-name"), + field.Invalid(field.NewPath("shortNamePtrField"), nil, "").WithOrigin("format=k8s-short-name"), + field.Invalid(field.NewPath("shortNameTypedefField"), nil, "").WithOrigin("format=k8s-short-name"), + }) + + st.Value(&Struct{ + ShortNameField: "Not a DNS label", + ShortNamePtrField: ptr.To("Not a DNS label"), + ShortNameTypedefField: "Not a DNS label", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("shortNameField"), nil, "").WithOrigin("format=k8s-short-name"), + field.Invalid(field.NewPath("shortNamePtrField"), nil, "").WithOrigin("format=k8s-short-name"), + field.Invalid(field.NewPath("shortNameTypedefField"), nil, "").WithOrigin("format=k8s-short-name"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/zz_generated.validations.go new file mode 100644 index 0000000000..e6053ed46d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-short-name/k8s-short-name/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package format + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ShortNameStringType validates an instance of ShortNameStringType according +// to declarative validation rules in the API schema. +func Validate_ShortNameStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ShortNameStringType) (errs field.ErrorList) { + + if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ShortNameField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.ShortNameField + }) + errs = append(errs, fn(fldPath.Child("shortNameField"), &obj.ShortNameField, oldVal, oldObj != nil)...) + } + + { // field Struct.ShortNamePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.ShortNamePtrField + }) + errs = append(errs, fn(fldPath.Child("shortNamePtrField"), obj.ShortNamePtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.ShortNameTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *ShortNameStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ShortNameStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ShortNameStringType { + return &oldObj.ShortNameTypedefField + }) + errs = append(errs, fn(fldPath.Child("shortNameTypedefField"), &obj.ShortNameTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc.go new file mode 100644 index 0000000000..f3eb4ff709 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package k8suuid + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// MyType is a struct that contains a field with the uuid format. +// +k8s:validation:Required +type MyType struct { + TypeMeta int + // +k8s:optional + // +k8s:format=k8s-uuid + UUIDField string `json:"uuidField"` + // +k8s:optional + // +k8s:format=k8s-uuid + UUIDPtrField *string `json:"uuidPtrField"` + // Note: no validation here + UUIDTypedefField UUIDStringType `json:"uuidTypedefField"` +} + +// +k8s:format=k8s-uuid +type UUIDStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc_test.go new file mode 100644 index 0000000000..095439575c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/doc_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2025 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 k8suuid + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestK8sUUID(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&MyType{ + UUIDField: "123e4567-e89b-12d3-a456-426614174000", + UUIDPtrField: ptr.To("123e4567-e89b-12d3-a456-426614174000"), + UUIDTypedefField: "123e4567-e89b-12d3-a456-426614174000", + }).ExpectValid() + + invalidStruct := &MyType{ + UUIDField: "123E4567-E89B-12D3-A456-426614174000", + UUIDPtrField: ptr.To("123E4567-E89B-12D3-A456-426614174000"), + UUIDTypedefField: "123E4567-E89B-12D3-A456-426614174000", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("uuidField"), nil, "").WithOrigin("format=k8s-uuid"), + field.Invalid(field.NewPath("uuidPtrField"), nil, "").WithOrigin("format=k8s-uuid"), + field.Invalid(field.NewPath("uuidTypedefField"), nil, "").WithOrigin("format=k8s-uuid"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() + + invalidStruct = &MyType{ + UUIDField: "not-a-uuid", + UUIDPtrField: ptr.To("not-a-uuid"), + UUIDTypedefField: "not-a-uuid", + } + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("uuidField"), nil, "").WithOrigin("format=k8s-uuid"), + field.Invalid(field.NewPath("uuidPtrField"), nil, "").WithOrigin("format=k8s-uuid"), + field.Invalid(field.NewPath("uuidTypedefField"), nil, "").WithOrigin("format=k8s-uuid"), + }) + // Test validation ratcheting + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/zz_generated.validations.go new file mode 100644 index 0000000000..f0b45cba9c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/format/k8s-uuid/zz_generated.validations.go @@ -0,0 +1,164 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package k8suuid + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type MyType + scheme.AddValidationFunc( + (*MyType)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyType( + ctx, op, nil, /* fldPath */ + obj.(*MyType), + safe.Cast[*MyType](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_MyType validates an instance of MyType according +// to declarative validation rules in the API schema. +func Validate_MyType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MyType) (errs field.ErrorList) { + + // field MyType.TypeMeta has no validation + + { // field MyType.UUIDField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyType) *string { + return &oldObj.UUIDField + }) + errs = append(errs, fn(fldPath.Child("uuidField"), &obj.UUIDField, oldVal, oldObj != nil)...) + } + + { // field MyType.UUIDPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyType) *string { + return oldObj.UUIDPtrField + }) + errs = append(errs, fn(fldPath.Child("uuidPtrField"), obj.UUIDPtrField, oldVal, oldObj != nil)...) + } + + { // field MyType.UUIDTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *UUIDStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_UUIDStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyType) *UUIDStringType { + return &oldObj.UUIDTypedefField + }) + errs = append(errs, fn(fldPath.Child("uuidTypedefField"), &obj.UUIDTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_UUIDStringType validates an instance of UUIDStringType according +// to declarative validation rules in the API schema. +func Validate_UUIDStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *UUIDStringType) (errs field.ErrorList) { + + if e := validate.UUID(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc.go new file mode 100644 index 0000000000..1f6a236f53 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package immutable + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:immutable + StringField string `json:"stringField"` + + // +k8s:immutable + StringPtrField *string `json:"stringPtrField"` + + // +k8s:immutable + StructField ComparableStruct `json:"structField"` + + // +k8s:immutable + StructPtrField *ComparableStruct `json:"structPtrField"` + + // +k8s:immutable + NonComparableStructField NonComparableStruct `json:"noncomparableStructField"` + + // +k8s:immutable + NonComparableStructPtrField *NonComparableStruct `json:"noncomparableStructPtrField"` + + // +k8s:immutable + SliceField []string `json:"sliceField"` + + // +k8s:immutable + MapField map[string]string `json:"mapField"` + + ImmutableField ImmutableType `json:"immutableField"` + + ImmutablePtrField *ImmutableType `json:"immutablePtrField"` +} + +type ComparableStruct struct { + StringField string `json:"stringField"` + StringPtrField *string `json:"stringPtrField"` +} + +type NonComparableStruct struct { + SliceField []string `json:"sliceField"` +} + +// +k8s:immutable +type ImmutableType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc_test.go new file mode 100644 index 0000000000..8134fe35d2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/doc_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2024 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 immutable + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + structA := Struct{ + StringField: "aaa", + StringPtrField: ptr.To("aaa"), + StructField: ComparableStruct{"bbb", ptr.To("BBB")}, + StructPtrField: ptr.To(ComparableStruct{"bbb", ptr.To("BBB")}), + NonComparableStructField: NonComparableStruct{[]string{"ccc"}}, + NonComparableStructPtrField: ptr.To(NonComparableStruct{[]string{"ccc"}}), + SliceField: []string{"ddd"}, + MapField: map[string]string{"eee": "eee"}, + ImmutableField: "fff", + ImmutablePtrField: ptr.To(ImmutableType("fff")), + } + + structA2 := structA // dup of A but with different pointer values + structA2.StringPtrField = ptr.To(*structA2.StringPtrField) + structA2.StructField.StringPtrField = ptr.To("BBB") + structA2.StructPtrField = ptr.To(*structA2.StructPtrField) + structA2.StructPtrField.StringPtrField = ptr.To("BBB") + structA2.NonComparableStructPtrField = ptr.To(*structA2.NonComparableStructPtrField) + structA2.ImmutablePtrField = ptr.To(*structA2.ImmutablePtrField) + + structB := Struct{ + StringField: "uuu", + StringPtrField: ptr.To("uuu"), + StructField: ComparableStruct{"vvv", ptr.To("VVV")}, + StructPtrField: ptr.To(ComparableStruct{"vvv", ptr.To("VVV")}), + NonComparableStructField: NonComparableStruct{[]string{"www"}}, + NonComparableStructPtrField: ptr.To(NonComparableStruct{[]string{"www"}}), + SliceField: []string{"xxx"}, + MapField: map[string]string{"yyy": "yyy"}, + ImmutableField: "zzz", + ImmutablePtrField: ptr.To(ImmutableType("zzz")), + } + + st.Value(&structA).OldValue(&structA).ExpectValid() + st.Value(&structA).OldValue(&structA2).ExpectValid() + st.Value(&structA2).OldValue(&structA).ExpectValid() + + st.Value(&structA).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().MatchShortCircuit(), field.ErrorList{ + field.Invalid(field.NewPath("stringField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("stringPtrField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("structField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("structPtrField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("noncomparableStructField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("noncomparableStructPtrField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("sliceField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("mapField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("immutableField"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("immutablePtrField"), nil, "").WithOrigin("immutable"), + }.MarkShortCircuit()) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/zz_generated.validations.go new file mode 100644 index 0000000000..a0c82a0e6d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/immutable/zz_generated.validations.go @@ -0,0 +1,362 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package immutable + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ImmutableType validates an instance of ImmutableType according +// to declarative validation rules in the API schema. +func Validate_ImmutableType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ImmutableType) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.StringPtrField + }) + errs = append(errs, fn(fldPath.Child("stringPtrField"), obj.StringPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.StructField + fn := func( + fldPath *field.Path, + obj, oldObj *ComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ComparableStruct { + return &oldObj.StructField + }) + errs = append(errs, fn(fldPath.Child("structField"), &obj.StructField, oldVal, oldObj != nil)...) + } + + { // field Struct.StructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *ComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ComparableStruct { + return oldObj.StructPtrField + }) + errs = append(errs, fn(fldPath.Child("structPtrField"), obj.StructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.NonComparableStructField + fn := func( + fldPath *field.Path, + obj, oldObj *NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *NonComparableStruct { + return &oldObj.NonComparableStructField + }) + errs = append(errs, fn(fldPath.Child("noncomparableStructField"), &obj.NonComparableStructField, oldVal, oldObj != nil)...) + } + + { // field Struct.NonComparableStructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *NonComparableStruct { + return oldObj.NonComparableStructPtrField + }) + errs = append(errs, fn(fldPath.Child("noncomparableStructPtrField"), obj.NonComparableStructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.ImmutableField + fn := func( + fldPath *field.Path, + obj, oldObj *ImmutableType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ImmutableType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ImmutableType { + return &oldObj.ImmutableField + }) + errs = append(errs, fn(fldPath.Child("immutableField"), &obj.ImmutableField, oldVal, oldObj != nil)...) + } + + { // field Struct.ImmutablePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *ImmutableType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ImmutableType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ImmutableType { + return oldObj.ImmutablePtrField + }) + errs = append(errs, fn(fldPath.Child("immutablePtrField"), obj.ImmutablePtrField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc.go new file mode 100644 index 0000000000..bbfad88616 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc.go @@ -0,0 +1,41 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package transitions + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:item(key1: a)=+k8s:immutable + // +k8s:item(key1: b)=+k8s:subfield(stringField)=+k8s:immutable + ListField []Item `json:"listField"` +} + +type Item struct { + Key1 string `json:"key1"` + StringField string `json:"stringField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc_test.go new file mode 100644 index 0000000000..f6b86edd8e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/doc_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2025 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 transitions + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + old := &Struct{ + ListField: []Item{ + {Key1: "a", StringField: "s1"}, + {Key1: "b", StringField: "s2"}, + {Key1: "c", StringField: "s3"}, + }, + } + + new := &Struct{ + ListField: []Item{ + {Key1: "a", StringField: "changed"}, + {Key1: "b", StringField: "changed"}, + {Key1: "c", StringField: "changed"}, + }, + } + + st.Value(new).OldValue(old).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listField").Index(1).Child("stringField"), nil, "immutable").WithOrigin("immutable"), + }) + + st.Value(new).OldValue(&Struct{ListField: []Item{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listField").Index(1).Child("stringField"), nil, "immutable").WithOrigin("immutable"), + }) + + // Test that "c" can change independently + st.Value(&Struct{ + ListField: []Item{ + {Key1: "a", StringField: "s1"}, + {Key1: "b", StringField: "s2"}, + {Key1: "c", StringField: "changed"}, + }, + }).OldValue(old).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/zz_generated.validations.go new file mode 100644 index 0000000000..1c1fbd8e12 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/immutable_transitions/zz_generated.validations.go @@ -0,0 +1,120 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package transitions + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key1 == b.Key1 }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key1": "a"}" + earlyReturn := false + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key1 == "a" }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + }() + func() { // cohort = "{"key1": "b"}" + earlyReturn := false + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key1 == "b" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *Item) *string { return &o.StringField }, validate.DirectEqual, validate.Immutable) + }).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc.go new file mode 100644 index 0000000000..57b2298405 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc.go @@ -0,0 +1,78 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package multiplekeys + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:listType=map + // +k8s:listMapKey=stringKey + // +k8s:listMapKey=intKey + // +k8s:listMapKey=boolKey + // +k8s:item(stringKey: "target", intKey: 42, boolKey: true)=+k8s:validateFalse="item Items[stringKey=target,intKey=42,boolKey=true] 1" + // +k8s:item(stringKey: "target", intKey: 42, boolKey: true)=+k8s:validateFalse="item Items[stringKey=target,intKey=42,boolKey=true] 2" + Items []Item `json:"items"` + + // +k8s:listType=map + // +k8s:listMapKey=stringKey + // +k8s:listMapKey=intKey + // +k8s:listMapKey=boolKey + // +k8s:item(boolKey: true, stringKey: "target", intKey: 42)=+k8s:validateFalse="item OutOfOrder[boolKey=42,stringKey=target,intKey=42]" + OutOfOrder []Item `json:"outOfOrder"` + + // +k8s:listType=map + // +k8s:listMapKey=stringKey + // +k8s:listMapKey=intKey + // +k8s:listMapKey=boolKey + // +k8s:item(stringKey: "target-ptr", intKey: 42, boolKey: true)=+k8s:validateFalse="item PtrItems[stringKey=target-ptr,intKey=42,boolKey=true]" + PtrItems []PtrItem `json:"ptrItems"` + + // +k8s:listType=map + // +k8s:listMapKey=stringPtrKey + // +k8s:listMapKey=stringKey + // +k8s:item(stringPtrKey: "target-ptr", stringKey: "target")=+k8s:validateFalse="item MixedPtrItems" + MixedPtrItems []MixedPtrItem `json:"mixedPtrItems"` +} + +type Item struct { + StringKey string `json:"stringKey"` + IntKey int `json:"intKey"` + BoolKey bool `json:"boolKey"` + Data string `json:"data"` +} + +type PtrItem struct { + StringKey *string `json:"stringKey"` + IntKey int `json:"intKey"` + BoolKey bool `json:"boolKey"` + Data string `json:"data"` +} + +type MixedPtrItem struct { + StringPtrKey *string `json:"stringPtrKey"` + StringKey string `json:"stringKey"` + Data string `json:"data"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc_test.go new file mode 100644 index 0000000000..4354b83923 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/doc_test.go @@ -0,0 +1,102 @@ +/* +Copyright 2025 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 multiplekeys + +import ( + "testing" + + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Items: []Item{}, + }).ExpectValid() + + st.Value(&Struct{ + Items: nil, + }).ExpectValid() + + oldStruct := &Struct{Items: nil} + newStruct := &Struct{Items: []Item{}} + st.Value(newStruct).OldValue(oldStruct).ExpectValid() + st.Value(oldStruct).OldValue(newStruct).ExpectValid() + + st.Value(&Struct{ + Items: []Item{ + {StringKey: "target", IntKey: 42, BoolKey: true, Data: "match"}, + {StringKey: "target", IntKey: 42, BoolKey: false, Data: "no match, bool differs"}, + {StringKey: "target", IntKey: 99, BoolKey: true, Data: "no match, int differs"}, + {StringKey: "other", IntKey: 42, BoolKey: true, Data: "no match, string differs"}, + {StringKey: "other", IntKey: 99, BoolKey: false, Data: "no match, all different"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `items[0]`: { + "item Items[stringKey=target,intKey=42,boolKey=true] 1", + "item Items[stringKey=target,intKey=42,boolKey=true] 2", + }, + }) + + st.Value(&Struct{ + Items: []Item{ + {StringKey: "a", IntKey: 1, BoolKey: false, Data: "d1"}, + {StringKey: "b", IntKey: 2, BoolKey: true, Data: "d2"}, + {StringKey: "c", IntKey: 3, BoolKey: false, Data: "d3"}, + }, + }).ExpectValid() + + // Test ratcheting. + st.Value(&Struct{ + Items: []Item{ + {StringKey: "target", IntKey: 42, BoolKey: true}, + {StringKey: "changed", IntKey: 2, BoolKey: false}, + }, + }).OldValue(&Struct{ + Items: []Item{ + {StringKey: "target", IntKey: 42, BoolKey: true}, + {StringKey: "other", IntKey: 1, BoolKey: false}, + }, + }).ExpectValid() + + st.Value(&Struct{ + PtrItems: []PtrItem{ + {StringKey: ptr.To("target-ptr"), IntKey: 42, BoolKey: true, Data: "match"}, + {StringKey: ptr.To("target-ptr"), IntKey: 42, BoolKey: false, Data: "no match, bool differs"}, + {StringKey: ptr.To("target-ptr"), IntKey: 99, BoolKey: true, Data: "no match, int differs"}, + {StringKey: ptr.To("other"), IntKey: 42, BoolKey: true, Data: "no match, string differs"}, + {StringKey: nil, IntKey: 42, BoolKey: true, Data: "no match, nil string"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `ptrItems[0]`: { + "item PtrItems[stringKey=target-ptr,intKey=42,boolKey=true]", + }, + }) + + st.Value(&Struct{ + MixedPtrItems: []MixedPtrItem{ + {StringPtrKey: ptr.To("target-ptr"), StringKey: "target", Data: "match"}, + {StringPtrKey: ptr.To("target-ptr"), StringKey: "other", Data: "no match"}, + {StringPtrKey: nil, StringKey: "target", Data: "no match"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `mixedPtrItems[0]`: { + "item MixedPtrItems", + }, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/zz_generated.validations.go new file mode 100644 index 0000000000..78c8f3462e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/multiple_keys/zz_generated.validations.go @@ -0,0 +1,232 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiplekeys + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Items + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { + return a.StringKey == b.StringKey && a.IntKey == b.IntKey && a.BoolKey == b.BoolKey + }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"stringKey": "target", "intKey": 42, "boolKey": true}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.StringKey == "target" && item.IntKey == 42 && item.BoolKey == true }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[stringKey=target,intKey=42,boolKey=true] 1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.StringKey == "target" && item.IntKey == 42 && item.BoolKey == true }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[stringKey=target,intKey=42,boolKey=true] 2") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.Items + }) + errs = append(errs, fn(fldPath.Child("items"), obj.Items, oldVal, oldObj != nil)...) + } + + { // field Struct.OutOfOrder + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { + return a.StringKey == b.StringKey && a.IntKey == b.IntKey && a.BoolKey == b.BoolKey + }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"boolKey": true, "stringKey": "target", "intKey": 42}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.StringKey == "target" && item.IntKey == 42 && item.BoolKey == true }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item OutOfOrder[boolKey=42,stringKey=target,intKey=42]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.OutOfOrder + }) + errs = append(errs, fn(fldPath.Child("outOfOrder"), obj.OutOfOrder, oldVal, oldObj != nil)...) + } + + { // field Struct.PtrItems + fn := func( + fldPath *field.Path, + obj, oldObj []PtrItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *PtrItem, b *PtrItem) bool { + return ((a.StringKey == nil && b.StringKey == nil) || (a.StringKey != nil && b.StringKey != nil && *a.StringKey == *b.StringKey)) && a.IntKey == b.IntKey && a.BoolKey == b.BoolKey + }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"stringKey": "target-ptr", "intKey": 42, "boolKey": true}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *PtrItem) bool { + return item.StringKey != nil && *item.StringKey == "target-ptr" && item.IntKey == 42 && item.BoolKey == true + }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *PtrItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item PtrItems[stringKey=target-ptr,intKey=42,boolKey=true]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []PtrItem { + return oldObj.PtrItems + }) + errs = append(errs, fn(fldPath.Child("ptrItems"), obj.PtrItems, oldVal, oldObj != nil)...) + } + + { // field Struct.MixedPtrItems + fn := func( + fldPath *field.Path, + obj, oldObj []MixedPtrItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MixedPtrItem, b *MixedPtrItem) bool { + return ((a.StringPtrKey == nil && b.StringPtrKey == nil) || (a.StringPtrKey != nil && b.StringPtrKey != nil && *a.StringPtrKey == *b.StringPtrKey)) && a.StringKey == b.StringKey + }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"stringPtrKey": "target-ptr", "stringKey": "target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MixedPtrItem) bool { + return item.StringPtrKey != nil && *item.StringPtrKey == "target-ptr" && item.StringKey == "target" + }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedPtrItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item MixedPtrItems") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []MixedPtrItem { + return oldObj.MixedPtrItems + }) + errs = append(errs, fn(fldPath.Child("mixedPtrItems"), obj.MixedPtrItems, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc.go new file mode 100644 index 0000000000..5eaf7ae2ba --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc.go @@ -0,0 +1,113 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package singlekey + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "target")=+k8s:validateFalse="item Items[key=target] 1" + // +k8s:item(key: "target")=+k8s:validateFalse="item Items[key=target] 2" + Items []Item `json:"items"` + + // +k8s:listType=map + // +k8s:listMapKey=intField + // +k8s:item(intField: 42)=+k8s:validateFalse="item IntKeyItems[intField=42] 1" + // +k8s:item(intField: 42)=+k8s:validateFalse="item IntKeyItems[intField=42] 2" + IntKeyItems []IntKeyItem `json:"intKeyItems"` + + // +k8s:listType=map + // +k8s:listMapKey=boolField + // +k8s:item(boolField: true)=+k8s:validateFalse="item BoolKeyItems[boolField=true] 1" + // +k8s:item(boolField: true)=+k8s:validateFalse="item BoolKeyItems[boolField=true] 2" + BoolKeyItems []BoolKeyItem `json:"boolKeyItems"` + + // +k8s:listType=map + // +k8s:listMapKey=id + // +k8s:item(id: "typedef-target")=+k8s:validateFalse="item TypedefItems[id=typedef-target] 1" + // +k8s:item(id: "typedef-target")=+k8s:validateFalse="item TypedefItems[id=typedef-target] 2" + TypedefItems TypedefItemList `json:"typedefItems"` + + // Test atomic + unique=map + item combination + // +k8s:listType=atomic + // +k8s:unique=map + // +k8s:listMapKey=key + // +k8s:item(key: "target")=+k8s:validateFalse="item AtomicUniqueMapItems[key=target]" + AtomicUniqueMapItems []Item `json:"atomicUniqueMapItems"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "target-ptr")=+k8s:validateFalse="item PtrKeyItems[key=target-ptr]" + PtrKeyItems []PtrKeyItem `json:"ptrKeyItems"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "target")=+k8s:validateFalse="item PointerItems[key=target]" + PointerItems []*Item `json:"pointerItems"` +} + +type StructWithNestedTypedef struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "nested-target")=+k8s:validateFalse="item NestedItems[key=nested-target]" + NestedItems []NestedTypedefItem `json:"nestedItems"` +} + +type Item struct { + Key string `json:"key"` + Data string `json:"data"` +} + +type IntKeyItem struct { + IntField int `json:"intField"` + Data string `json:"data"` +} + +type BoolKeyItem struct { + BoolField bool `json:"boolField"` + Data string `json:"data"` +} + +type TypedefItem struct { + ID string `json:"id"` + Description string `json:"description"` +} + +type TypedefItemList []TypedefItem + +type StringAlias string +type NestedTypedefItem struct { + Key StringAlias `json:"key"` + Name string `json:"name"` +} + +type PtrKeyItem struct { + Key *string `json:"key"` + Data string `json:"data"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc_test.go new file mode 100644 index 0000000000..9a7efe2ba8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/doc_test.go @@ -0,0 +1,197 @@ +/* +Copyright 2025 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 singlekey + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Items: []Item{ + {Key: "a", Data: "d1"}, + {Key: "target", Data: "d2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `items[1]`: { + "item Items[key=target] 1", + "item Items[key=target] 2", + }, + }) + + st.Value(&Struct{ + Items: []Item{ + {Key: "a", Data: "d1"}, + {Key: "b", Data: "d2"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + Items: []Item{}, + }).ExpectValid() + + st.Value(&Struct{ + Items: nil, + }).ExpectValid() + + oldStruct := &Struct{Items: nil} + newStruct := &Struct{Items: []Item{}} + st.Value(newStruct).OldValue(oldStruct).ExpectValid() + st.Value(oldStruct).OldValue(newStruct).ExpectValid() + + st.Value(&Struct{ + IntKeyItems: []IntKeyItem{ + {IntField: 10, Data: "d1"}, + {IntField: 42, Data: "d2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `intKeyItems[1]`: { + "item IntKeyItems[intField=42] 1", + "item IntKeyItems[intField=42] 2", + }, + }) + + st.Value(&Struct{ + BoolKeyItems: []BoolKeyItem{ + {BoolField: false, Data: "d1"}, + {BoolField: true, Data: "d2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `boolKeyItems[1]`: { + "item BoolKeyItems[boolField=true] 1", + "item BoolKeyItems[boolField=true] 2", + }, + }) + + // Test typedef slice. + st.Value(&Struct{ + TypedefItems: TypedefItemList{ + {ID: "a", Description: "d1"}, + {ID: "typedef-target", Description: "d2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `typedefItems[1]`: { + "item TypedefItems[id=typedef-target] 1", + "item TypedefItems[id=typedef-target] 2", + }, + }) + + st.Value(&Struct{ + TypedefItems: TypedefItemList{ + {ID: "a", Description: "d1"}, + {ID: "b", Description: "d2"}, + }, + }).ExpectValid() + + // Test nested typedef. + st.Value(&StructWithNestedTypedef{ + NestedItems: []NestedTypedefItem{ + {Key: "a", Name: "n1"}, + {Key: "nested-target", Name: "n2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `nestedItems[1]`: {"item NestedItems[key=nested-target]"}, + }) + + st.Value(&StructWithNestedTypedef{ + NestedItems: []NestedTypedefItem{ + {Key: "a", Name: "n1"}, + {Key: "b", Name: "n2"}, + }, + }).ExpectValid() + + // Test atomic + unique=map + item combination + st.Value(&Struct{ + AtomicUniqueMapItems: []Item{ + {Key: "a", Data: "d1"}, + {Key: "target", Data: "d2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `atomicUniqueMapItems[1]`: { + "item AtomicUniqueMapItems[key=target]", + }, + }) + + st.Value(&Struct{ + AtomicUniqueMapItems: []Item{ + {Key: "a", Data: "d1"}, + {Key: "b", Data: "d2"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + AtomicUniqueMapItems: []Item{}, + }).ExpectValid() + + st.Value(&Struct{ + AtomicUniqueMapItems: nil, + }).ExpectValid() + + st.Value(&Struct{ + PtrKeyItems: []PtrKeyItem{ + {Key: ptr.To("a"), Data: "d1"}, + {Key: ptr.To("target-ptr"), Data: "d2"}, + {Key: nil, Data: "d3"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `ptrKeyItems[1]`: { + "item PtrKeyItems[key=target-ptr]", + }, + }) + + st.Value(&Struct{ + PtrKeyItems: []PtrKeyItem{ + {Key: ptr.To("a"), Data: "d1"}, + {Key: ptr.To("b"), Data: "d2"}, + {Key: nil, Data: "d3"}, + }, + }).ExpectValid() + + // Test pointer items (PtrSliceItem) + st.Value(&Struct{ + PointerItems: []*Item{ + {Key: "a", Data: "d1"}, + {Key: "target", Data: "d2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `pointerItems[1]`: { + "item PointerItems[key=target]", + }, + }) + + st.Value(&Struct{ + PointerItems: []*Item{ + {Key: "a", Data: "d1"}, + {Key: "b", Data: "d2"}, + }, + }).ExpectValid() + + // Nil element in list should trigger Required error + st.Value(&Struct{ + PointerItems: []*Item{ + {Key: "a", Data: "d1"}, + nil, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("pointerItems").Index(1), ""), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/zz_generated.validations.go new file mode 100644 index 0000000000..9556a06894 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/single_key/zz_generated.validations.go @@ -0,0 +1,417 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package singlekey + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructWithNestedTypedef + scheme.AddValidationFunc( + (*StructWithNestedTypedef)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructWithNestedTypedef( + ctx, op, nil, /* fldPath */ + obj.(*StructWithNestedTypedef), + safe.Cast[*StructWithNestedTypedef](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Items + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key == "target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[key=target] 1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key == "target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[key=target] 2") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.Items + }) + errs = append(errs, fn(fldPath.Child("items"), obj.Items, oldVal, oldObj != nil)...) + } + + { // field Struct.IntKeyItems + fn := func( + fldPath *field.Path, + obj, oldObj []IntKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *IntKeyItem, b *IntKeyItem) bool { return a.IntField == b.IntField }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"intField": 42}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *IntKeyItem) bool { return item.IntField == 42 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *IntKeyItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item IntKeyItems[intField=42] 1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *IntKeyItem) bool { return item.IntField == 42 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *IntKeyItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item IntKeyItems[intField=42] 2") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []IntKeyItem { + return oldObj.IntKeyItems + }) + errs = append(errs, fn(fldPath.Child("intKeyItems"), obj.IntKeyItems, oldVal, oldObj != nil)...) + } + + { // field Struct.BoolKeyItems + fn := func( + fldPath *field.Path, + obj, oldObj []BoolKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *BoolKeyItem, b *BoolKeyItem) bool { return a.BoolField == b.BoolField }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"boolField": true}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *BoolKeyItem) bool { return item.BoolField == true }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *BoolKeyItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item BoolKeyItems[boolField=true] 1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *BoolKeyItem) bool { return item.BoolField == true }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *BoolKeyItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item BoolKeyItems[boolField=true] 2") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []BoolKeyItem { + return oldObj.BoolKeyItems + }) + errs = append(errs, fn(fldPath.Child("boolKeyItems"), obj.BoolKeyItems, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefItems + fn := func( + fldPath *field.Path, + obj, oldObj TypedefItemList, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *TypedefItem, b *TypedefItem) bool { return a.ID == b.ID }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"id": "typedef-target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *TypedefItem) bool { return item.ID == "typedef-target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *TypedefItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item TypedefItems[id=typedef-target] 1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *TypedefItem) bool { return item.ID == "typedef-target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *TypedefItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item TypedefItems[id=typedef-target] 2") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) TypedefItemList { + return oldObj.TypedefItems + }) + errs = append(errs, fn(fldPath.Child("typedefItems"), obj.TypedefItems, oldVal, oldObj != nil)...) + } + + { // field Struct.AtomicUniqueMapItems + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key == "target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item AtomicUniqueMapItems[key=target]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.AtomicUniqueMapItems + }) + errs = append(errs, fn(fldPath.Child("atomicUniqueMapItems"), obj.AtomicUniqueMapItems, oldVal, oldObj != nil)...) + } + + { // field Struct.PtrKeyItems + fn := func( + fldPath *field.Path, + obj, oldObj []PtrKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *PtrKeyItem, b *PtrKeyItem) bool { + return ((a.Key == nil && b.Key == nil) || (a.Key != nil && b.Key != nil && *a.Key == *b.Key)) + }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "target-ptr"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *PtrKeyItem) bool { return item.Key != nil && *item.Key == "target-ptr" }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *PtrKeyItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item PtrKeyItems[key=target-ptr]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []PtrKeyItem { + return oldObj.PtrKeyItems + }) + errs = append(errs, fn(fldPath.Child("ptrKeyItems"), obj.PtrKeyItems, oldVal, oldObj != nil)...) + } + + { // field Struct.PointerItems + fn := func( + fldPath *field.Path, + obj, oldObj []*Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[Item](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.PtrSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "target"}" + if e := validate.PtrSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key == "target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item PointerItems[key=target]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*Item { + return oldObj.PointerItems + }) + errs = append(errs, fn(fldPath.Child("pointerItems"), obj.PointerItems, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_StructWithNestedTypedef validates an instance of StructWithNestedTypedef according +// to declarative validation rules in the API schema. +func Validate_StructWithNestedTypedef( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructWithNestedTypedef) (errs field.ErrorList) { + + // field StructWithNestedTypedef.TypeMeta has no validation + + { // field StructWithNestedTypedef.NestedItems + fn := func( + fldPath *field.Path, + obj, oldObj []NestedTypedefItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *NestedTypedefItem, b *NestedTypedefItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "nested-target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *NestedTypedefItem) bool { return item.Key == "nested-target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NestedTypedefItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item NestedItems[key=nested-target]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructWithNestedTypedef) []NestedTypedefItem { + return oldObj.NestedItems + }) + errs = append(errs, fn(fldPath.Child("nestedItems"), obj.NestedItems, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc.go new file mode 100644 index 0000000000..7f1a779669 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc.go @@ -0,0 +1,51 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package subfield + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "target")=+k8s:subfield(stringField)=+k8s:validateFalse="item Items[key=target].stringField" + Items []Item `json:"items"` + + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "ratchet")=+k8s:subfield(status)=+k8s:neq="forbidden" + RatchetItems []RatchetItem `json:"ratchetItems"` +} + +type Item struct { + Key string `json:"key"` + StringField string `json:"stringField"` +} + +type RatchetItem struct { + Key string `json:"key"` + Status string `json:"status"` + Version int `json:"version"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc_test.go new file mode 100644 index 0000000000..7ecbbca0c5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/doc_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2025 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 subfield + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Items: []Item{ + {Key: "other", StringField: "anything"}, + {Key: "target", StringField: "fails"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `items[1].stringField`: {"item Items[key=target].stringField"}, + }) + + st.Value(&Struct{ + Items: []Item{ + {Key: "other", StringField: "anything"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + RatchetItems: []RatchetItem{ + {Key: "ratchet", Status: "forbidden", Version: 1}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring(), field.ErrorList{ + field.Invalid(field.NewPath("ratchetItems").Index(0).Child("status"), nil, ""), + }) + + st.Value(&Struct{ + RatchetItems: []RatchetItem{ + {Key: "ratchet", Status: "allowed", Version: 1}, + }, + }).ExpectValid() + + oldStruct := &Struct{ + RatchetItems: []RatchetItem{ + {Key: "ratchet", Status: "forbidden", Version: 1}, + }, + } + newStruct := &Struct{ + RatchetItems: []RatchetItem{ + {Key: "ratchet", Status: "forbidden", Version: 2}, + }, + } + st.Value(newStruct).OldValue(oldStruct).ExpectValid() + + st.Value(&Struct{ + RatchetItems: []RatchetItem{ + {Key: "ratchet", Status: "forbidden", Version: 2}, + }, + }).OldValue(&Struct{ + RatchetItems: []RatchetItem{ + {Key: "ratchet", Status: "allowed", Version: 1}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring(), field.ErrorList{ + field.Invalid(field.NewPath("ratchetItems").Index(0).Child("status"), nil, ""), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/zz_generated.validations.go new file mode 100644 index 0000000000..a21068e067 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/subfield/zz_generated.validations.go @@ -0,0 +1,146 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package subfield + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Items + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key == "target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *Item) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[key=target].stringField") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.Items + }) + errs = append(errs, fn(fldPath.Child("items"), obj.Items, oldVal, oldObj != nil)...) + } + + { // field Struct.RatchetItems + fn := func( + fldPath *field.Path, + obj, oldObj []RatchetItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *RatchetItem, b *RatchetItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "ratchet"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *RatchetItem) bool { return item.Key == "ratchet" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *RatchetItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "status", + func(o *RatchetItem) *string { return &o.Status }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.NEQ(ctx, op, fldPath, obj, oldObj, "forbidden") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []RatchetItem { + return oldObj.RatchetItems + }) + errs = append(errs, fn(fldPath.Child("ratchetItems"), obj.RatchetItems, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc.go new file mode 100644 index 0000000000..b9458b5177 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc.go @@ -0,0 +1,70 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package typedef + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + TypedefItems ItemList `json:"typedefItems"` + NestedTypedefItems ItemListAlias `json:"nestedTypedefItems"` + + // +k8s:item(id: "field-target")=+k8s:validateFalse="item DualItems[id=field-target] from field" + DualItems DualItemList `json:"dualItems"` + + // +k8s:item(id: "target")=+k8s:validateFalse="item ConflictingItems[id=target] from field" + ConflictingItems ConflictingItemList `json:"conflictingItems"` +} + +type Item struct { + Key string `json:"key"` + Data string `json:"data"` +} + +// +k8s:listType=map +// +k8s:listMapKey=key +// +k8s:item(key: "immutable")=+k8s:immutable +// +k8s:item(key: "validated")=+k8s:validateFalse="item ItemList[key=validated]" +type ItemList []Item + +// +k8s:listType=map +// +k8s:listMapKey=key +// +k8s:item(key: "aliased")=+k8s:validateFalse="item ItemListAlias[key=aliased]" +type ItemListAlias ItemList + +type DualItem struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// +k8s:listType=map +// +k8s:listMapKey=id +// +k8s:item(id: "typedef-target")=+k8s:validateFalse="item DualItems[id=typedef-target] from typedef" +type DualItemList []DualItem + +// +k8s:listType=map +// +k8s:listMapKey=id +// +k8s:item(id: "target")=+k8s:validateFalse="item ConflictingItems[id=target] from typedef" +type ConflictingItemList []DualItem diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc_test.go new file mode 100644 index 0000000000..d823a2f361 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/doc_test.go @@ -0,0 +1,93 @@ +/* +Copyright 2025 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 typedef + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + TypedefItems: ItemList{ + {Key: "a", Data: "d1"}, + {Key: "b", Data: "d2"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + TypedefItems: ItemList{ + {Key: "a", Data: "d1"}, + {Key: "validated", Data: "d2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `typedefItems[1]`: {"item ItemList[key=validated]"}, + }) + + // Test immutability on typedef. + oldStruct := &Struct{ + TypedefItems: ItemList{ + {Key: "immutable", Data: "original"}, + }, + } + newStruct := &Struct{ + TypedefItems: ItemList{ + {Key: "immutable", Data: "changed"}, + }, + } + st.Value(newStruct).OldValue(oldStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("typedefItems").Index(0), nil, "immutable").WithOrigin("immutable"), + }) + + // Test nested typedef (typedef of typedef). + st.Value(&Struct{ + NestedTypedefItems: ItemListAlias{ + {Key: "normal", Data: "d1"}, + {Key: "aliased", Data: "d2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `nestedTypedefItems[1]`: {"item ItemListAlias[key=aliased]"}, + }) + + // Test tag on field and typedef. + st.Value(&Struct{ + DualItems: DualItemList{ + {ID: "a", Name: "n1"}, + {ID: "typedef-target", Name: "n2"}, + {ID: "field-target", Name: "n3"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `dualItems[1]`: {"item DualItems[id=typedef-target] from typedef"}, + `dualItems[2]`: {"item DualItems[id=field-target] from field"}, + }) + + // Test tag on field and typedef with same key. + st.Value(&Struct{ + ConflictingItems: ConflictingItemList{ + {ID: "a", Name: "n1"}, + {ID: "target", Name: "n2"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `conflictingItems[1]`: { + "item ConflictingItems[id=target] from typedef", + "item ConflictingItems[id=target] from field", + }, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/zz_generated.validations.go new file mode 100644 index 0000000000..a84a4bbd3b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/typedef/zz_generated.validations.go @@ -0,0 +1,283 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedef + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ConflictingItemList validates an instance of ConflictingItemList according +// to declarative validation rules in the API schema. +func Validate_ConflictingItemList( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ConflictingItemList) (errs field.ErrorList) { + + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *DualItem, b *DualItem) bool { return a.ID == b.ID }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"id": "target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *DualItem) bool { return item.ID == "target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *DualItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ConflictingItems[id=target] from typedef") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + + return errs +} + +// Validate_DualItemList validates an instance of DualItemList according +// to declarative validation rules in the API schema. +func Validate_DualItemList( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj DualItemList) (errs field.ErrorList) { + + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *DualItem, b *DualItem) bool { return a.ID == b.ID }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"id": "typedef-target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *DualItem) bool { return item.ID == "typedef-target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *DualItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item DualItems[id=typedef-target] from typedef") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + + return errs +} + +// Validate_ItemList validates an instance of ItemList according +// to declarative validation rules in the API schema. +func Validate_ItemList( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ItemList) (errs field.ErrorList) { + + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "immutable"}" + earlyReturn := false + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key == "immutable" }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + }() + func() { // cohort = "{"key": "validated"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key == "validated" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ItemList[key=validated]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + + return errs +} + +// Validate_ItemListAlias validates an instance of ItemListAlias according +// to declarative validation rules in the API schema. +func Validate_ItemListAlias( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ItemListAlias) (errs field.ErrorList) { + + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "aliased"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *Item) bool { return item.Key == "aliased" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ItemListAlias[key=aliased]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.TypedefItems + fn := func( + fldPath *field.Path, + obj, oldObj ItemList, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ItemList(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ItemList { + return oldObj.TypedefItems + }) + errs = append(errs, fn(fldPath.Child("typedefItems"), obj.TypedefItems, oldVal, oldObj != nil)...) + } + + { // field Struct.NestedTypedefItems + fn := func( + fldPath *field.Path, + obj, oldObj ItemListAlias, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ItemListAlias(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ItemListAlias { + return oldObj.NestedTypedefItems + }) + errs = append(errs, fn(fldPath.Child("nestedTypedefItems"), obj.NestedTypedefItems, oldVal, oldObj != nil)...) + } + + { // field Struct.DualItems + fn := func( + fldPath *field.Path, + obj, oldObj DualItemList, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "{"id": "field-target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *DualItem) bool { return item.ID == "field-target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *DualItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item DualItems[id=field-target] from field") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_DualItemList(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) DualItemList { + return oldObj.DualItems + }) + errs = append(errs, fn(fldPath.Child("dualItems"), obj.DualItems, oldVal, oldObj != nil)...) + } + + { // field Struct.ConflictingItems + fn := func( + fldPath *field.Path, + obj, oldObj ConflictingItemList, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "{"id": "target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *DualItem) bool { return item.ID == "target" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *DualItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ConflictingItems[id=target] from field") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_ConflictingItemList(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ConflictingItemList { + return oldObj.ConflictingItems + }) + errs = append(errs, fn(fldPath.Child("conflictingItems"), obj.ConflictingItems, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc.go new file mode 100644 index 0000000000..22221781ca --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc.go @@ -0,0 +1,41 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package unionsimple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:item(name: "succeeded")=+k8s:unionMember + // +k8s:item(name: "failed")=+k8s:unionMember + Tasks []Task `json:"tasks"` +} + +type Task struct { + Name string `json:"name"` + State string `json:"state"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc_test.go new file mode 100644 index 0000000000..0b527b5db5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/doc_test.go @@ -0,0 +1,64 @@ +/* +Copyright 2025 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 unionsimple + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Tasks: []Task{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "other", State: "Other"}, + }, + }).ExpectValid() + + invalidBothSet := &Struct{ + Tasks: []Task{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "failed", State: "Failed"}, + }, + } + + st.Value(invalidBothSet).ExpectMatches( + field.ErrorMatcher{}, + field.ErrorList{ + field.Invalid(field.NewPath("tasks"), "{Tasks[{\"name\": \"failed\"}], Tasks[{\"name\": \"succeeded\"}]}", + "must specify exactly one of: `Tasks[{\"name\": \"succeeded\"}]`, `Tasks[{\"name\": \"failed\"}]`"), + }, + ) + + invalidEmpty := &Struct{ + Tasks: []Task{}, + } + st.Value(invalidEmpty).ExpectMatches( + field.ErrorMatcher{}, + field.ErrorList{ + field.Invalid(field.NewPath("tasks"), "", + "must specify one of: `Tasks[{\"name\": \"succeeded\"}]`, `Tasks[{\"name\": \"failed\"}]`"), + }, + ) + + // Test ratcheting. + st.Value(invalidEmpty).OldValue(invalidEmpty).ExpectValid() + st.Value(invalidBothSet).OldValue(invalidBothSet).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/zz_generated.validations.go new file mode 100644 index 0000000000..b248852530 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/simple/zz_generated.validations.go @@ -0,0 +1,115 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package unionsimple + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_union_simple_Struct_tasks_ = validate.NewUnionMembership(validate.NewUnionMember("tasks[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("tasks[{\"name\": \"failed\"}]")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Tasks + fn := func( + fldPath *field.Path, + obj, oldObj []Task, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_union_simple_Struct_tasks_, + func(list []Task) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, + func(list []Task) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Task, b *Task) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Task { + return oldObj.Tasks + }) + errs = append(errs, fn(fldPath.Child("tasks"), obj.Tasks, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc.go new file mode 100644 index 0000000000..905c7cb252 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package uniontypedef + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + Tasks TaskList `json:"tasks"` +} + +// +k8s:listType=map +// +k8s:listMapKey=name +// +k8s:item(name: "succeeded")=+k8s:unionMember +// +k8s:item(name: "failed")=+k8s:unionMember +type TaskList []Task + +type Task struct { + Name string `json:"name"` + State string `json:"state"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc_test.go new file mode 100644 index 0000000000..9cde647731 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/doc_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2025 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 uniontypedef + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Tasks: TaskList{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "other", State: "Other"}, + }, + }).ExpectValid() + + invalidBothSet := &Struct{ + Tasks: []Task{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "failed", State: "Failed"}, + }, + } + + st.Value(invalidBothSet).ExpectMatches( + field.ErrorMatcher{}, + field.ErrorList{ + field.Invalid(field.NewPath("tasks"), "{TaskList[{\"name\": \"failed\"}], TaskList[{\"name\": \"succeeded\"}]}", + "must specify exactly one of: `TaskList[{\"name\": \"succeeded\"}]`, `TaskList[{\"name\": \"failed\"}]`"), + }, + ) + + invalidEmpty := &Struct{ + Tasks: TaskList{}, + } + st.Value(invalidEmpty).ExpectMatches( + field.ErrorMatcher{}, + field.ErrorList{ + field.Invalid(field.NewPath("tasks"), "", + "must specify one of: `TaskList[{\"name\": \"succeeded\"}]`, `TaskList[{\"name\": \"failed\"}]`"), + }, + ) + + // Test ratcheting. + st.Value(invalidEmpty).OldValue(invalidEmpty).ExpectValid() + + st.Value(invalidBothSet).OldValue(invalidBothSet).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/zz_generated.validations.go new file mode 100644 index 0000000000..d57d0c2568 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/union/typedef/zz_generated.validations.go @@ -0,0 +1,126 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package uniontypedef + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Tasks + fn := func( + fldPath *field.Path, + obj, oldObj TaskList, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_TaskList(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) TaskList { + return oldObj.Tasks + }) + errs = append(errs, fn(fldPath.Child("tasks"), obj.Tasks, oldVal, oldObj != nil)...) + } + + return errs +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_union_typedef_TaskList_ = validate.NewUnionMembership(validate.NewUnionMember("TaskList[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("TaskList[{\"name\": \"failed\"}]")) + +// Validate_TaskList validates an instance of TaskList according +// to declarative validation rules in the API schema. +func Validate_TaskList( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj TaskList) (errs field.ErrorList) { + + if e := validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_union_typedef_TaskList_, + func(list TaskList) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, + func(list TaskList) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Task, b *Task) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc.go new file mode 100644 index 0000000000..acb8d1192b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc.go @@ -0,0 +1,41 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package zeroroneofsimple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:item(name: "succeeded")=+k8s:zeroOrOneOfMember + // +k8s:item(name: "failed")=+k8s:zeroOrOneOfMember + Tasks []Task `json:"tasks"` +} + +type Task struct { + Name string `json:"name"` + State string `json:"state"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc_test.go new file mode 100644 index 0000000000..a2ace70a13 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/doc_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2025 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 zeroroneofsimple + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Tasks: []Task{ + {Name: "other", State: "Other"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + Tasks: []Task{ + {Name: "succeeded", State: "Succeeded"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + Tasks: []Task{}, + }).ExpectValid() + + invalidBothSet := &Struct{ + Tasks: []Task{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "failed", State: "Failed"}, + }, + } + st.Value(invalidBothSet).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Invalid(field.NewPath("tasks"), nil, "").WithOrigin("zeroOrOneOf"), + }, + ) + + // Test ratcheting. + st.Value(invalidBothSet).OldValue(invalidBothSet).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/zz_generated.validations.go new file mode 100644 index 0000000000..5edb55fdaa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/simple/zz_generated.validations.go @@ -0,0 +1,115 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package zeroroneofsimple + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_zerorooneof_simple_Struct_tasks_ = validate.NewUnionMembership(validate.NewUnionMember("tasks[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("tasks[{\"name\": \"failed\"}]")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Tasks + fn := func( + fldPath *field.Path, + obj, oldObj []Task, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_zerorooneof_simple_Struct_tasks_, + func(list []Task) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, + func(list []Task) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Task, b *Task) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Task { + return oldObj.Tasks + }) + errs = append(errs, fn(fldPath.Child("tasks"), obj.Tasks, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc.go new file mode 100644 index 0000000000..9d9d213c00 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package zeroroneooftypedef + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + Tasks TaskList `json:"tasks"` +} + +// +k8s:listType=map +// +k8s:listMapKey=name +// +k8s:item(name: "succeeded")=+k8s:zeroOrOneOfMember +// +k8s:item(name: "failed")=+k8s:zeroOrOneOfMember +type TaskList []Task + +type Task struct { + Name string `json:"name"` + State string `json:"state"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc_test.go new file mode 100644 index 0000000000..b0b8ce0c39 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/doc_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2025 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 zeroroneooftypedef + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Tasks: TaskList{ + {Name: "other", State: "Other"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + Tasks: TaskList{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "other", State: "Other"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + Tasks: TaskList{}, + }).ExpectValid() + + invalidBothSet := &Struct{ + Tasks: TaskList{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "failed", State: "Failed"}, + }, + } + + st.Value(invalidBothSet).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Invalid(field.NewPath("tasks"), nil, "").WithOrigin("zeroOrOneOf"), + }, + ) + + // Test ratcheting. + st.Value(invalidBothSet).OldValue(invalidBothSet).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/zz_generated.validations.go new file mode 100644 index 0000000000..7438212137 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/item/zerorooneof/typedef/zz_generated.validations.go @@ -0,0 +1,126 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package zeroroneooftypedef + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Tasks + fn := func( + fldPath *field.Path, + obj, oldObj TaskList, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_TaskList(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) TaskList { + return oldObj.Tasks + }) + errs = append(errs, fn(fldPath.Child("tasks"), obj.Tasks, oldVal, oldObj != nil)...) + } + + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_zerorooneof_typedef_TaskList_ = validate.NewUnionMembership(validate.NewUnionMember("TaskList[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("TaskList[{\"name\": \"failed\"}]")) + +// Validate_TaskList validates an instance of TaskList according +// to declarative validation rules in the API schema. +func Validate_TaskList( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj TaskList) (errs field.ErrorList) { + + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_item_zerorooneof_typedef_TaskList_, + func(list TaskList) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, + func(list TaskList) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Task, b *Task) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc.go new file mode 100644 index 0000000000..2cf3a6f135 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc.go @@ -0,0 +1,54 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package atomicslice + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type AtomicSliceStruct struct { + TypeMeta int + + // Case: Standard eachVal + // +k8s:listType=atomic + // +k8s:eachVal=+k8s:minimum=10 + Standard []int `json:"standard"` + + // Case: Alpha eachVal + // +k8s:listType=atomic + // +k8s:alpha=+k8s:eachVal=+k8s:minimum=10 + Alpha []int `json:"Alpha"` + + // Case: Beta eachVal + // +k8s:listType=atomic + // +k8s:beta=+k8s:eachVal=+k8s:minimum=10 + Beta []int `json:"Beta"` + + // Case: Standard eachVal, Alpha validation + // +k8s:listType=atomic + // +k8s:eachVal=+k8s:alpha=+k8s:minimum=10 + AlphaValidation []int `json:"AlphaValidation"` + + // Case: Standard eachVal, Beta validation + // +k8s:listType=atomic + // +k8s:eachVal=+k8s:beta=+k8s:minimum=10 + BetaValidation []int `json:"BetaValidation"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc_test.go new file mode 100644 index 0000000000..3193ec10ca --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/doc_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2025 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 atomicslice + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestAtomicSlice(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&AtomicSliceStruct{ + Standard: []int{5}, + Alpha: []int{5}, + Beta: []int{5}, + AlphaValidation: []int{5}, + BetaValidation: []int{5}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Case: Standard -> Normal Error + field.Invalid(field.NewPath("standard").Index(0), 5, "").WithOrigin("minimum"), + + // Case: Alpha eachVal -> Alpha Error + field.Invalid(field.NewPath("Alpha").Index(0), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case: Beta eachVal -> Beta Error + field.Invalid(field.NewPath("Beta").Index(0), 5, "").WithOrigin("minimum").MarkBeta(), + + // Case: Standard eachVal, Alpha validation -> Alpha Error + field.Invalid(field.NewPath("AlphaValidation").Index(0), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case: Standard eachVal, Beta validation -> Beta Error + field.Invalid(field.NewPath("BetaValidation").Index(0), 5, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/zz_generated.validations.go new file mode 100644 index 0000000000..05becedac1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/atomicslice/zz_generated.validations.go @@ -0,0 +1,203 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package atomicslice + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type AtomicSliceStruct + scheme.AddValidationFunc( + (*AtomicSliceStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_AtomicSliceStruct( + ctx, op, nil, /* fldPath */ + obj.(*AtomicSliceStruct), + safe.Cast[*AtomicSliceStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_AtomicSliceStruct validates an instance of AtomicSliceStruct according +// to declarative validation rules in the API schema. +func Validate_AtomicSliceStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *AtomicSliceStruct) (errs field.ErrorList) { + + // field AtomicSliceStruct.TypeMeta has no validation + + { // field AtomicSliceStruct.Standard + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AtomicSliceStruct) []int { + return oldObj.Standard + }) + errs = append(errs, fn(fldPath.Child("standard"), obj.Standard, oldVal, oldObj != nil)...) + } + + { // field AtomicSliceStruct.Alpha + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AtomicSliceStruct) []int { + return oldObj.Alpha + }) + errs = append(errs, fn(fldPath.Child("Alpha"), obj.Alpha, oldVal, oldObj != nil)...) + } + + { // field AtomicSliceStruct.Beta + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AtomicSliceStruct) []int { + return oldObj.Beta + }) + errs = append(errs, fn(fldPath.Child("Beta"), obj.Beta, oldVal, oldObj != nil)...) + } + + { // field AtomicSliceStruct.AlphaValidation + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha() + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AtomicSliceStruct) []int { + return oldObj.AlphaValidation + }) + errs = append(errs, fn(fldPath.Child("AlphaValidation"), obj.AlphaValidation, oldVal, oldObj != nil)...) + } + + { // field AtomicSliceStruct.BetaValidation + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta() + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *AtomicSliceStruct) []int { + return oldObj.BetaValidation + }) + errs = append(errs, fn(fldPath.Child("BetaValidation"), obj.BetaValidation, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc.go new file mode 100644 index 0000000000..149c5cfbef --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc.go @@ -0,0 +1,47 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package enums + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + EnumField Enum `json:"enumField"` + + EnumFieldBeta BetaEnum `json:"enumFieldBeta"` +} + +// +k8s:alpha=+k8s:enum +type Enum string + +const ( + EnumA Enum = "A" +) + +// +k8s:beta=+k8s:enum +type BetaEnum string + +const ( + BetaEnumA BetaEnum = "A" +) diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc_test.go new file mode 100644 index 0000000000..3a2fca9742 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/doc_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2025 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 enums + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + EnumField: "A", + EnumFieldBeta: "A", + }).ExpectValid() + + // Test failures marked as alpha + st.Value(&Struct{ + EnumField: "B", + EnumFieldBeta: "A", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.NotSupported(field.NewPath("enumField"), Enum("B"), []string{"A"}).MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + EnumField: "A", + EnumFieldBeta: "A", + }).ExpectValid() + + // Test failures marked as beta + st.Value(&Struct{ + EnumField: "A", + EnumFieldBeta: "B", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.NotSupported(field.NewPath("enumFieldBeta"), BetaEnum("B"), []string{"A"}).MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/zz_generated.validations.go new file mode 100644 index 0000000000..423c2131bf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/enums/zz_generated.validations.go @@ -0,0 +1,142 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package enums + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + sets "k8s.io/apimachinery/pkg/util/sets" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var symbolsForBetaEnum = sets.New(BetaEnumA) + +// Validate_BetaEnum validates an instance of BetaEnum according +// to declarative validation rules in the API schema. +func Validate_BetaEnum( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *BetaEnum) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForBetaEnum, nil).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +var symbolsForEnum = sets.New(EnumA) + +// Validate_Enum validates an instance of Enum according +// to declarative validation rules in the API schema. +func Validate_Enum( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Enum) (errs field.ErrorList) { + + if e := validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum, nil).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.EnumField + fn := func( + fldPath *field.Path, + obj, oldObj *Enum, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Enum(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Enum { + return &oldObj.EnumField + }) + errs = append(errs, fn(fldPath.Child("enumField"), &obj.EnumField, oldVal, oldObj != nil)...) + } + + { // field Struct.EnumFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *BetaEnum, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_BetaEnum(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *BetaEnum { + return &oldObj.EnumFieldBeta + }) + errs = append(errs, fn(fldPath.Child("enumFieldBeta"), &obj.EnumFieldBeta, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc.go new file mode 100644 index 0000000000..f5f0b43ceb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc.go @@ -0,0 +1,118 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package listkeys + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type MapItem struct { + Key string `json:"key"` + Value int `json:"value"` +} + +type MultiKeyItem struct { + Key1 string `json:"key1"` + Key2 int `json:"key2"` + Value int `json:"value"` +} + +type ListKeyStruct struct { + TypeMeta int + + // Case: Alpha listType, Standard Key + // +k8s:alpha=+k8s:listType=map + // +k8s:listMapKey=key + AlphaListTypeStandardKey []MapItem `json:"alphaListTypeStandardKey"` + + // Case: Standard listType, Alpha Key + // +k8s:listType=map + // +k8s:alpha=+k8s:listMapKey=key + StandardListTypeAlphaKey []MapItem `json:"standardListTypeAlphaKey"` + + // Case: Alpha listType, Alpha Key + // +k8s:alpha=+k8s:listType=map + // +k8s:alpha=+k8s:listMapKey=key + AlphaListTypeAlphaKey []MapItem `json:"alphaListTypeAlphaKey"` + + // Case: Standard listType, Alpha Key1, Standard Key2 + // +k8s:listType=map + // +k8s:alpha=+k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + StandardListTypeMixedKeys1 []MultiKeyItem `json:"standardListTypeMixedKeys1"` + + // Case: Standard listType, Standard Key1, Alpha Key2 + // +k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:alpha=+k8s:listMapKey=key2 + StandardListTypeMixedKeys2 []MultiKeyItem `json:"standardListTypeMixedKeys2"` + + // Case: Alpha listType, Alpha Key1, Standard Key2 + // +k8s:alpha=+k8s:listType=map + // +k8s:alpha=+k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + AlphaListTypeMixedKeys1 []MultiKeyItem `json:"alphaListTypeMixedKeys1"` + + // Case: Alpha listType, Standard Key1, Alpha Key2 + // +k8s:alpha=+k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:alpha=+k8s:listMapKey=key2 + AlphaListTypeMixedKeys2 []MultiKeyItem `json:"alphaListTypeMixedKeys2"` + + // Case: Standard listType, Beta Key1, Standard Key2 + // +k8s:listType=map + // +k8s:beta=+k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + StandardListTypeMixedKeysBeta1 []MultiKeyItem `json:"standardListTypeMixedKeysBeta1"` + + // Case: Standard listType, Standard Key1, Beta Key2 + // +k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:beta=+k8s:listMapKey=key2 + StandardListTypeMixedKeysBeta2 []MultiKeyItem `json:"standardListTypeMixedKeysBeta2"` + + // Case: Beta listType, Beta Key1, Standard Key2 + // +k8s:beta=+k8s:listType=map + // +k8s:beta=+k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + BetaListTypeMixedKeys1 []MultiKeyItem `json:"betaListTypeMixedKeys1"` + + // Case: Beta listType, Standard Key1, Beta Key2 + // +k8s:beta=+k8s:listType=map + // +k8s:listMapKey=key1 + // +k8s:beta=+k8s:listMapKey=key2 + BetaListTypeMixedKeys2 []MultiKeyItem `json:"betaListTypeMixedKeys2"` + + // Case: Beta listType, Standard Key + // +k8s:beta=+k8s:listType=map + // +k8s:listMapKey=key + BetaListTypeStandardKey []MapItem `json:"betaListTypeStandardKey"` + + // Case: Standard listType, Beta Key + // +k8s:listType=map + // +k8s:beta=+k8s:listMapKey=key + StandardListTypeBetaKey []MapItem `json:"standardListTypeBetaKey"` + + // Case: Beta listType, Beta Key + // +k8s:beta=+k8s:listType=map + // +k8s:beta=+k8s:listMapKey=key + BetaListTypeBetaKey []MapItem `json:"betaListTypeBetaKey"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc_test.go new file mode 100644 index 0000000000..b18ccd769c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/doc_test.go @@ -0,0 +1,147 @@ +/* +Copyright 2025 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 listkeys + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestListKeys(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ListKeyStruct{ + // Case 1: Alpha listType, Standard Key + AlphaListTypeStandardKey: []MapItem{ + {Key: "a", Value: 1}, + {Key: "a", Value: 2}, + }, + // Case 2: Standard listType, Alpha Key + StandardListTypeAlphaKey: []MapItem{ + {Key: "b", Value: 1}, + {Key: "b", Value: 2}, + }, + // Case 3: Alpha listType, Alpha Key + AlphaListTypeAlphaKey: []MapItem{ + {Key: "c", Value: 1}, + {Key: "c", Value: 2}, + }, + // Case 4: Standard listType, Alpha Key1, Standard Key2 + StandardListTypeMixedKeys1: []MultiKeyItem{ + {Key1: "d", Key2: 1, Value: 1}, + {Key1: "d", Key2: 1, Value: 2}, + }, + // Case 5: Standard listType, Standard Key1, Alpha Key2 + StandardListTypeMixedKeys2: []MultiKeyItem{ + {Key1: "e", Key2: 1, Value: 1}, + {Key1: "e", Key2: 1, Value: 2}, + }, + // Case 6: Alpha listType, Alpha Key1, Standard Key2 + AlphaListTypeMixedKeys1: []MultiKeyItem{ + {Key1: "f", Key2: 1, Value: 1}, + {Key1: "f", Key2: 1, Value: 2}, + }, + // Case 7: Alpha listType, Standard Key1, Alpha Key2 + AlphaListTypeMixedKeys2: []MultiKeyItem{ + {Key1: "g", Key2: 1, Value: 1}, + {Key1: "g", Key2: 1, Value: 2}, + }, + + // Case 11: Standard listType, Beta Key1, Standard Key2 + StandardListTypeMixedKeysBeta1: []MultiKeyItem{ + {Key1: "k", Key2: 1, Value: 1}, + {Key1: "k", Key2: 1, Value: 2}, + }, + // Case 12: Standard listType, Standard Key1, Beta Key2 + StandardListTypeMixedKeysBeta2: []MultiKeyItem{ + {Key1: "l", Key2: 1, Value: 1}, + {Key1: "l", Key2: 1, Value: 2}, + }, + // Case 13: Beta listType, Beta Key1, Standard Key2 + BetaListTypeMixedKeys1: []MultiKeyItem{ + {Key1: "m", Key2: 1, Value: 1}, + {Key1: "m", Key2: 1, Value: 2}, + }, + // Case 14: Beta listType, Standard Key1, Beta Key2 + BetaListTypeMixedKeys2: []MultiKeyItem{ + {Key1: "n", Key2: 1, Value: 1}, + {Key1: "n", Key2: 1, Value: 2}, + }, + + // Case 8: Beta listType, Standard Key + BetaListTypeStandardKey: []MapItem{ + {Key: "h", Value: 1}, + {Key: "h", Value: 2}, + }, + // Case 9: Standard listType, Beta Key + StandardListTypeBetaKey: []MapItem{ + {Key: "i", Value: 1}, + {Key: "i", Value: 2}, + }, + // Case 10: Beta listType, Beta Key + BetaListTypeBetaKey: []MapItem{ + {Key: "j", Value: 1}, + {Key: "j", Value: 2}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Case 1: Alpha listType, Standard Key -> Alpha Error + field.Duplicate(field.NewPath("alphaListTypeStandardKey").Index(1), MapItem{Key: "a", Value: 2}).MarkAlpha(), + + // Case 2: Standard listType, Alpha Key -> Normal Error + // The alpha key is treated as a functional key for the list validation. + field.Duplicate(field.NewPath("standardListTypeAlphaKey").Index(1), MapItem{Key: "b", Value: 2}), + + // Case 3: Alpha listType, Alpha Key -> Alpha Error + field.Duplicate(field.NewPath("alphaListTypeAlphaKey").Index(1), MapItem{Key: "c", Value: 2}).MarkAlpha(), + + // Case 4: Standard listType, Alpha Key1, Standard Key2 -> Normal Error + // Both keys participate in the uniqueness check. + field.Duplicate(field.NewPath("standardListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "d", Key2: 1, Value: 2}), + + // Case 5: Standard listType, Standard Key1, Alpha Key2 -> Normal Error + field.Duplicate(field.NewPath("standardListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "e", Key2: 1, Value: 2}), + + // Case 6: Alpha listType, Alpha Key1, Standard Key2 -> Alpha Error + field.Duplicate(field.NewPath("alphaListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "f", Key2: 1, Value: 2}).MarkAlpha(), + + // Case 7: Alpha listType, Standard Key1, Alpha Key2 -> Alpha Error + field.Duplicate(field.NewPath("alphaListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "g", Key2: 1, Value: 2}).MarkAlpha(), + + // Case 11: Standard listType, Beta Key1, Standard Key2 -> Normal Error + field.Duplicate(field.NewPath("standardListTypeMixedKeysBeta1").Index(1), MultiKeyItem{Key1: "k", Key2: 1, Value: 2}), + + // Case 12: Standard listType, Standard Key1, Beta Key2 -> Normal Error + field.Duplicate(field.NewPath("standardListTypeMixedKeysBeta2").Index(1), MultiKeyItem{Key1: "l", Key2: 1, Value: 2}), + + // Case 13: Beta listType, Beta Key1, Standard Key2 -> Beta Error + field.Duplicate(field.NewPath("betaListTypeMixedKeys1").Index(1), MultiKeyItem{Key1: "m", Key2: 1, Value: 2}).MarkBeta(), + + // Case 14: Beta listType, Standard Key1, Beta Key2 -> Beta Error + field.Duplicate(field.NewPath("betaListTypeMixedKeys2").Index(1), MultiKeyItem{Key1: "n", Key2: 1, Value: 2}).MarkBeta(), + + // Case 8: Beta listType, Standard Key -> Beta Error + field.Duplicate(field.NewPath("betaListTypeStandardKey").Index(1), MapItem{Key: "h", Value: 2}).MarkBeta(), + + // Case 9: Standard listType, Beta Key -> Normal Error + // The beta key is treated as a functional key for the list validation. + field.Duplicate(field.NewPath("standardListTypeBetaKey").Index(1), MapItem{Key: "i", Value: 2}), + + // Case 10: Beta listType, Beta Key -> Beta Error + field.Duplicate(field.NewPath("betaListTypeBetaKey").Index(1), MapItem{Key: "j", Value: 2}).MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/zz_generated.validations.go new file mode 100644 index 0000000000..03459d0929 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listkeys/zz_generated.validations.go @@ -0,0 +1,432 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package listkeys + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ListKeyStruct + scheme.AddValidationFunc( + (*ListKeyStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ListKeyStruct( + ctx, op, nil, /* fldPath */ + obj.(*ListKeyStruct), + safe.Cast[*ListKeyStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ListKeyStruct validates an instance of ListKeyStruct according +// to declarative validation rules in the API schema. +func Validate_ListKeyStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ListKeyStruct) (errs field.ErrorList) { + + // field ListKeyStruct.TypeMeta has no validation + + { // field ListKeyStruct.AlphaListTypeStandardKey + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MapItem { + return oldObj.AlphaListTypeStandardKey + }) + errs = append(errs, fn(fldPath.Child("alphaListTypeStandardKey"), obj.AlphaListTypeStandardKey, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.StandardListTypeAlphaKey + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MapItem { + return oldObj.StandardListTypeAlphaKey + }) + errs = append(errs, fn(fldPath.Child("standardListTypeAlphaKey"), obj.StandardListTypeAlphaKey, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.AlphaListTypeAlphaKey + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MapItem { + return oldObj.AlphaListTypeAlphaKey + }) + errs = append(errs, fn(fldPath.Child("alphaListTypeAlphaKey"), obj.AlphaListTypeAlphaKey, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.StandardListTypeMixedKeys1 + fn := func( + fldPath *field.Path, + obj, oldObj []MultiKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MultiKeyItem, b *MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MultiKeyItem { + return oldObj.StandardListTypeMixedKeys1 + }) + errs = append(errs, fn(fldPath.Child("standardListTypeMixedKeys1"), obj.StandardListTypeMixedKeys1, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.StandardListTypeMixedKeys2 + fn := func( + fldPath *field.Path, + obj, oldObj []MultiKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MultiKeyItem, b *MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MultiKeyItem { + return oldObj.StandardListTypeMixedKeys2 + }) + errs = append(errs, fn(fldPath.Child("standardListTypeMixedKeys2"), obj.StandardListTypeMixedKeys2, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.AlphaListTypeMixedKeys1 + fn := func( + fldPath *field.Path, + obj, oldObj []MultiKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MultiKeyItem, b *MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MultiKeyItem { + return oldObj.AlphaListTypeMixedKeys1 + }) + errs = append(errs, fn(fldPath.Child("alphaListTypeMixedKeys1"), obj.AlphaListTypeMixedKeys1, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.AlphaListTypeMixedKeys2 + fn := func( + fldPath *field.Path, + obj, oldObj []MultiKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MultiKeyItem, b *MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MultiKeyItem { + return oldObj.AlphaListTypeMixedKeys2 + }) + errs = append(errs, fn(fldPath.Child("alphaListTypeMixedKeys2"), obj.AlphaListTypeMixedKeys2, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.StandardListTypeMixedKeysBeta1 + fn := func( + fldPath *field.Path, + obj, oldObj []MultiKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MultiKeyItem, b *MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MultiKeyItem { + return oldObj.StandardListTypeMixedKeysBeta1 + }) + errs = append(errs, fn(fldPath.Child("standardListTypeMixedKeysBeta1"), obj.StandardListTypeMixedKeysBeta1, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.StandardListTypeMixedKeysBeta2 + fn := func( + fldPath *field.Path, + obj, oldObj []MultiKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MultiKeyItem, b *MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MultiKeyItem { + return oldObj.StandardListTypeMixedKeysBeta2 + }) + errs = append(errs, fn(fldPath.Child("standardListTypeMixedKeysBeta2"), obj.StandardListTypeMixedKeysBeta2, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.BetaListTypeMixedKeys1 + fn := func( + fldPath *field.Path, + obj, oldObj []MultiKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MultiKeyItem, b *MultiKeyItem) bool { return a.Key1 == b.Key1 && a.Key2 == b.Key2 }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MultiKeyItem { + return oldObj.BetaListTypeMixedKeys1 + }) + errs = append(errs, fn(fldPath.Child("betaListTypeMixedKeys1"), obj.BetaListTypeMixedKeys1, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.BetaListTypeMixedKeys2 + fn := func( + fldPath *field.Path, + obj, oldObj []MultiKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MultiKeyItem, b *MultiKeyItem) bool { return a.Key2 == b.Key2 && a.Key1 == b.Key1 }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MultiKeyItem { + return oldObj.BetaListTypeMixedKeys2 + }) + errs = append(errs, fn(fldPath.Child("betaListTypeMixedKeys2"), obj.BetaListTypeMixedKeys2, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.BetaListTypeStandardKey + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MapItem { + return oldObj.BetaListTypeStandardKey + }) + errs = append(errs, fn(fldPath.Child("betaListTypeStandardKey"), obj.BetaListTypeStandardKey, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.StandardListTypeBetaKey + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MapItem { + return oldObj.StandardListTypeBetaKey + }) + errs = append(errs, fn(fldPath.Child("standardListTypeBetaKey"), obj.StandardListTypeBetaKey, oldVal, oldObj != nil)...) + } + + { // field ListKeyStruct.BetaListTypeBetaKey + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListKeyStruct) []MapItem { + return oldObj.BetaListTypeBetaKey + }) + errs = append(errs, fn(fldPath.Child("betaListTypeBetaKey"), obj.BetaListTypeBetaKey, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc.go new file mode 100644 index 0000000000..864a390dc1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc.go @@ -0,0 +1,80 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package listmapitem + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type MapItem struct { + Key string `json:"key"` + Value int `json:"value"` +} + +type InnerItem struct { + Value int `json:"value"` +} + +type ListMapItemStruct struct { + TypeMeta int + + // Case: Standard Item + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10 + StandardItem []MapItem `json:"standardItem"` + + // Case: Alpha Item tag + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:alpha=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10 + AlphaItemTag []MapItem `json:"alphaItemTag"` + + // Case: Alpha validation + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10 + AlphaValidation []MapItem `json:"alphaValidation"` + + // Case: Double Alpha + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:alpha=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10 + DoubleAlpha []MapItem `json:"doubleAlpha"` + + // Case: Beta Item tag + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:beta=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:minimum=10 + BetaItemTag []MapItem `json:"betaItemTag"` + + // Case: Beta validation + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10 + BetaValidation []MapItem `json:"betaValidation"` + + // Case: Double Beta + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:beta=+k8s:item(key: "foo")=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10 + DoubleBeta []MapItem `json:"doubleBeta"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc_test.go new file mode 100644 index 0000000000..282da8b0ff --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/doc_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2025 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 listmapitem + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestListMapItem(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ListMapItemStruct{ + StandardItem: []MapItem{{Key: "foo", Value: 5}}, + AlphaItemTag: []MapItem{{Key: "foo", Value: 5}}, + AlphaValidation: []MapItem{{Key: "foo", Value: 5}}, + DoubleAlpha: []MapItem{{Key: "foo", Value: 5}}, + + BetaItemTag: []MapItem{{Key: "foo", Value: 5}}, + BetaValidation: []MapItem{{Key: "foo", Value: 5}}, + DoubleBeta: []MapItem{{Key: "foo", Value: 5}}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Case 1: Standard Item -> Normal Error + field.Invalid(field.NewPath("standardItem").Index(0).Child("value"), 5, "").WithOrigin("minimum"), + + // Case 2: Alpha Item tag -> Alpha Error + field.Invalid(field.NewPath("alphaItemTag").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case 3: Alpha validation -> Alpha Error + field.Invalid(field.NewPath("alphaValidation").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case 4: Double Alpha -> Alpha Error + field.Invalid(field.NewPath("doubleAlpha").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkAlpha(), + + // Case 5: Beta Item tag -> Beta Error + field.Invalid(field.NewPath("betaItemTag").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(), + + // Case 6: Beta validation -> Beta Error + field.Invalid(field.NewPath("betaValidation").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(), + + // Case 7: Double Beta -> Beta Error + field.Invalid(field.NewPath("doubleBeta").Index(0).Child("value"), 5, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/zz_generated.validations.go new file mode 100644 index 0000000000..ce12c926e1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listmapitem/zz_generated.validations.go @@ -0,0 +1,341 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package listmapitem + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ListMapItemStruct + scheme.AddValidationFunc( + (*ListMapItemStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ListMapItemStruct( + ctx, op, nil, /* fldPath */ + obj.(*ListMapItemStruct), + safe.Cast[*ListMapItemStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ListMapItemStruct validates an instance of ListMapItemStruct according +// to declarative validation rules in the API schema. +func Validate_ListMapItemStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ListMapItemStruct) (errs field.ErrorList) { + + // field ListMapItemStruct.TypeMeta has no validation + + { // field ListMapItemStruct.StandardItem + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "foo"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *MapItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListMapItemStruct) []MapItem { + return oldObj.StandardItem + }) + errs = append(errs, fn(fldPath.Child("standardItem"), obj.StandardItem, oldVal, oldObj != nil)...) + } + + { // field ListMapItemStruct.AlphaItemTag + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "foo"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *MapItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }) + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListMapItemStruct) []MapItem { + return oldObj.AlphaItemTag + }) + errs = append(errs, fn(fldPath.Child("alphaItemTag"), obj.AlphaItemTag, oldVal, oldObj != nil)...) + } + + { // field ListMapItemStruct.AlphaValidation + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "foo"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *MapItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha() + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListMapItemStruct) []MapItem { + return oldObj.AlphaValidation + }) + errs = append(errs, fn(fldPath.Child("alphaValidation"), obj.AlphaValidation, oldVal, oldObj != nil)...) + } + + { // field ListMapItemStruct.DoubleAlpha + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "foo"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *MapItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha() + }) + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListMapItemStruct) []MapItem { + return oldObj.DoubleAlpha + }) + errs = append(errs, fn(fldPath.Child("doubleAlpha"), obj.DoubleAlpha, oldVal, oldObj != nil)...) + } + + { // field ListMapItemStruct.BetaItemTag + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "foo"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *MapItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }) + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListMapItemStruct) []MapItem { + return oldObj.BetaItemTag + }) + errs = append(errs, fn(fldPath.Child("betaItemTag"), obj.BetaItemTag, oldVal, oldObj != nil)...) + } + + { // field ListMapItemStruct.BetaValidation + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "foo"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *MapItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta() + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListMapItemStruct) []MapItem { + return oldObj.BetaValidation + }) + errs = append(errs, fn(fldPath.Child("betaValidation"), obj.BetaValidation, oldVal, oldObj != nil)...) + } + + { // field ListMapItemStruct.DoubleBeta + fn := func( + fldPath *field.Path, + obj, oldObj []MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MapItem, b *MapItem) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key": "foo"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MapItem) bool { return item.Key == "foo" }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *MapItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta() + }) + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListMapItemStruct) []MapItem { + return oldObj.DoubleBeta + }) + errs = append(errs, fn(fldPath.Child("doubleBeta"), obj.DoubleBeta, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc.go new file mode 100644 index 0000000000..5676d6e733 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc.go @@ -0,0 +1,72 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package listset + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type ListSetStruct struct { + TypeMeta int + + // Case: Alpha listType=set + // +k8s:alpha=+k8s:listType=set + Set []ComplexSetItem `json:"set"` + + // Case: Beta listType=set + // +k8s:beta=+k8s:listType=set + BetaSet []ComplexSetItem `json:"betaSet"` + + // Case: Chained subfield validation + // +k8s:listType=set + // +k8s:eachVal=+k8s:subfield(value)=+k8s:alpha=+k8s:minimum=10 + ChainedSubfieldSet []SimpleSetItem `json:"chainedSubfieldSet"` + + // Case: Alpha listType=set, Beta item validation + // +k8s:alpha=+k8s:listType=set + SetBetaItem []ComplexSetItemBeta `json:"setBetaItem"` + + // Case: Beta listType=set, Beta item validation + // +k8s:beta=+k8s:listType=set + BetaSetBetaItem []ComplexSetItemBeta `json:"betaSetBetaItem"` + + // Case: Chained subfield validation (Beta) + // +k8s:listType=set + // +k8s:eachVal=+k8s:subfield(value)=+k8s:beta=+k8s:minimum=10 + ChainedSubfieldSetBeta []SimpleSetItem `json:"chainedSubfieldSetBeta"` +} + +type ComplexSetItem struct { + // +k8s:alpha=+k8s:minimum=10 + Value int `json:"value"` + StringVal string `json:"stringVal"` +} + +type ComplexSetItemBeta struct { + // +k8s:beta=+k8s:minimum=10 + Value int `json:"value"` + StringVal string `json:"stringVal"` +} + +type SimpleSetItem struct { + Value int `json:"value"` + StringVal string `json:"stringVal"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc_test.go new file mode 100644 index 0000000000..24ffc0e147 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/doc_test.go @@ -0,0 +1,70 @@ +/* +Copyright 2025 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 listset + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestListSet(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ListSetStruct{ + Set: []ComplexSetItem{ + {StringVal: "abc", Value: 10}, + {StringVal: "abc", Value: 10}, // Duplicate value (alpha) + }, + BetaSet: []ComplexSetItem{ + {StringVal: "abc", Value: 10}, + {StringVal: "abc", Value: 10}, // Duplicate value (beta) + }, + ChainedSubfieldSet: []SimpleSetItem{ + {StringVal: "def", Value: 9}, // Value 9 < 10 (alpha) + }, + SetBetaItem: []ComplexSetItemBeta{ + {StringVal: "ghi", Value: 10}, + {StringVal: "ghi", Value: 10}, // Duplicate value (alpha) + }, + BetaSetBetaItem: []ComplexSetItemBeta{ + {StringVal: "jkl", Value: 10}, + {StringVal: "jkl", Value: 10}, // Duplicate value (beta) + }, + ChainedSubfieldSetBeta: []SimpleSetItem{ + {StringVal: "mno", Value: 9}, // Value 9 < 10 (beta) + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Set: Duplicate value is SHADOWED + field.Duplicate(field.NewPath("set").Index(1), ComplexSetItem{StringVal: "abc", Value: 10}).MarkAlpha(), + + // BetaSet: Duplicate value is SHADOWED + field.Duplicate(field.NewPath("betaSet").Index(1), ComplexSetItem{StringVal: "abc", Value: 10}).MarkBeta(), + + // ChainedSubfieldSet: Value 9 < 10 is SHADOWED + field.Invalid(field.NewPath("chainedSubfieldSet").Index(0).Child("value"), 9, "").WithOrigin("minimum").MarkAlpha(), + + // SetBetaItem: Duplicate value is SHADOWED (Alpha list) + field.Duplicate(field.NewPath("setBetaItem").Index(1), ComplexSetItemBeta{StringVal: "ghi", Value: 10}).MarkAlpha(), + + // BetaSetBetaItem: Duplicate value is SHADOWED (Beta list) + field.Duplicate(field.NewPath("betaSetBetaItem").Index(1), ComplexSetItemBeta{StringVal: "jkl", Value: 10}).MarkBeta(), + + // ChainedSubfieldSetBeta: Value 9 < 10 is SHADOWED (Beta item) + field.Invalid(field.NewPath("chainedSubfieldSetBeta").Index(0).Child("value"), 9, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/zz_generated.validations.go new file mode 100644 index 0000000000..cacb1561e3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/listset/zz_generated.validations.go @@ -0,0 +1,322 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package listset + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ListSetStruct + scheme.AddValidationFunc( + (*ListSetStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ListSetStruct( + ctx, op, nil, /* fldPath */ + obj.(*ListSetStruct), + safe.Cast[*ListSetStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ComplexSetItem validates an instance of ComplexSetItem according +// to declarative validation rules in the API schema. +func Validate_ComplexSetItem( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ComplexSetItem) (errs field.ErrorList) { + + { // field ComplexSetItem.Value + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ComplexSetItem) *int { + return &oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), &obj.Value, oldVal, oldObj != nil)...) + } + + // field ComplexSetItem.StringVal has no validation + return errs +} + +// Validate_ComplexSetItemBeta validates an instance of ComplexSetItemBeta according +// to declarative validation rules in the API schema. +func Validate_ComplexSetItemBeta( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ComplexSetItemBeta) (errs field.ErrorList) { + + { // field ComplexSetItemBeta.Value + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ComplexSetItemBeta) *int { + return &oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), &obj.Value, oldVal, oldObj != nil)...) + } + + // field ComplexSetItemBeta.StringVal has no validation + return errs +} + +// Validate_ListSetStruct validates an instance of ListSetStruct according +// to declarative validation rules in the API schema. +func Validate_ListSetStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ListSetStruct) (errs field.ErrorList) { + + // field ListSetStruct.TypeMeta has no validation + + { // field ListSetStruct.Set + fn := func( + fldPath *field.Path, + obj, oldObj []ComplexSetItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItem); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListSetStruct) []ComplexSetItem { + return oldObj.Set + }) + errs = append(errs, fn(fldPath.Child("set"), obj.Set, oldVal, oldObj != nil)...) + } + + { // field ListSetStruct.BetaSet + fn := func( + fldPath *field.Path, + obj, oldObj []ComplexSetItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItem); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListSetStruct) []ComplexSetItem { + return oldObj.BetaSet + }) + errs = append(errs, fn(fldPath.Child("betaSet"), obj.BetaSet, oldVal, oldObj != nil)...) + } + + { // field ListSetStruct.ChainedSubfieldSet + fn := func( + fldPath *field.Path, + obj, oldObj []SimpleSetItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SimpleSetItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *SimpleSetItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha() + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListSetStruct) []SimpleSetItem { + return oldObj.ChainedSubfieldSet + }) + errs = append(errs, fn(fldPath.Child("chainedSubfieldSet"), obj.ChainedSubfieldSet, oldVal, oldObj != nil)...) + } + + { // field ListSetStruct.SetBetaItem + fn := func( + fldPath *field.Path, + obj, oldObj []ComplexSetItemBeta, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItemBeta); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListSetStruct) []ComplexSetItemBeta { + return oldObj.SetBetaItem + }) + errs = append(errs, fn(fldPath.Child("setBetaItem"), obj.SetBetaItem, oldVal, oldObj != nil)...) + } + + { // field ListSetStruct.BetaSetBetaItem + fn := func( + fldPath *field.Path, + obj, oldObj []ComplexSetItemBeta, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, Validate_ComplexSetItemBeta); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListSetStruct) []ComplexSetItemBeta { + return oldObj.BetaSetBetaItem + }) + errs = append(errs, fn(fldPath.Child("betaSetBetaItem"), obj.BetaSetBetaItem, oldVal, oldObj != nil)...) + } + + { // field ListSetStruct.ChainedSubfieldSetBeta + fn := func( + fldPath *field.Path, + obj, oldObj []SimpleSetItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SimpleSetItem) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *SimpleSetItem) *int { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta() + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ListSetStruct) []SimpleSetItem { + return oldObj.ChainedSubfieldSetBeta + }) + errs = append(errs, fn(fldPath.Child("chainedSubfieldSetBeta"), obj.ChainedSubfieldSetBeta, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc.go new file mode 100644 index 0000000000..a69b892f17 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc.go @@ -0,0 +1,61 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package mapvalidation + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type MapValidationStruct struct { + TypeMeta int + + // Case: Standard eachVal + // +k8s:eachVal=+k8s:maxLength=2 + StandardEachVal map[string]string `json:"standardEachVal"` + + // Case: Alpha eachVal + // +k8s:alpha=+k8s:eachVal=+k8s:maxLength=2 + AlphaEachVal map[string]string `json:"AlphaEachVal"` + + // Case: Beta eachVal + // +k8s:beta=+k8s:eachVal=+k8s:maxLength=2 + BetaEachVal map[string]string `json:"BetaEachVal"` + + // Case: Standard eachKey + // +k8s:eachKey=+k8s:maxLength=2 + StandardEachKey map[string]string `json:"standardEachKey"` + + // Case: Alpha eachKey + // +k8s:alpha=+k8s:eachKey=+k8s:maxLength=2 + AlphaEachKey map[string]string `json:"AlphaEachKey"` + + // Case: Beta eachKey + // +k8s:beta=+k8s:eachKey=+k8s:maxLength=2 + BetaEachKey map[string]string `json:"BetaEachKey"` + + // Case: Alpha Validation + // +k8s:eachVal=+k8s:alpha=+k8s:maxLength=2 + AlphaValidation map[string]string `json:"AlphaValidation"` + + // Case: Beta Validation + // +k8s:eachVal=+k8s:beta=+k8s:maxLength=2 + BetaValidation map[string]string `json:"BetaValidation"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc_test.go new file mode 100644 index 0000000000..b8239c06e5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/doc_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2025 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 mapvalidation + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestMapValidation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&MapValidationStruct{ + StandardEachVal: map[string]string{"a": "foo"}, + AlphaEachVal: map[string]string{"a": "foo"}, + BetaEachVal: map[string]string{"a": "foo"}, + StandardEachKey: map[string]string{"foo": "a"}, + AlphaEachKey: map[string]string{"foo": "a"}, + BetaEachKey: map[string]string{"foo": "a"}, + AlphaValidation: map[string]string{"a": "foo"}, + BetaValidation: map[string]string{"a": "foo"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + // Case: Standard eachVal -> Normal Error + field.TooLong(field.NewPath("standardEachVal").Key("a"), "foo", 2).WithOrigin("maxLength"), + + // Case: Alpha eachVal -> Alpha Error + field.TooLong(field.NewPath("AlphaEachVal").Key("a"), "foo", 2).WithOrigin("maxLength").MarkAlpha(), + + // Case: Beta eachVal -> Beta Error + field.TooLong(field.NewPath("BetaEachVal").Key("a"), "foo", 2).WithOrigin("maxLength").MarkBeta(), + + // Case: Standard eachKey -> Normal Error + field.TooLong(field.NewPath("standardEachKey"), "foo", 2).WithOrigin("maxLength"), + + // Case: Alpha eachKey -> Alpha Error + field.TooLong(field.NewPath("AlphaEachKey"), "foo", 2).WithOrigin("maxLength").MarkAlpha(), + + // Case: Beta eachKey -> Beta Error + field.TooLong(field.NewPath("BetaEachKey"), "foo", 2).WithOrigin("maxLength").MarkBeta(), + + // Case: Alpha Validation -> Alpha Error + field.TooLong(field.NewPath("AlphaValidation").Key("a"), "foo", 2).WithOrigin("maxLength").MarkAlpha(), + + // Case: Beta Validation -> Beta Error + field.TooLong(field.NewPath("BetaValidation").Key("a"), "foo", 2).WithOrigin("maxLength").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/zz_generated.validations.go new file mode 100644 index 0000000000..1de20c27d7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/mapvalidation/zz_generated.validations.go @@ -0,0 +1,284 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mapvalidation + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type MapValidationStruct + scheme.AddValidationFunc( + (*MapValidationStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MapValidationStruct( + ctx, op, nil, /* fldPath */ + obj.(*MapValidationStruct), + safe.Cast[*MapValidationStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_MapValidationStruct validates an instance of MapValidationStruct according +// to declarative validation rules in the API schema. +func Validate_MapValidationStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MapValidationStruct) (errs field.ErrorList) { + + // field MapValidationStruct.TypeMeta has no validation + + { // field MapValidationStruct.StandardEachVal + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MapValidationStruct) map[string]string { + return oldObj.StandardEachVal + }) + errs = append(errs, fn(fldPath.Child("standardEachVal"), obj.StandardEachVal, oldVal, oldObj != nil)...) + } + + { // field MapValidationStruct.AlphaEachVal + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MapValidationStruct) map[string]string { + return oldObj.AlphaEachVal + }) + errs = append(errs, fn(fldPath.Child("AlphaEachVal"), obj.AlphaEachVal, oldVal, oldObj != nil)...) + } + + { // field MapValidationStruct.BetaEachVal + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MapValidationStruct) map[string]string { + return oldObj.BetaEachVal + }) + errs = append(errs, fn(fldPath.Child("BetaEachVal"), obj.BetaEachVal, oldVal, oldObj != nil)...) + } + + { // field MapValidationStruct.StandardEachKey + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MapValidationStruct) map[string]string { + return oldObj.StandardEachKey + }) + errs = append(errs, fn(fldPath.Child("standardEachKey"), obj.StandardEachKey, oldVal, oldObj != nil)...) + } + + { // field MapValidationStruct.AlphaEachKey + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MapValidationStruct) map[string]string { + return oldObj.AlphaEachKey + }) + errs = append(errs, fn(fldPath.Child("AlphaEachKey"), obj.AlphaEachKey, oldVal, oldObj != nil)...) + } + + { // field MapValidationStruct.BetaEachKey + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2) + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MapValidationStruct) map[string]string { + return oldObj.BetaEachKey + }) + errs = append(errs, fn(fldPath.Child("BetaEachKey"), obj.BetaEachKey, oldVal, oldObj != nil)...) + } + + { // field MapValidationStruct.AlphaValidation + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha() + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MapValidationStruct) map[string]string { + return oldObj.AlphaValidation + }) + errs = append(errs, fn(fldPath.Child("AlphaValidation"), obj.AlphaValidation, oldVal, oldObj != nil)...) + } + + { // field MapValidationStruct.BetaValidation + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 2).MarkBeta() + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MapValidationStruct) map[string]string { + return oldObj.BetaValidation + }) + errs = append(errs, fn(fldPath.Child("BetaValidation"), obj.BetaValidation, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/doc.go new file mode 100644 index 0000000000..7492f5d075 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/doc.go @@ -0,0 +1,103 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package modes + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type AlphaStruct struct { + TypeMeta int + + // +k8s:alpha=+k8s:modeDiscriminator + D1 string `json:"d1"` + + // +k8s:alpha=+k8s:ifMode("A")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:alpha=+k8s:ifMode("B")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type BetaStruct struct { + TypeMeta int + + // +k8s:beta=+k8s:modeDiscriminator + D1 string `json:"d1"` + + // +k8s:beta=+k8s:ifMode("A")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:beta=+k8s:ifMode("B")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type MixedLevels struct { + TypeMeta int + + // +k8s:modeDiscriminator + Mode string `json:"mode"` + + // +k8s:alpha=+k8s:ifMode("A")=+k8s:required + A *string `json:"a,omitempty"` + + // +k8s:beta=+k8s:ifMode("B")=+k8s:required + B *string `json:"b,omitempty"` +} + +type CrossLevels struct { + TypeMeta int + + // +k8s:beta=+k8s:modeDiscriminator + Kind string `json:"kind"` + + // +k8s:alpha=+k8s:ifMode("A")=+k8s:required + A *string `json:"a,omitempty"` + + // +k8s:alpha=+k8s:ifMode("B")=+k8s:required + B *string `json:"b,omitempty"` +} + +type SameFieldMixed struct { + TypeMeta int + + // +k8s:modeDiscriminator + Mode string `json:"mode"` + + // +k8s:alpha=+k8s:ifMode("A")=+k8s:required + // +k8s:beta=+k8s:ifMode("B")=+k8s:required + Value *string `json:"value,omitempty"` +} + +// SameValueMixedPayloads tests that multiple payload validations on the same +// discriminator value can have different stability levels. +type SameValueMixedPayloads struct { + TypeMeta int + + // +k8s:modeDiscriminator + Mode string `json:"mode"` + + // +k8s:alpha=+k8s:ifMode("A")=+k8s:required + // +k8s:beta=+k8s:ifMode("A")=+k8s:minLength=3 + Value *string `json:"value,omitempty"` +} + +type TypeMeta int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/doc_test.go new file mode 100644 index 0000000000..5b00f8e18f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/doc_test.go @@ -0,0 +1,162 @@ +/* +Copyright 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 modes + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid: mode A with FieldA set + st.Value(&AlphaStruct{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + + // Invalid: mode A with FieldA missing (required), should be stability level alpha + st.Value(&AlphaStruct{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), "").MarkAlpha(), + }) + + // Invalid: mode A with FieldB set (forbidden), should be stability level alpha + st.Value(&AlphaStruct{D1: "A", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldB"), "").MarkAlpha(), + }) + + // Valid: mode B with FieldB set + st.Value(&AlphaStruct{D1: "B", FieldB: ptr.To("val")}).ExpectValid() + + // Invalid: mode B with FieldB missing (required), should be stability level alpha + st.Value(&AlphaStruct{D1: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("fieldB"), "").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid: mode A with FieldA set + st.Value(&BetaStruct{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + + // Invalid: mode A with FieldA missing (required), should be stability level beta + st.Value(&BetaStruct{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), "").MarkBeta(), + }) + + // Invalid: mode A with FieldB set (forbidden), should be stability level beta + st.Value(&BetaStruct{D1: "A", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldB"), "").MarkBeta(), + }) + + // Valid: mode B with FieldB set + st.Value(&BetaStruct{D1: "B", FieldB: ptr.To("val")}).ExpectValid() + + // Invalid: mode B with FieldB missing (required), should be stability level beta + st.Value(&BetaStruct{D1: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("fieldB"), "").MarkBeta(), + }) +} + +func TestMixedLevels(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid cases + st.Value(&MixedLevels{Mode: "A", A: ptr.To("val")}).ExpectValid() + st.Value(&MixedLevels{Mode: "B", B: ptr.To("val")}).ExpectValid() + + // Mode=A, missing A -> alpha error (field A is alpha-gated) + st.Value(&MixedLevels{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("a"), "").MarkAlpha(), + }) + + // Mode=B, missing B -> beta error (field B is beta-gated) + st.Value(&MixedLevels{Mode: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("b"), "").MarkBeta(), + }) + + // Mode=A with B set (forbidden) -> beta error (field B's +k8s:modeDiscriminator is beta) + st.Value(&MixedLevels{Mode: "A", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("b"), "").MarkBeta(), + }) + + // Mode=B with A set (forbidden) -> alpha error (field A's +k8s:modeDiscriminator is alpha) + st.Value(&MixedLevels{Mode: "B", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("a"), "").MarkAlpha(), + }) +} + +func TestCrossLevels(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid cases + st.Value(&CrossLevels{Kind: "A", A: ptr.To("val")}).ExpectValid() + st.Value(&CrossLevels{Kind: "B", B: ptr.To("val")}).ExpectValid() + + // Kind=A, missing A -> alpha error (field A is alpha-gated, discriminator is beta) + st.Value(&CrossLevels{Kind: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("a"), "").MarkAlpha(), + }) + + // Kind=B, missing B -> alpha error (field B is alpha-gated, discriminator is beta) + st.Value(&CrossLevels{Kind: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("b"), "").MarkAlpha(), + }) + + // Kind=A with B set (forbidden) -> alpha error (field B's member tag is alpha) + st.Value(&CrossLevels{Kind: "A", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Forbidden(field.NewPath("b"), "").MarkAlpha(), + }) +} + +func TestSameFieldMixed(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid cases + st.Value(&SameFieldMixed{Mode: "A", Value: ptr.To("val")}).ExpectValid() + st.Value(&SameFieldMixed{Mode: "B", Value: ptr.To("val")}).ExpectValid() + + // Mode=A, missing Value -> alpha error (member("A") is alpha-gated) + st.Value(&SameFieldMixed{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("value"), "").MarkAlpha(), + }) + + // Mode=B, missing Value -> beta error (member("B") is beta-gated) + st.Value(&SameFieldMixed{Mode: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("value"), "").MarkBeta(), + }) +} + +// TestSameValueMixedPayloads verifies that multiple payload validations on +// the same discriminator value can have different stability levels. +func TestSameValueMixedPayloads(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid: Mode=A with Value set and long enough + st.Value(&SameValueMixedPayloads{Mode: "A", Value: ptr.To("abc")}).ExpectValid() + + // Mode=A, missing Value -> alpha error (required is alpha-gated) + st.Value(&SameValueMixedPayloads{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("value"), "").MarkAlpha(), + }) + + // Mode=A, Value too short -> beta error (minLength is beta-gated) + st.Value(&SameValueMixedPayloads{Mode: "A", Value: ptr.To("ab")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.TooShort(field.NewPath("value"), "", 3).MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/zz_generated.validations.go new file mode 100644 index 0000000000..d04788c865 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/modes/zz_generated.validations.go @@ -0,0 +1,510 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package modes + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type AlphaStruct + scheme.AddValidationFunc( + (*AlphaStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_AlphaStruct( + ctx, op, nil, /* fldPath */ + obj.(*AlphaStruct), + safe.Cast[*AlphaStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type BetaStruct + scheme.AddValidationFunc( + (*BetaStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_BetaStruct( + ctx, op, nil, /* fldPath */ + obj.(*BetaStruct), + safe.Cast[*BetaStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type CrossLevels + scheme.AddValidationFunc( + (*CrossLevels)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_CrossLevels( + ctx, op, nil, /* fldPath */ + obj.(*CrossLevels), + safe.Cast[*CrossLevels](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MixedLevels + scheme.AddValidationFunc( + (*MixedLevels)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MixedLevels( + ctx, op, nil, /* fldPath */ + obj.(*MixedLevels), + safe.Cast[*MixedLevels](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type SameFieldMixed + scheme.AddValidationFunc( + (*SameFieldMixed)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_SameFieldMixed( + ctx, op, nil, /* fldPath */ + obj.(*SameFieldMixed), + safe.Cast[*SameFieldMixed](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type SameValueMixedPayloads + scheme.AddValidationFunc( + (*SameValueMixedPayloads)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_SameValueMixedPayloads( + ctx, op, nil, /* fldPath */ + obj.(*SameValueMixedPayloads), + safe.Cast[*SameValueMixedPayloads](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_AlphaStruct validates an instance of AlphaStruct according +// to declarative validation rules in the API schema. +func Validate_AlphaStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *AlphaStruct) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *AlphaStruct) *string { return obj.FieldA }, + func(obj *AlphaStruct) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", + func(obj *AlphaStruct) *string { return obj.FieldB }, + func(obj *AlphaStruct) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field AlphaStruct.TypeMeta has no validation + // field AlphaStruct.D1 has no validation + // field AlphaStruct.FieldA has no validation + // field AlphaStruct.FieldB has no validation + return errs +} + +// Validate_BetaStruct validates an instance of BetaStruct according +// to declarative validation rules in the API schema. +func Validate_BetaStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *BetaStruct) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *BetaStruct) *string { return obj.FieldA }, + func(obj *BetaStruct) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", + func(obj *BetaStruct) *string { return obj.FieldB }, + func(obj *BetaStruct) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field BetaStruct.TypeMeta has no validation + // field BetaStruct.D1 has no validation + // field BetaStruct.FieldA has no validation + // field BetaStruct.FieldB has no validation + return errs +} + +// Validate_CrossLevels validates an instance of CrossLevels according +// to declarative validation rules in the API schema. +func Validate_CrossLevels( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *CrossLevels) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "a", + func(obj *CrossLevels) *string { return obj.A }, + func(obj *CrossLevels) string { return obj.Kind }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "b", + func(obj *CrossLevels) *string { return obj.B }, + func(obj *CrossLevels) string { return obj.Kind }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field CrossLevels.TypeMeta has no validation + // field CrossLevels.Kind has no validation + // field CrossLevels.A has no validation + // field CrossLevels.B has no validation + return errs +} + +// Validate_MixedLevels validates an instance of MixedLevels according +// to declarative validation rules in the API schema. +func Validate_MixedLevels( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MixedLevels) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "a", + func(obj *MixedLevels) *string { return obj.A }, + func(obj *MixedLevels) string { return obj.Mode }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha()...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "b", + func(obj *MixedLevels) *string { return obj.B }, + func(obj *MixedLevels) string { return obj.Mode }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta()...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field MixedLevels.TypeMeta has no validation + // field MixedLevels.Mode has no validation + // field MixedLevels.A has no validation + // field MixedLevels.B has no validation + return errs +} + +// Validate_SameFieldMixed validates an instance of SameFieldMixed according +// to declarative validation rules in the API schema. +func Validate_SameFieldMixed( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *SameFieldMixed) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "value", + func(obj *SameFieldMixed) *string { return obj.Value }, + func(obj *SameFieldMixed) string { return obj.Mode }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field SameFieldMixed.TypeMeta has no validation + // field SameFieldMixed.Mode has no validation + // field SameFieldMixed.Value has no validation + return errs +} + +// Validate_SameValueMixedPayloads validates an instance of SameValueMixedPayloads according +// to declarative validation rules in the API schema. +func Validate_SameValueMixedPayloads( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *SameValueMixedPayloads) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "value", + func(obj *SameValueMixedPayloads) *string { return obj.Value }, + func(obj *SameValueMixedPayloads) string { return obj.Mode }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 3).MarkBeta()...) + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field SameValueMixedPayloads.TypeMeta has no validation + // field SameValueMixedPayloads.Mode has no validation + // field SameValueMixedPayloads.Value has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc.go new file mode 100644 index 0000000000..f9e86247f7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc.go @@ -0,0 +1,35 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package optionalrequired + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:required + RequiredField *string `json:"requiredField"` + + // +k8s:beta=+k8s:required + RequiredFieldBeta *string `json:"requiredFieldBeta"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc_test.go new file mode 100644 index 0000000000..92cb8ee44c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/doc_test.go @@ -0,0 +1,64 @@ +/* +Copyright 2025 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 optionalrequired + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + RequiredField: ptr.To("val"), + RequiredFieldBeta: ptr.To("val"), + }).ExpectValid() + + // Test failures marked as alpha + st.Value(&Struct{ + RequiredField: nil, + RequiredFieldBeta: ptr.To("val"), + }).OldValue(&Struct{ + RequiredField: ptr.To("old"), + RequiredFieldBeta: ptr.To("old"), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("requiredField"), "").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + RequiredField: ptr.To("val"), + RequiredFieldBeta: ptr.To("val"), + }).ExpectValid() + + // Test failures marked as beta + st.Value(&Struct{ + RequiredField: ptr.To("val"), + RequiredFieldBeta: nil, + }).OldValue(&Struct{ + RequiredField: ptr.To("old"), + RequiredFieldBeta: ptr.To("old"), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Required(field.NewPath("requiredFieldBeta"), "").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/zz_generated.validations.go new file mode 100644 index 0000000000..4e5bd59230 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/optionalrequired/zz_generated.validations.go @@ -0,0 +1,125 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package optionalrequired + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.RequiredField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.RequiredField + }) + errs = append(errs, fn(fldPath.Child("requiredField"), obj.RequiredField, oldVal, oldObj != nil)...) + } + + { // field Struct.RequiredFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkBeta().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.RequiredFieldBeta + }) + errs = append(errs, fn(fldPath.Child("requiredFieldBeta"), obj.RequiredFieldBeta, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc.go new file mode 100644 index 0000000000..e7a66fcd53 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc.go @@ -0,0 +1,91 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package simple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:minimum=10 + IntField int `json:"intField"` + + // +k8s:beta=+k8s:minimum=10 + IntFieldBeta int `json:"intFieldBeta"` + + // +k8s:alpha=+k8s:maxLength=5 + StringField string `json:"stringField"` + + // +k8s:beta=+k8s:maxLength=5 + StringFieldBeta string `json:"stringFieldBeta"` + + // +k8s:alpha=+k8s:maxItems=2 + SliceField []string `json:"sliceField"` + + // +k8s:beta=+k8s:maxItems=2 + SliceFieldBeta []string `json:"sliceFieldBeta"` + + // +k8s:alpha=+k8s:format=k8s-uuid + UUIDField string `json:"uuidField"` + + // +k8s:beta=+k8s:format=k8s-uuid + UUIDFieldBeta string `json:"uuidFieldBeta"` + + // +k8s:alpha=+k8s:immutable + ImmutableField string `json:"immutableField"` + + // +k8s:beta=+k8s:immutable + ImmutableFieldBeta string `json:"immutableFieldBeta"` +} + +type SpecialValidationStruct struct { + TypeMeta int + + // +k8s:alpha=+k8s:neq=5 + NEQField int `json:"neqField"` + + // +k8s:beta=+k8s:neq=5 + NEQFieldBeta int `json:"neqFieldBeta"` + + // +k8s:alpha=+k8s:forbidden + ForbiddenField *string `json:"forbiddenField"` + + // +k8s:beta=+k8s:forbidden + ForbiddenFieldBeta *string `json:"forbiddenFieldBeta"` + + // +k8s:alpha=+k8s:update=NoModify + UpdateField string `json:"updateField"` + + // +k8s:beta=+k8s:update=NoModify + UpdateFieldBeta string `json:"updateFieldBeta"` +} + +type StructWithValidateFalse struct { + TypeMeta int + + // +k8s:alpha=+k8s:validateFalse="always fails" + ValidateFalse *string `json:"validateFalse"` + + // +k8s:beta=+k8s:validateFalse="always fails" + ValidateFalseBeta *string `json:"validateFalseBeta"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc_test.go new file mode 100644 index 0000000000..d768f825c4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/doc_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2025 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 simple + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + IntField: 10, + StringField: "abc", + SliceField: []string{"a", "b"}, + UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableField: "foo", + IntFieldBeta: 10, + StringFieldBeta: "abc", + SliceFieldBeta: []string{"a", "b"}, + UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableFieldBeta: "foo", + }).ExpectValid() + + // Test failures marked as alpha + st.Value(&Struct{ + IntField: 5, + StringField: "too-long", + SliceField: []string{"a", "b", "c"}, + UUIDField: "not-a-uuid", + ImmutableField: "bar", + IntFieldBeta: 10, + StringFieldBeta: "abc", + SliceFieldBeta: []string{"a", "b"}, + UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableFieldBeta: "foo", + }).OldValue(&Struct{ + ImmutableField: "foo", + ImmutableFieldBeta: "foo", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), 5, "").WithOrigin("minimum").MarkAlpha(), + field.TooLong(field.NewPath("stringField"), "too-long", 5).WithOrigin("maxLength").MarkAlpha(), + field.TooMany(field.NewPath("sliceField"), 3, 2).WithOrigin("maxItems").MarkAlpha(), + field.Invalid(field.NewPath("uuidField"), "not-a-uuid", "").WithOrigin("format=k8s-uuid").MarkAlpha(), + field.Invalid(field.NewPath("immutableField"), "bar", "").WithOrigin("immutable").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + IntField: 10, + StringField: "abc", + SliceField: []string{"a", "b"}, + UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableField: "foo", + IntFieldBeta: 10, + StringFieldBeta: "abc", + SliceFieldBeta: []string{"a", "b"}, + UUIDFieldBeta: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableFieldBeta: "foo", + }).ExpectValid() + + // Test failures marked as beta + st.Value(&Struct{ + IntField: 10, + StringField: "abc", + SliceField: []string{"a", "b"}, + UUIDField: "a0a2a2d2-0b87-4964-a123-78d00a8787a6", + ImmutableField: "foo", + IntFieldBeta: 5, + StringFieldBeta: "too-long", + SliceFieldBeta: []string{"a", "b", "c"}, + UUIDFieldBeta: "not-a-uuid", + ImmutableFieldBeta: "bar", + }).OldValue(&Struct{ + ImmutableField: "foo", + ImmutableFieldBeta: "foo", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("intFieldBeta"), 5, "").WithOrigin("minimum").MarkBeta(), + field.TooLong(field.NewPath("stringFieldBeta"), "too-long", 5).WithOrigin("maxLength").MarkBeta(), + field.TooMany(field.NewPath("sliceFieldBeta"), 3, 2).WithOrigin("maxItems").MarkBeta(), + field.Invalid(field.NewPath("uuidFieldBeta"), "not-a-uuid", "").WithOrigin("format=k8s-uuid").MarkBeta(), + field.Invalid(field.NewPath("immutableFieldBeta"), "bar", "").WithOrigin("immutable").MarkBeta(), + }) +} + +func TestSpecialValidationStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&SpecialValidationStruct{ + NEQField: 10, + NEQFieldBeta: 10, + }).ExpectValid() + + st.Value(&SpecialValidationStruct{ + NEQField: 5, + NEQFieldBeta: 5, + ForbiddenField: ptr.To("val"), + ForbiddenFieldBeta: ptr.To("val"), + UpdateField: "new", + UpdateFieldBeta: "new", + }).OldValue(&SpecialValidationStruct{ + UpdateField: "old", + UpdateFieldBeta: "old", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("neqField"), 5, "must not be 5").WithOrigin("neq").MarkAlpha(), + field.Invalid(field.NewPath("neqFieldBeta"), 5, "must not be 5").WithOrigin("neq").MarkBeta(), + field.Forbidden(field.NewPath("forbiddenField"), "").MarkAlpha(), + field.Forbidden(field.NewPath("forbiddenFieldBeta"), "").MarkBeta(), + field.Invalid(field.NewPath("updateField"), "new", "field cannot be modified once set").WithOrigin("update").MarkAlpha(), + field.Invalid(field.NewPath("updateFieldBeta"), "new", "field cannot be modified once set").WithOrigin("update").MarkBeta(), + }) + + st.Value(&StructWithValidateFalse{ + ValidateFalse: ptr.To("val"), + ValidateFalseBeta: ptr.To("val"), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("validateFalse"), "val", "always fails").MarkAlpha(), + field.Invalid(field.NewPath("validateFalseBeta"), "val", "always fails").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go new file mode 100644 index 0000000000..c176df16c8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/simple/zz_generated.validations.go @@ -0,0 +1,600 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package simple + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type SpecialValidationStruct + scheme.AddValidationFunc( + (*SpecialValidationStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_SpecialValidationStruct( + ctx, op, nil, /* fldPath */ + obj.(*SpecialValidationStruct), + safe.Cast[*SpecialValidationStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructWithValidateFalse + scheme.AddValidationFunc( + (*StructWithValidateFalse)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructWithValidateFalse( + ctx, op, nil, /* fldPath */ + obj.(*StructWithValidateFalse), + safe.Cast[*StructWithValidateFalse](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_SpecialValidationStruct validates an instance of SpecialValidationStruct according +// to declarative validation rules in the API schema. +func Validate_SpecialValidationStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *SpecialValidationStruct) (errs field.ErrorList) { + + // field SpecialValidationStruct.TypeMeta has no validation + + { // field SpecialValidationStruct.NEQField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, 5).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *SpecialValidationStruct) *int { + return &oldObj.NEQField + }) + errs = append(errs, fn(fldPath.Child("neqField"), &obj.NEQField, oldVal, oldObj != nil)...) + } + + { // field SpecialValidationStruct.NEQFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, 5).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *SpecialValidationStruct) *int { + return &oldObj.NEQFieldBeta + }) + errs = append(errs, fn(fldPath.Child("neqFieldBeta"), &obj.NEQFieldBeta, oldVal, oldObj != nil)...) + } + + { // field SpecialValidationStruct.ForbiddenField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *SpecialValidationStruct) *string { + return oldObj.ForbiddenField + }) + errs = append(errs, fn(fldPath.Child("forbiddenField"), obj.ForbiddenField, oldVal, oldObj != nil)...) + } + + { // field SpecialValidationStruct.ForbiddenFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkBeta().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkBeta().MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *SpecialValidationStruct) *string { + return oldObj.ForbiddenFieldBeta + }) + errs = append(errs, fn(fldPath.Child("forbiddenFieldBeta"), obj.ForbiddenFieldBeta, oldVal, oldObj != nil)...) + } + + { // field SpecialValidationStruct.UpdateField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *SpecialValidationStruct) *string { + return &oldObj.UpdateField + }) + errs = append(errs, fn(fldPath.Child("updateField"), &obj.UpdateField, oldVal, oldObj != nil)...) + } + + { // field SpecialValidationStruct.UpdateFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoModify).MarkBeta().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *SpecialValidationStruct) *string { + return &oldObj.UpdateFieldBeta + }) + errs = append(errs, fn(fldPath.Child("updateFieldBeta"), &obj.UpdateFieldBeta, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntFieldBeta + }) + errs = append(errs, fn(fldPath.Child("intFieldBeta"), &obj.IntFieldBeta, oldVal, oldObj != nil)...) + } + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringFieldBeta + }) + errs = append(errs, fn(fldPath.Child("stringFieldBeta"), &obj.StringFieldBeta, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkBeta().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceFieldBeta + }) + errs = append(errs, fn(fldPath.Child("sliceFieldBeta"), obj.SliceFieldBeta, oldVal, oldObj != nil)...) + } + + { // field Struct.UUIDField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.UUID(ctx, op, fldPath, obj, oldObj).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.UUIDField + }) + errs = append(errs, fn(fldPath.Child("uuidField"), &obj.UUIDField, oldVal, oldObj != nil)...) + } + + { // field Struct.UUIDFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.UUID(ctx, op, fldPath, obj, oldObj).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.UUIDFieldBeta + }) + errs = append(errs, fn(fldPath.Child("uuidFieldBeta"), &obj.UUIDFieldBeta, oldVal, oldObj != nil)...) + } + + { // field Struct.ImmutableField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.ImmutableField + }) + errs = append(errs, fn(fldPath.Child("immutableField"), &obj.ImmutableField, oldVal, oldObj != nil)...) + } + + { // field Struct.ImmutableFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkBeta().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.ImmutableFieldBeta + }) + errs = append(errs, fn(fldPath.Child("immutableFieldBeta"), &obj.ImmutableFieldBeta, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_StructWithValidateFalse validates an instance of StructWithValidateFalse according +// to declarative validation rules in the API schema. +func Validate_StructWithValidateFalse( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructWithValidateFalse) (errs field.ErrorList) { + + // field StructWithValidateFalse.TypeMeta has no validation + + { // field StructWithValidateFalse.ValidateFalse + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "always fails").MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructWithValidateFalse) *string { + return oldObj.ValidateFalse + }) + errs = append(errs, fn(fldPath.Child("validateFalse"), obj.ValidateFalse, oldVal, oldObj != nil)...) + } + + { // field StructWithValidateFalse.ValidateFalseBeta + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "always fails").MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructWithValidateFalse) *string { + return oldObj.ValidateFalseBeta + }) + errs = append(errs, fn(fldPath.Child("validateFalseBeta"), obj.ValidateFalseBeta, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc.go new file mode 100644 index 0000000000..5a5aeecfe7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc.go @@ -0,0 +1,61 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package structs + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type MixedStruct struct { + TypeMeta int + + // +k8s:minimum=5 + // +k8s:alpha=+k8s:minimum=10 + IntField int `json:"intField"` + + // +k8s:minimum=5 + // +k8s:beta=+k8s:minimum=10 + IntFieldBeta int `json:"intFieldBeta"` + + // +k8s:maxItems=5 + // +k8s:alpha=+k8s:maxItems=3 + ListField []string `json:"listField"` + + // +k8s:maxItems=5 + // +k8s:beta=+k8s:maxItems=3 + ListFieldBeta []string `json:"listFieldBeta"` +} + +type ConditionalStruct struct { + TypeMeta int + + // +k8s:alpha=+k8s:ifEnabled(MyFeature)=+k8s:minimum=10 + ConditionalField int `json:"conditionalField"` + + // +k8s:beta=+k8s:ifEnabled(MyFeature)=+k8s:minimum=10 + ConditionalFieldBeta int `json:"conditionalFieldBeta"` + + // +k8s:alpha=+k8s:alpha=+k8s:minimum=20 + RecursiveAlpha int `json:"recursiveAlpha"` + + // +k8s:beta=+k8s:beta=+k8s:minimum=20 + RecursiveBeta int `json:"recursiveBeta"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc_test.go new file mode 100644 index 0000000000..6f16c50071 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/doc_test.go @@ -0,0 +1,92 @@ +/* +Copyright 2025 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 structs + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestMixed(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid case (meets both normal and alpha requirements) + st.Value(&MixedStruct{ + IntField: 15, + IntFieldBeta: 15, + ListField: []string{"a", "b", "c"}, + ListFieldBeta: []string{"a", "b", "c"}, + }).ExpectValid() + + // Fails alpha validation but passes normal validation + // IntField: 5 <= 8 < 10 (alpha fails) + // ListField: 3 < 4 <= 5 (alpha fails) + st.Value(&MixedStruct{ + IntField: 8, + IntFieldBeta: 8, + ListField: []string{"a", "b", "c", "d"}, + ListFieldBeta: []string{"a", "b", "c", "d"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), 8, "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(field.NewPath("intFieldBeta"), 8, "").WithOrigin("minimum").MarkBeta(), + field.TooMany(field.NewPath("listField"), 4, 3).WithOrigin("maxItems").MarkAlpha(), + field.TooMany(field.NewPath("listFieldBeta"), 4, 3).WithOrigin("maxItems").MarkBeta(), + }) + + // Fails both normal and alpha validation + // IntField: 4 < 5 (normal fails) AND 4 < 10 (alpha fails) + // ListField: 6 > 5 (normal fails) AND 6 > 3 (alpha fails) + st.Value(&MixedStruct{ + IntField: 4, + IntFieldBeta: 4, + ListField: []string{"a", "b", "c", "d", "e", "f"}, + ListFieldBeta: []string{"a", "b", "c", "d", "e", "f"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), 4, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("intField"), 4, "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(field.NewPath("intFieldBeta"), 4, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("intFieldBeta"), 4, "").WithOrigin("minimum").MarkBeta(), + field.TooMany(field.NewPath("listField"), 6, 5).WithOrigin("maxItems"), + field.TooMany(field.NewPath("listField"), 6, 3).WithOrigin("maxItems").MarkAlpha(), + field.TooMany(field.NewPath("listFieldBeta"), 6, 5).WithOrigin("maxItems"), + field.TooMany(field.NewPath("listFieldBeta"), 6, 3).WithOrigin("maxItems").MarkBeta(), + }) +} + +func TestConditionalStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&ConditionalStruct{ + ConditionalField: 15, + ConditionalFieldBeta: 15, + RecursiveAlpha: 25, + RecursiveBeta: 25, + }).Opts(map[string]bool{"MyFeature": true}).ExpectValid() + + st.Value(&ConditionalStruct{ + ConditionalField: 5, + ConditionalFieldBeta: 5, + RecursiveAlpha: 10, + RecursiveBeta: 10, + }).Opts(map[string]bool{"MyFeature": true}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("conditionalField"), 5, "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(field.NewPath("conditionalFieldBeta"), 5, "").WithOrigin("minimum").MarkBeta(), + field.Invalid(field.NewPath("recursiveAlpha"), 10, "").WithOrigin("minimum").MarkAlpha(), + field.Invalid(field.NewPath("recursiveBeta"), 10, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/zz_generated.validations.go new file mode 100644 index 0000000000..f17ea18372 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/structs/zz_generated.validations.go @@ -0,0 +1,316 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package structs + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ConditionalStruct + scheme.AddValidationFunc( + (*ConditionalStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ConditionalStruct( + ctx, op, nil, /* fldPath */ + obj.(*ConditionalStruct), + safe.Cast[*ConditionalStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MixedStruct + scheme.AddValidationFunc( + (*MixedStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MixedStruct( + ctx, op, nil, /* fldPath */ + obj.(*MixedStruct), + safe.Cast[*MixedStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ConditionalStruct validates an instance of ConditionalStruct according +// to declarative validation rules in the API schema. +func Validate_ConditionalStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ConditionalStruct) (errs field.ErrorList) { + + // field ConditionalStruct.TypeMeta has no validation + + { // field ConditionalStruct.ConditionalField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "MyFeature", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ConditionalStruct) *int { + return &oldObj.ConditionalField + }) + errs = append(errs, fn(fldPath.Child("conditionalField"), &obj.ConditionalField, oldVal, oldObj != nil)...) + } + + { // field ConditionalStruct.ConditionalFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "MyFeature", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 10) + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ConditionalStruct) *int { + return &oldObj.ConditionalFieldBeta + }) + errs = append(errs, fn(fldPath.Child("conditionalFieldBeta"), &obj.ConditionalFieldBeta, oldVal, oldObj != nil)...) + } + + { // field ConditionalStruct.RecursiveAlpha + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 20).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ConditionalStruct) *int { + return &oldObj.RecursiveAlpha + }) + errs = append(errs, fn(fldPath.Child("recursiveAlpha"), &obj.RecursiveAlpha, oldVal, oldObj != nil)...) + } + + { // field ConditionalStruct.RecursiveBeta + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 20).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ConditionalStruct) *int { + return &oldObj.RecursiveBeta + }) + errs = append(errs, fn(fldPath.Child("recursiveBeta"), &obj.RecursiveBeta, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_MixedStruct validates an instance of MixedStruct according +// to declarative validation rules in the API schema. +func Validate_MixedStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MixedStruct) (errs field.ErrorList) { + + // field MixedStruct.TypeMeta has no validation + + { // field MixedStruct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 5); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MixedStruct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field MixedStruct.IntFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 10).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 5); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MixedStruct) *int { + return &oldObj.IntFieldBeta + }) + errs = append(errs, fn(fldPath.Child("intFieldBeta"), &obj.IntFieldBeta, oldVal, oldObj != nil)...) + } + + { // field MixedStruct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 3).MarkAlpha().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 5).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MixedStruct) []string { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field MixedStruct.ListFieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 3).MarkBeta().MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 5).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MixedStruct) []string { + return oldObj.ListFieldBeta + }) + errs = append(errs, fn(fldPath.Child("listFieldBeta"), obj.ListFieldBeta, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc.go new file mode 100644 index 0000000000..df4b88e397 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc.go @@ -0,0 +1,48 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package subfields + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:subfield(inner)=+k8s:minimum=5 + Subfield SubStruct `json:"subfield"` + + // +k8s:beta=+k8s:subfield(inner)=+k8s:minimum=5 + SubfieldBeta SubStruct `json:"subfieldBeta"` + + // +k8s:alpha=+k8s:subfield(z1)=+k8s:zeroOrOneOfMember + // +k8s:alpha=+k8s:subfield(z2)=+k8s:zeroOrOneOfMember + UnionField SubUnion `json:"unionField"` +} + +type SubStruct struct { + Inner int `json:"inner"` +} + +type SubUnion struct { + Z1 *int `json:"z1"` + Z2 *int `json:"z2"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc_test.go new file mode 100644 index 0000000000..f4e62109e2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/doc_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2025 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 subfields + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Subfield: SubStruct{Inner: 5}, + SubfieldBeta: SubStruct{Inner: 5}, + }).ExpectValid() + + // Test failures marked as alpha + st.Value(&Struct{ + Subfield: SubStruct{Inner: 1}, + SubfieldBeta: SubStruct{Inner: 5}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("subfield", "inner"), 1, "").WithOrigin("minimum").MarkAlpha(), + }) + + one := 1 + st.Value(&Struct{ + Subfield: SubStruct{Inner: 5}, + SubfieldBeta: SubStruct{Inner: 5}, + UnionField: SubUnion{ + Z1: &one, + Z2: &one, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("unionField"), &SubUnion{ + Z1: &one, Z2: &one, + }, "only one of z1, z2 may be specified").WithOrigin("zeroOrOneOf").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + Subfield: SubStruct{Inner: 5}, + SubfieldBeta: SubStruct{Inner: 5}, + }).ExpectValid() + + // Test failures marked as beta + st.Value(&Struct{ + Subfield: SubStruct{Inner: 5}, + SubfieldBeta: SubStruct{Inner: 1}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("subfieldBeta", "inner"), 1, "").WithOrigin("minimum").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/zz_generated.validations.go new file mode 100644 index 0000000000..b316c2b067 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/subfields/zz_generated.validations.go @@ -0,0 +1,166 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package subfields + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_subfields_Struct_unionField_ = validate.NewUnionMembership(validate.NewUnionMember("z1"), validate.NewUnionMember("z2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Subfield + fn := func( + fldPath *field.Path, + obj, oldObj *SubStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "inner" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "inner", + func(o *SubStruct) *int { return &o.Inner }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 5) + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *SubStruct { + return &oldObj.Subfield + }) + errs = append(errs, fn(fldPath.Child("subfield"), &obj.Subfield, oldVal, oldObj != nil)...) + } + + { // field Struct.SubfieldBeta + fn := func( + fldPath *field.Path, + obj, oldObj *SubStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "inner" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "inner", + func(o *SubStruct) *int { return &o.Inner }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *int) field.ErrorList { + return validate.Minimum(ctx, op, fldPath, obj, oldObj, 5) + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *SubStruct { + return &oldObj.SubfieldBeta + }) + errs = append(errs, fn(fldPath.Child("subfieldBeta"), &obj.SubfieldBeta, oldVal, oldObj != nil)...) + } + + { // field Struct.UnionField + fn := func( + fldPath *field.Path, + obj, oldObj *SubUnion, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_subfields_Struct_unionField_, + func(obj *SubUnion) bool { + if obj == nil { + return false + } + return obj.Z1 != nil + }, + func(obj *SubUnion) bool { + if obj == nil { + return false + } + return obj.Z2 != nil + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *SubUnion { + return &oldObj.UnionField + }) + errs = append(errs, fn(fldPath.Child("unionField"), &obj.UnionField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc.go new file mode 100644 index 0000000000..04a441f844 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc.go @@ -0,0 +1,130 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package unions + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:unionDiscriminator + D D `json:"d"` + + // +k8s:alpha=+k8s:unionMember + // +k8s:optional + M1 *M1 `json:"m1"` + + // +k8s:alpha(since:"1.35")=+k8s:unionMember + // +k8s:optional + M2 *M2 `json:"m2"` +} + +type UnionStructBeta struct { + TypeMeta int + + // +k8s:beta=+k8s:unionDiscriminator + DBeta BetaD `json:"dBeta"` + + // +k8s:beta=+k8s:unionMember + // +k8s:optional + M1Beta *BetaM1 `json:"m1Beta"` + + // +k8s:beta(since:"1.35")=+k8s:unionMember + // +k8s:optional + M2Beta *BetaM2 `json:"m2Beta"` +} + +type MyStruct struct { + TypeMeta int + + // +k8s:alpha=+k8s:zeroOrOneOfMember + // +k8s:optional + Z1 *Z1 `json:"z1"` + + // +k8s:alpha=+k8s:zeroOrOneOfMember + // +k8s:optional + Z2 *Z2 `json:"z2"` +} + +type MyStructBeta struct { + TypeMeta int + + // +k8s:beta=+k8s:zeroOrOneOfMember + // +k8s:optional + Z1Beta *BetaZ1 `json:"z1Beta"` + + // +k8s:beta=+k8s:zeroOrOneOfMember + // +k8s:optional + Z2Beta *BetaZ2 `json:"z2Beta"` +} + +type D string + +const ( + DM1 D = "M1" + DM2 D = "M2" +) + +type M1 struct{} +type M2 struct{} + +type BetaD string + +const ( + BetaDM1 BetaD = "M1Beta" + BetaDM2 BetaD = "M2Beta" +) + +type BetaM1 struct{} +type BetaM2 struct{} + +type Z1 struct{} +type Z2 struct{} + +type BetaZ1 struct{} +type BetaZ2 struct{} + +type MyListStruct struct { + TypeMeta int + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:alpha=+k8s:item(name: "succeeded")=+k8s:zeroOrOneOfMember + // +k8s:alpha=+k8s:item(name: "failed")=+k8s:zeroOrOneOfMember + Tasks []Task `json:"tasks"` +} + +type MyListStructBeta struct { + TypeMeta int + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:beta=+k8s:item(name: "succeeded")=+k8s:zeroOrOneOfMember + // +k8s:beta=+k8s:item(name: "failed")=+k8s:zeroOrOneOfMember + TasksBeta []Task `json:"tasksBeta"` +} + +type Task struct { + Name string `json:"name"` + State string `json:"state"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc_test.go new file mode 100644 index 0000000000..a4aa873e75 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/doc_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2025 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 unions + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestAlpha(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + D: DM1, + M1: &M1{}, + }).ExpectValid() + + st.Value(&Struct{ + D: DM1, + M1: nil, // required by discriminator + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "").WithOrigin("union").MarkAlpha(), + }) + + st.Value(&MyStruct{ + Z1: &Z1{}, + Z2: &Z2{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(nil, &MyStruct{ + Z1: &Z1{}, Z2: &Z2{}, + }, "only one of z1, z2 may be specified").WithOrigin("zeroOrOneOf").MarkAlpha(), + }) + + st.Value(&MyListStruct{ + Tasks: []Task{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "failed", State: "Failed"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("tasks"), nil, "").WithOrigin("zeroOrOneOf").MarkAlpha(), + }) +} + +func TestBeta(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&UnionStructBeta{ + DBeta: BetaDM1, + M1Beta: &BetaM1{}, + }).ExpectValid() + + st.Value(&UnionStructBeta{ + DBeta: BetaDM1, + M1Beta: nil, // required by discriminator + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("m1Beta"), nil, "").WithOrigin("union").MarkBeta(), + }) + + st.Value(&MyStructBeta{ + Z1Beta: &BetaZ1{}, + Z2Beta: &BetaZ2{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(nil, &MyStructBeta{ + Z1Beta: &BetaZ1{}, Z2Beta: &BetaZ2{}, + }, "only one of z1Beta, z2Beta may be specified").WithOrigin("zeroOrOneOf").MarkBeta(), + }) + + st.Value(&MyListStructBeta{ + TasksBeta: []Task{ + {Name: "succeeded", State: "Succeeded"}, + {Name: "failed", State: "Failed"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Invalid(field.NewPath("tasksBeta"), nil, "").WithOrigin("zeroOrOneOf").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/zz_generated.validations.go new file mode 100644 index 0000000000..a58f73f119 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/unions/zz_generated.validations.go @@ -0,0 +1,602 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package unions + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type MyListStruct + scheme.AddValidationFunc( + (*MyListStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyListStruct( + ctx, op, nil, /* fldPath */ + obj.(*MyListStruct), + safe.Cast[*MyListStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MyListStructBeta + scheme.AddValidationFunc( + (*MyListStructBeta)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyListStructBeta( + ctx, op, nil, /* fldPath */ + obj.(*MyListStructBeta), + safe.Cast[*MyListStructBeta](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MyStruct + scheme.AddValidationFunc( + (*MyStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyStruct( + ctx, op, nil, /* fldPath */ + obj.(*MyStruct), + safe.Cast[*MyStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MyStructBeta + scheme.AddValidationFunc( + (*MyStructBeta)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MyStructBeta( + ctx, op, nil, /* fldPath */ + obj.(*MyStructBeta), + safe.Cast[*MyStructBeta](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type UnionStructBeta + scheme.AddValidationFunc( + (*UnionStructBeta)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_UnionStructBeta( + ctx, op, nil, /* fldPath */ + obj.(*UnionStructBeta), + safe.Cast[*UnionStructBeta](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyListStruct_tasks_ = validate.NewUnionMembership(validate.NewUnionMember("tasks[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("tasks[{\"name\": \"failed\"}]")) + +// Validate_MyListStruct validates an instance of MyListStruct according +// to declarative validation rules in the API schema. +func Validate_MyListStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MyListStruct) (errs field.ErrorList) { + + // field MyListStruct.TypeMeta has no validation + + { // field MyListStruct.Tasks + fn := func( + fldPath *field.Path, + obj, oldObj []Task, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyListStruct_tasks_, + func(list []Task) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, + func(list []Task) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Task, b *Task) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyListStruct) []Task { + return oldObj.Tasks + }) + errs = append(errs, fn(fldPath.Child("tasks"), obj.Tasks, oldVal, oldObj != nil)...) + } + + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyListStructBeta_tasksBeta_ = validate.NewUnionMembership(validate.NewUnionMember("tasksBeta[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("tasksBeta[{\"name\": \"failed\"}]")) + +// Validate_MyListStructBeta validates an instance of MyListStructBeta according +// to declarative validation rules in the API schema. +func Validate_MyListStructBeta( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MyListStructBeta) (errs field.ErrorList) { + + // field MyListStructBeta.TypeMeta has no validation + + { // field MyListStructBeta.TasksBeta + fn := func( + fldPath *field.Path, + obj, oldObj []Task, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyListStructBeta_tasksBeta_, + func(list []Task) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, + func(list []Task) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Task, b *Task) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyListStructBeta) []Task { + return oldObj.TasksBeta + }) + errs = append(errs, fn(fldPath.Child("tasksBeta"), obj.TasksBeta, oldVal, oldObj != nil)...) + } + + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStruct_ = validate.NewUnionMembership(validate.NewUnionMember("z1"), validate.NewUnionMember("z2")) + +// Validate_MyStruct validates an instance of MyStruct according +// to declarative validation rules in the API schema. +func Validate_MyStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MyStruct) (errs field.ErrorList) { + + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStruct_, + func(obj *MyStruct) bool { + if obj == nil { + return false + } + return obj.Z1 != nil + }, + func(obj *MyStruct) bool { + if obj == nil { + return false + } + return obj.Z2 != nil + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + + // field MyStruct.TypeMeta has no validation + + { // field MyStruct.Z1 + fn := func( + fldPath *field.Path, + obj, oldObj *Z1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyStruct) *Z1 { + return oldObj.Z1 + }) + errs = append(errs, fn(fldPath.Child("z1"), obj.Z1, oldVal, oldObj != nil)...) + } + + { // field MyStruct.Z2 + fn := func( + fldPath *field.Path, + obj, oldObj *Z2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyStruct) *Z2 { + return oldObj.Z2 + }) + errs = append(errs, fn(fldPath.Child("z2"), obj.Z2, oldVal, oldObj != nil)...) + } + + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStructBeta_ = validate.NewUnionMembership(validate.NewUnionMember("z1Beta"), validate.NewUnionMember("z2Beta")) + +// Validate_MyStructBeta validates an instance of MyStructBeta according +// to declarative validation rules in the API schema. +func Validate_MyStructBeta( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MyStructBeta) (errs field.ErrorList) { + + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_MyStructBeta_, + func(obj *MyStructBeta) bool { + if obj == nil { + return false + } + return obj.Z1Beta != nil + }, + func(obj *MyStructBeta) bool { + if obj == nil { + return false + } + return obj.Z2Beta != nil + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + + // field MyStructBeta.TypeMeta has no validation + + { // field MyStructBeta.Z1Beta + fn := func( + fldPath *field.Path, + obj, oldObj *BetaZ1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyStructBeta) *BetaZ1 { + return oldObj.Z1Beta + }) + errs = append(errs, fn(fldPath.Child("z1Beta"), obj.Z1Beta, oldVal, oldObj != nil)...) + } + + { // field MyStructBeta.Z2Beta + fn := func( + fldPath *field.Path, + obj, oldObj *BetaZ2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *MyStructBeta) *BetaZ2 { + return oldObj.Z2Beta + }) + errs = append(errs, fn(fldPath.Child("z2Beta"), obj.Z2Beta, oldVal, oldObj != nil)...) + } + + return errs +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_Struct_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "M1"), validate.NewDiscriminatedUnionMember("m2", "M2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_Struct_, + func(obj *Struct) string { + if obj == nil { + return "" + } + return string(obj.D) + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.D has no validation + + { // field Struct.M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.M1 + }) + errs = append(errs, fn(fldPath.Child("m1"), obj.M1, oldVal, oldObj != nil)...) + } + + { // field Struct.M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.M2 + }) + errs = append(errs, fn(fldPath.Child("m2"), obj.M2, oldVal, oldObj != nil)...) + } + + return errs +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_UnionStructBeta_ = validate.NewDiscriminatedUnionMembership("dBeta", validate.NewDiscriminatedUnionMember("m1Beta", "M1Beta"), validate.NewDiscriminatedUnionMember("m2Beta", "M2Beta")) + +// Validate_UnionStructBeta validates an instance of UnionStructBeta according +// to declarative validation rules in the API schema. +func Validate_UnionStructBeta( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *UnionStructBeta) (errs field.ErrorList) { + + if e := validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_levels_unions_UnionStructBeta_, + func(obj *UnionStructBeta) string { + if obj == nil { + return "" + } + return string(obj.DBeta) + }, + func(obj *UnionStructBeta) bool { + if obj == nil { + return false + } + return obj.M1Beta != nil + }, + func(obj *UnionStructBeta) bool { + if obj == nil { + return false + } + return obj.M2Beta != nil + }).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + + // field UnionStructBeta.TypeMeta has no validation + // field UnionStructBeta.DBeta has no validation + + { // field UnionStructBeta.M1Beta + fn := func( + fldPath *field.Path, + obj, oldObj *BetaM1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UnionStructBeta) *BetaM1 { + return oldObj.M1Beta + }) + errs = append(errs, fn(fldPath.Child("m1Beta"), obj.M1Beta, oldVal, oldObj != nil)...) + } + + { // field UnionStructBeta.M2Beta + fn := func( + fldPath *field.Path, + obj, oldObj *BetaM2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UnionStructBeta) *BetaM2 { + return oldObj.M2Beta + }) + errs = append(errs, fn(fldPath.Child("m2Beta"), obj.M2Beta, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc.go new file mode 100644 index 0000000000..715adcbf56 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc.go @@ -0,0 +1,37 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// +k8s:validation-gen-nolint +package uniquetag + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type UniqueStruct struct { + TypeMeta int + + // +k8s:listType=atomic + // +k8s:alpha=+k8s:unique=set + AlphaUniqueSet []string `json:"alphaUniqueSet"` + + // +k8s:listType=atomic + // +k8s:beta=+k8s:unique=set + BetaUniqueSet []string `json:"betaUniqueSet"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc_test.go new file mode 100644 index 0000000000..a37747ed01 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/doc_test.go @@ -0,0 +1,35 @@ +/* +Copyright 2025 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 uniquetag + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestMisc(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&UniqueStruct{ + AlphaUniqueSet: []string{"a", "a"}, + BetaUniqueSet: []string{"a", "a"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByValidationStabilityLevel(), field.ErrorList{ + field.Duplicate(field.NewPath("alphaUniqueSet").Index(1), "a").MarkAlpha(), + field.Duplicate(field.NewPath("betaUniqueSet").Index(1), "a").MarkBeta(), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/zz_generated.validations.go new file mode 100644 index 0000000000..1c3237948a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/levels/uniquetag/zz_generated.validations.go @@ -0,0 +1,118 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package uniquetag + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type UniqueStruct + scheme.AddValidationFunc( + (*UniqueStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_UniqueStruct( + ctx, op, nil, /* fldPath */ + obj.(*UniqueStruct), + safe.Cast[*UniqueStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_UniqueStruct validates an instance of UniqueStruct according +// to declarative validation rules in the API schema. +func Validate_UniqueStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *UniqueStruct) (errs field.ErrorList) { + + // field UniqueStruct.TypeMeta has no validation + + { // field UniqueStruct.AlphaUniqueSet + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UniqueStruct) []string { + return oldObj.AlphaUniqueSet + }) + errs = append(errs, fn(fldPath.Child("alphaUniqueSet"), obj.AlphaUniqueSet, oldVal, oldObj != nil)...) + } + + { // field UniqueStruct.BetaUniqueSet + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual).MarkBeta(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UniqueStruct) []string { + return oldObj.BetaUniqueSet + }) + errs = append(errs, fn(fldPath.Child("betaUniqueSet"), obj.BetaUniqueSet, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc.go new file mode 100644 index 0000000000..389055bd70 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc.go @@ -0,0 +1,100 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package multiplekeys + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:listType=map + // +k8s:listMapKey=key1Field + // +k8s:listMapKey=key2Field + // +k8s:eachVal=+k8s:immutable + ListField []OtherStruct `json:"listField"` + + // +k8s:listType=map + // +k8s:listMapKey=key1Field + // +k8s:listMapKey=key2Field + // +k8s:eachVal=+k8s:immutable + ListTypedefField []OtherTypedefStruct `json:"listTypedefField"` + + // +k8s:eachVal=+k8s:immutable + TypedefField ListType `json:"typedefField"` + + // +k8s:listType=map + // +k8s:listMapKey=key1Field + // +k8s:listMapKey=key2Field + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListComparableField[*]" + ListComparableField []OtherStruct `json:"listComparableField"` + + // +k8s:listType=map + // +k8s:listMapKey=key1Field + // +k8s:listMapKey=key2Field + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListNonComparableField[*]" + ListNonComparableField []NonComparableStruct `json:"listNonComparableField"` + + // +k8s:listType=map + // +k8s:listMapKey=key1Field + // +k8s:listMapKey=key2Field + // +k8s:item(key1Field: "target-ptr", key2Field: 42)=+k8s:validateFalse="item ListPtrKeyField[key1Field=target-ptr,key2Field=42]" + ListPtrKeyField []PtrKeyStruct `json:"listPtrKeyField"` + + // +k8s:listType=map + // +k8s:listMapKey=stringPtrKey + // +k8s:listMapKey=stringKey + // +k8s:item(stringPtrKey: "target-ptr", stringKey: "target")=+k8s:validateFalse="item ListMixedPtrKeyField" + ListMixedPtrKeyField []MixedPtrKeyStruct `json:"listMixedPtrKeyField"` +} + +type OtherStruct struct { + Key1Field string `json:"key1Field"` + Key2Field int `json:"key2Field"` + DataField string `json:"dataField"` +} + +type OtherTypedefStruct OtherStruct + +type NonComparableStruct struct { + Key1Field string `json:"key1Field"` + Key2Field int `json:"key2Field"` + DataField []string `json:"dataField"` +} + +// +k8s:listType=map +// +k8s:listMapKey=key1Field +// +k8s:listMapKey=key2Field +type ListType []OtherStruct + +type PtrKeyStruct struct { + Key1Field *string `json:"key1Field"` + Key2Field int `json:"key2Field"` + DataField string `json:"dataField"` +} + +type MixedPtrKeyStruct struct { + StringPtrKey *string `json:"stringPtrKey"` + StringKey string `json:"stringKey"` + DataField string `json:"dataField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc_test.go new file mode 100644 index 0000000000..bb41132f25 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/doc_test.go @@ -0,0 +1,201 @@ +/* +Copyright 2024 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 multiplekeys + +import ( + "testing" + + field "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestUniqueness(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + ListField: []OtherStruct{ + {"key1", 1, "one"}, // unique + {"key2", 2, "two"}, // dup + {"key2", 2, "two"}, + }, + ListTypedefField: []OtherTypedefStruct{ + {"key1", 1, "one"}, // unique + {"key2", 2, "two"}, // dup + {"key2", 2, "two"}, + }, + TypedefField: ListType{ + {"key1", 1, "one"}, // unique + {"key2", 2, "two"}, // dup + {"key2", 2, "two"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Duplicate(field.NewPath("listField").Index(2), nil), + field.Duplicate(field.NewPath("listTypedefField").Index(2), nil), + field.Duplicate(field.NewPath("typedefField").Index(2), nil), + }) +} + +func TestUpdateCorrelation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + structA1 := Struct{ + ListField: []OtherStruct{ + {"key1", 1, "one"}, + {"key2", 2, "two"}, + }, + ListTypedefField: []OtherTypedefStruct{ + {"key1", 1, "one"}, + {"key2", 2, "two"}, + }, + TypedefField: ListType{ + {"key1", 1, "one"}, + {"key2", 2, "two"}, + }, + } + + // Same data, different order. + structA2 := Struct{ + ListField: []OtherStruct{ + {"key2", 2, "two"}, + {"key1", 1, "one"}, + }, + ListTypedefField: []OtherTypedefStruct{ + {"key2", 2, "two"}, + {"key1", 1, "one"}, + }, + TypedefField: ListType{ + {"key2", 2, "two"}, + {"key1", 1, "one"}, + }, + } + + // Different data. + structB := Struct{ + ListField: []OtherStruct{ + {"key3", 3, "THREE"}, + {"key1", 1, "ONE"}, + {"key2", 2, "TWO"}, + }, + ListTypedefField: []OtherTypedefStruct{ + {"key3", 3, "THREE"}, + {"key1", 1, "ONE"}, + {"key2", 2, "TWO"}, + }, + TypedefField: ListType{ + {"key3", 3, "THREE"}, + {"key1", 1, "ONE"}, + {"key2", 2, "TWO"}, + }, + } + + st.Value(&structA1).OldValue(&structA2).ExpectValid() + + st.Value(&structA2).OldValue(&structA1).ExpectValid() + + st.Value(&structA1).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(1), nil, "immutable").WithOrigin("immutable"), + }) + + st.Value(&structB).OldValue(&structA1).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listField").Index(2), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(2), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(2), nil, "immutable").WithOrigin("immutable"), + }) +} + +func TestRatcheting(t *testing.T) { + st := localSchemeBuilder.Test(t) + + struct1 := Struct{ + ListComparableField: []OtherStruct{ + {"key1", 1, "one"}, + {"key2", 2, "two"}, + }, + ListNonComparableField: []NonComparableStruct{ + {"key1", 1, []string{"one"}}, + {"key2", 2, []string{"two"}}, + }, + } + + // Same data, different order. + struct2 := Struct{ + ListComparableField: []OtherStruct{ + {"key2", 2, "two"}, + {"key1", 1, "one"}, + }, + ListNonComparableField: []NonComparableStruct{ + {"key2", 2, []string{"two"}}, + {"key1", 1, []string{"one"}}, + }, + } + + st.Value(&struct1).ExpectValidateFalseByPath(map[string][]string{ + "listComparableField[0]": {"field Struct.ListComparableField[*]"}, + "listComparableField[1]": {"field Struct.ListComparableField[*]"}, + "listNonComparableField[0]": {"field Struct.ListNonComparableField[*]"}, + "listNonComparableField[1]": {"field Struct.ListNonComparableField[*]"}, + }) + st.Value(&struct1).OldValue(&struct2).ExpectValid() + st.Value(&struct2).OldValue(&struct1).ExpectValid() +} + +func TestItemWithPtrKey(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + ListPtrKeyField: []PtrKeyStruct{ + {Key1Field: ptr.To("target-ptr"), Key2Field: 42, DataField: "match"}, + {Key1Field: ptr.To("target-ptr"), Key2Field: 99, DataField: "no match, int differs"}, + {Key1Field: ptr.To("other"), Key2Field: 42, DataField: "no match, string differs"}, + {Key1Field: nil, Key2Field: 42, DataField: "no match, nil string"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `listPtrKeyField[0]`: { + "item ListPtrKeyField[key1Field=target-ptr,key2Field=42]", + }, + }) + + st.Value(&Struct{ + ListPtrKeyField: []PtrKeyStruct{ + {Key1Field: ptr.To("other"), Key2Field: 42, DataField: "d1"}, + {Key1Field: nil, Key2Field: 99, DataField: "d2"}, + }, + }).ExpectValid() + + st.Value(&Struct{ + ListMixedPtrKeyField: []MixedPtrKeyStruct{ + {StringPtrKey: ptr.To("target-ptr"), StringKey: "target", DataField: "match"}, + {StringPtrKey: ptr.To("target-ptr"), StringKey: "other", DataField: "no match"}, + {StringPtrKey: nil, StringKey: "target", DataField: "no match"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + `listMixedPtrKeyField[0]`: { + "item ListMixedPtrKeyField", + }, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/zz_generated.validations.go new file mode 100644 index 0000000000..bd1654a64c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/multiple_keys/zz_generated.validations.go @@ -0,0 +1,354 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiplekeys + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ListType validates an instance of ListType according +// to declarative validation rules in the API schema. +func Validate_ListType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ListType) (errs field.ErrorList) { + + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherTypedefStruct, b *OtherTypedefStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherTypedefStruct, b *OtherTypedefStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherTypedefStruct { + return oldObj.ListTypedefField + }) + errs = append(errs, fn(fldPath.Child("listTypedefField"), obj.ListTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj ListType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_ListType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ListType { + return oldObj.TypedefField + }) + errs = append(errs, fn(fldPath.Child("typedefField"), obj.TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.ListComparableField + }) + errs = append(errs, fn(fldPath.Child("listComparableField"), obj.ListComparableField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *NonComparableStruct, b *NonComparableStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListNonComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *NonComparableStruct, b *NonComparableStruct) bool { + return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []NonComparableStruct { + return oldObj.ListNonComparableField + }) + errs = append(errs, fn(fldPath.Child("listNonComparableField"), obj.ListNonComparableField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListPtrKeyField + fn := func( + fldPath *field.Path, + obj, oldObj []PtrKeyStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *PtrKeyStruct, b *PtrKeyStruct) bool { + return ((a.Key1Field == nil && b.Key1Field == nil) || (a.Key1Field != nil && b.Key1Field != nil && *a.Key1Field == *b.Key1Field)) && a.Key2Field == b.Key2Field + }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"key1Field": "target-ptr", "key2Field": 42}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *PtrKeyStruct) bool { + return item.Key1Field != nil && *item.Key1Field == "target-ptr" && item.Key2Field == 42 + }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *PtrKeyStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ListPtrKeyField[key1Field=target-ptr,key2Field=42]") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []PtrKeyStruct { + return oldObj.ListPtrKeyField + }) + errs = append(errs, fn(fldPath.Child("listPtrKeyField"), obj.ListPtrKeyField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListMixedPtrKeyField + fn := func( + fldPath *field.Path, + obj, oldObj []MixedPtrKeyStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *MixedPtrKeyStruct, b *MixedPtrKeyStruct) bool { + return ((a.StringPtrKey == nil && b.StringPtrKey == nil) || (a.StringPtrKey != nil && b.StringPtrKey != nil && *a.StringPtrKey == *b.StringPtrKey)) && a.StringKey == b.StringKey + }); len(e) != 0 { + errs = append(errs, e...) + } + func() { // cohort = "{"stringPtrKey": "target-ptr", "stringKey": "target"}" + if e := validate.ValSliceItem(ctx, op, fldPath, obj, oldObj, + func(item *MixedPtrKeyStruct) bool { + return item.StringPtrKey != nil && *item.StringPtrKey == "target-ptr" && item.StringKey == "target" + }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedPtrKeyStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item ListMixedPtrKeyField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []MixedPtrKeyStruct { + return oldObj.ListMixedPtrKeyField + }) + errs = append(errs, fn(fldPath.Child("listMixedPtrKeyField"), obj.ListMixedPtrKeyField, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc.go new file mode 100644 index 0000000000..08e46ea2e7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc.go @@ -0,0 +1,78 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package singlekey + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:listType=map + // +k8s:listMapKey=keyField + // +k8s:eachVal=+k8s:immutable + ListField []OtherStruct `json:"listField"` + + // +k8s:listType=map + // +k8s:listMapKey=keyField + // +k8s:eachVal=+k8s:immutable + ListTypedefField []OtherTypedefStruct `json:"listTypedefField"` + + // +k8s:eachVal=+k8s:immutable + TypedefField ListType `json:"typedefField"` + + // +k8s:listType=map + // +k8s:listMapKey=keyField + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListComparableField[*]" + ListComparableField []OtherStruct `json:"listComparableField"` + + // +k8s:listType=map + // +k8s:listMapKey=keyField + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListNonComparableField[*]" + ListNonComparableField []NonComparableStruct `json:"listNonComparableField"` + + // +k8s:listType=map + // +k8s:listMapKey=keyField + ListPtrKeyField []PtrKeyStruct `json:"listPtrKeyField"` +} + +type OtherStruct struct { + KeyField string `json:"keyField"` + DataField string `json:"dataField"` +} + +type OtherTypedefStruct OtherStruct + +type NonComparableStruct struct { + KeyField string `json:"keyField"` + StringPtrField *string `json:"stringPtrField"` +} + +type PtrKeyStruct struct { + KeyField *string `json:"keyField"` + DataField string `json:"dataField"` +} + +// +k8s:listType=map +// +k8s:listMapKey=keyField +type ListType []OtherStruct diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc_test.go new file mode 100644 index 0000000000..01a2a782a9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/doc_test.go @@ -0,0 +1,181 @@ +/* +Copyright 2024 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 singlekey + +import ( + "testing" + + field "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestUniqueness(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + ListField: []OtherStruct{ + {"key1", "one"}, + {"key2", "two"}, + {"key2", "two"}, + }, + ListTypedefField: []OtherTypedefStruct{ + {"key1", "one"}, + {"key2", "two"}, + {"key2", "two"}, + }, + TypedefField: ListType{ + {"key1", "one"}, + {"key2", "two"}, + {"key2", "two"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Duplicate(field.NewPath("listField").Index(2), nil), + field.Duplicate(field.NewPath("listTypedefField").Index(2), nil), + field.Duplicate(field.NewPath("typedefField").Index(2), nil), + }) +} + +func TestUpdateCorrelation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + structA1 := Struct{ + ListField: []OtherStruct{ + {"key1", "one"}, + {"key2", "two"}, + }, + ListTypedefField: []OtherTypedefStruct{ + {"key1", "one"}, + {"key2", "two"}, + }, + TypedefField: ListType{ + {"key1", "one"}, + {"key2", "two"}, + }, + } + + // Same data, different order. + structA2 := Struct{ + ListField: []OtherStruct{ + {"key2", "two"}, + {"key1", "one"}, + }, + ListTypedefField: []OtherTypedefStruct{ + {"key2", "two"}, + {"key1", "one"}, + }, + TypedefField: ListType{ + {"key2", "two"}, + {"key1", "one"}, + }, + } + + // Different data. + structB := Struct{ + ListField: []OtherStruct{ + {"key3", "THREE"}, + {"key1", "ONE"}, + {"key2", "TWO"}, + }, + ListTypedefField: []OtherTypedefStruct{ + {"key3", "THREE"}, + {"key1", "ONE"}, + {"key2", "TWO"}, + }, + TypedefField: ListType{ + {"key3", "THREE"}, + {"key1", "ONE"}, + {"key2", "TWO"}, + }, + } + + st.Value(&structA1).OldValue(&structA2).ExpectValid() + + st.Value(&structA2).OldValue(&structA1).ExpectValid() + + st.Value(&structA1).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(1), nil, "immutable").WithOrigin("immutable"), + }) + + st.Value(&structB).OldValue(&structA1).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listField").Index(2), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("listTypedefField").Index(2), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(0), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(1), nil, "immutable").WithOrigin("immutable"), + field.Invalid(field.NewPath("typedefField").Index(2), nil, "immutable").WithOrigin("immutable"), + }) +} + +func TestRatcheting(t *testing.T) { + st := localSchemeBuilder.Test(t) + + struct1 := Struct{ + ListComparableField: []OtherStruct{ + {"key1", "one"}, + {"key2", "two"}, + }, + ListNonComparableField: []NonComparableStruct{ + {"key1", ptr.To("one")}, + {"key2", ptr.To("two")}, + }, + } + + // Same data, different order. + struct2 := Struct{ + ListComparableField: []OtherStruct{ + {"key2", "two"}, + {"key1", "one"}, + }, + ListNonComparableField: []NonComparableStruct{ + {"key2", ptr.To("two")}, + {"key1", ptr.To("one")}, + }, + } + st.Value(&struct1).ExpectValidateFalseByPath(map[string][]string{ + "listComparableField[0]": {"field Struct.ListComparableField[*]"}, + "listComparableField[1]": {"field Struct.ListComparableField[*]"}, + "listNonComparableField[0]": {"field Struct.ListNonComparableField[*]"}, + "listNonComparableField[1]": {"field Struct.ListNonComparableField[*]"}, + }) + st.Value(&struct1).OldValue(&struct2).ExpectValid() + st.Value(&struct2).OldValue(&struct1).ExpectValid() +} + +func TestUniquenessPtrKey(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + ListPtrKeyField: []PtrKeyStruct{ + {ptr.To("key1"), "one"}, + {ptr.To("key2"), "two"}, + {ptr.To("key2"), "three"}, // duplicate key + {nil, "four"}, + {nil, "five"}, // duplicate nil key + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Duplicate(field.NewPath("listPtrKeyField").Index(2), nil), + field.Duplicate(field.NewPath("listPtrKeyField").Index(4), nil), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/zz_generated.validations.go new file mode 100644 index 0000000000..fb48c34721 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listmap/single_key/zz_generated.validations.go @@ -0,0 +1,284 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package singlekey + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ListType validates an instance of ListType according +// to declarative validation rules in the API schema. +func Validate_ListType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ListType) (errs field.ErrorList) { + + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.KeyField == b.KeyField }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ListField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.KeyField == b.KeyField }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.ListField + }) + errs = append(errs, fn(fldPath.Child("listField"), obj.ListField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherTypedefStruct, b *OtherTypedefStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherTypedefStruct, b *OtherTypedefStruct) bool { return a.KeyField == b.KeyField }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherTypedefStruct { + return oldObj.ListTypedefField + }) + errs = append(errs, fn(fldPath.Child("listTypedefField"), obj.ListTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj ListType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_ListType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ListType { + return oldObj.TypedefField + }) + errs = append(errs, fn(fldPath.Child("typedefField"), obj.TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.KeyField == b.KeyField }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.ListComparableField + }) + errs = append(errs, fn(fldPath.Child("listComparableField"), obj.ListComparableField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *NonComparableStruct, b *NonComparableStruct) bool { return a.KeyField == b.KeyField }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonComparableStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListNonComparableField[*]") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *NonComparableStruct, b *NonComparableStruct) bool { return a.KeyField == b.KeyField }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []NonComparableStruct { + return oldObj.ListNonComparableField + }) + errs = append(errs, fn(fldPath.Child("listNonComparableField"), obj.ListNonComparableField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListPtrKeyField + fn := func( + fldPath *field.Path, + obj, oldObj []PtrKeyStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *PtrKeyStruct, b *PtrKeyStruct) bool { + return ((a.KeyField == nil && b.KeyField == nil) || (a.KeyField != nil && b.KeyField != nil && *a.KeyField == *b.KeyField)) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []PtrKeyStruct { + return oldObj.ListPtrKeyField + }) + errs = append(errs, fn(fldPath.Child("listPtrKeyField"), obj.ListPtrKeyField, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc.go new file mode 100644 index 0000000000..50560cf2b8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc.go @@ -0,0 +1,88 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package listset + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:listType=set + SliceStringField []string `json:"sliceStringField"` + + // +k8s:listType=set + SliceIntField []int `json:"sliceIntField"` + + // +k8s:listType=set + SliceComparableField []ComparableStruct `json:"sliceComparableField"` + + // +k8s:listType=set + SliceNonComparableField []NonComparableStruct `json:"sliceNonComparableField"` + + // +k8s:listType=set + SliceFalselyComparableField []FalselyComparableStruct `json:"sliceFalselyComparableField"` +} + +type ImmutableStruct struct { + TypeMeta int + + // +k8s:eachVal=+k8s:immutable + SliceComparableField []ComparableStruct `json:"sliceComparableField"` + + // +k8s:listType=set + // +k8s:eachVal=+k8s:immutable + SliceSetComparableField []ComparableStruct `json:"sliceSetComparableField"` + + // +k8s:eachVal=+k8s:immutable + SliceNonComparableField []NonComparableStruct `json:"sliceNonComparableField"` + + // +k8s:listType=set + // +k8s:eachVal=+k8s:immutable + SliceSetNonComparableField []NonComparableStruct `json:"sliceSetNonComparableField"` + + // +k8s:eachVal=+k8s:immutable + SlicePrimitiveField []int `json:"slicePrimitiveField"` + + // +k8s:listType=set + // +k8s:eachVal=+k8s:immutable + SliceSetPrimitiveField []int `json:"sliceSetPrimitiveField"` + + // +k8s:listType=set + // +k8s:eachVal=+k8s:immutable + SliceSetFalselyComparableField []FalselyComparableStruct `json:"sliceSetFalselyComparableField"` +} + +type ComparableStruct struct { + StringField string `json:"stringField"` +} + +type NonComparableStruct struct { + SliceField []string `json:"sliceField"` +} + +// FalselyComparableStruct contains a pointer field which makes Go's == operator +// only compare the pointers, not the underlying values +type FalselyComparableStruct struct { + StringPtrField *string `json:"stringPtrField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc_test.go new file mode 100644 index 0000000000..a04c007b0e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/doc_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2025 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 listset + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{}).ExpectValid() + + st.Value(&Struct{ + SliceStringField: []string{"aaa", "bbb"}, + SliceIntField: []int{1, 2}, + SliceComparableField: []ComparableStruct{{"aaa"}, {"bbb"}}, + SliceNonComparableField: []NonComparableStruct{ + {[]string{"aaa", "bbb"}}, + {[]string{"bbb", "aaa"}}, + }, + }).ExpectValid() + + ptrS1 := ptr.To("same value") + ptrS2 := ptr.To("same value") + st.Value(&Struct{ + SliceStringField: []string{"aaa", "bbb", "ccc", "ccc", "bbb", "aaa"}, + SliceIntField: []int{1, 2, 3, 3, 2, 1}, + SliceComparableField: []ComparableStruct{{"aaa"}, {"bbb"}, {"ccc"}, {"ccc"}, {"bbb"}, {"aaa"}}, + SliceNonComparableField: []NonComparableStruct{ + {[]string{"aaa", "111"}}, + {[]string{"bbb", "222"}}, + {[]string{"ccc", "333"}}, + {[]string{"ccc", "333"}}, + {[]string{"bbb", "222"}}, + {[]string{"aaa", "111"}}, + }, + SliceFalselyComparableField: []FalselyComparableStruct{ + {StringPtrField: ptrS1}, + {StringPtrField: ptrS2}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Duplicate(field.NewPath("sliceStringField").Index(3), nil), + field.Duplicate(field.NewPath("sliceStringField").Index(4), nil), + field.Duplicate(field.NewPath("sliceStringField").Index(5), nil), + field.Duplicate(field.NewPath("sliceIntField").Index(3), nil), + field.Duplicate(field.NewPath("sliceIntField").Index(4), nil), + field.Duplicate(field.NewPath("sliceIntField").Index(5), nil), + field.Duplicate(field.NewPath("sliceComparableField").Index(3), nil), + field.Duplicate(field.NewPath("sliceComparableField").Index(4), nil), + field.Duplicate(field.NewPath("sliceComparableField").Index(5), nil), + field.Duplicate(field.NewPath("sliceNonComparableField").Index(3), nil), + field.Duplicate(field.NewPath("sliceNonComparableField").Index(4), nil), + field.Duplicate(field.NewPath("sliceNonComparableField").Index(5), nil), + field.Duplicate(field.NewPath("sliceFalselyComparableField").Index(1), nil), + }) +} + +func TestSetCorrelation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + structNew := ImmutableStruct{SliceSetComparableField: []ComparableStruct{{"aaa"}, {"bbb"}}} + structOld := ImmutableStruct{SliceSetComparableField: []ComparableStruct{{"bbb"}, {"aaa"}}} + st.Value(&structNew).OldValue(&structOld).ExpectValid() + + structNew = ImmutableStruct{SliceSetNonComparableField: []NonComparableStruct{{[]string{"aaa"}}, {[]string{"bbb"}}}} + structOld = ImmutableStruct{SliceSetNonComparableField: []NonComparableStruct{{[]string{"bbb"}}, {[]string{"aaa"}}}} + st.Value(&structNew).OldValue(&structOld).ExpectValid() + + structNew = ImmutableStruct{SliceSetPrimitiveField: []int{1, 2}} + structOld = ImmutableStruct{SliceSetPrimitiveField: []int{2, 1}} + st.Value(&structNew).OldValue(&structOld).ExpectValid() + + structNew = ImmutableStruct{SliceSetFalselyComparableField: []FalselyComparableStruct{{StringPtrField: ptr.To("same value")}}} + structOld = ImmutableStruct{SliceSetFalselyComparableField: []FalselyComparableStruct{{StringPtrField: ptr.To("same value")}}} + st.Value(&structNew).OldValue(&structOld).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/zz_generated.validations.go new file mode 100644 index 0000000000..77b155ffdd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/listset/zz_generated.validations.go @@ -0,0 +1,443 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package listset + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ImmutableStruct + scheme.AddValidationFunc( + (*ImmutableStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ImmutableStruct( + ctx, op, nil, /* fldPath */ + obj.(*ImmutableStruct), + safe.Cast[*ImmutableStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ImmutableStruct validates an instance of ImmutableStruct according +// to declarative validation rules in the API schema. +func Validate_ImmutableStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ImmutableStruct) (errs field.ErrorList) { + + // field ImmutableStruct.TypeMeta has no validation + + { // field ImmutableStruct.SliceComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []ComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ImmutableStruct) []ComparableStruct { + return oldObj.SliceComparableField + }) + errs = append(errs, fn(fldPath.Child("sliceComparableField"), obj.SliceComparableField, oldVal, oldObj != nil)...) + } + + { // field ImmutableStruct.SliceSetComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []ComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ImmutableStruct) []ComparableStruct { + return oldObj.SliceSetComparableField + }) + errs = append(errs, fn(fldPath.Child("sliceSetComparableField"), obj.SliceSetComparableField, oldVal, oldObj != nil)...) + } + + { // field ImmutableStruct.SliceNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ImmutableStruct) []NonComparableStruct { + return oldObj.SliceNonComparableField + }) + errs = append(errs, fn(fldPath.Child("sliceNonComparableField"), obj.SliceNonComparableField, oldVal, oldObj != nil)...) + } + + { // field ImmutableStruct.SliceSetNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ImmutableStruct) []NonComparableStruct { + return oldObj.SliceSetNonComparableField + }) + errs = append(errs, fn(fldPath.Child("sliceSetNonComparableField"), obj.SliceSetNonComparableField, oldVal, oldObj != nil)...) + } + + { // field ImmutableStruct.SlicePrimitiveField + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ImmutableStruct) []int { + return oldObj.SlicePrimitiveField + }) + errs = append(errs, fn(fldPath.Child("slicePrimitiveField"), obj.SlicePrimitiveField, oldVal, oldObj != nil)...) + } + + { // field ImmutableStruct.SliceSetPrimitiveField + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ImmutableStruct) []int { + return oldObj.SliceSetPrimitiveField + }) + errs = append(errs, fn(fldPath.Child("sliceSetPrimitiveField"), obj.SliceSetPrimitiveField, oldVal, oldObj != nil)...) + } + + { // field ImmutableStruct.SliceSetFalselyComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []FalselyComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, nil, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ImmutableStruct) []FalselyComparableStruct { + return oldObj.SliceSetFalselyComparableField + }) + errs = append(errs, fn(fldPath.Child("sliceSetFalselyComparableField"), obj.SliceSetFalselyComparableField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.SliceStringField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceStringField + }) + errs = append(errs, fn(fldPath.Child("sliceStringField"), obj.SliceStringField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceIntField + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []int { + return oldObj.SliceIntField + }) + errs = append(errs, fn(fldPath.Child("sliceIntField"), obj.SliceIntField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []ComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []ComparableStruct { + return oldObj.SliceComparableField + }) + errs = append(errs, fn(fldPath.Child("sliceComparableField"), obj.SliceComparableField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceNonComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []NonComparableStruct { + return oldObj.SliceNonComparableField + }) + errs = append(errs, fn(fldPath.Child("sliceNonComparableField"), obj.SliceNonComparableField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceFalselyComparableField + fn := func( + fldPath *field.Path, + obj, oldObj []FalselyComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []FalselyComparableStruct { + return oldObj.SliceFalselyComparableField + }) + errs = append(errs, fn(fldPath.Child("sliceFalselyComparableField"), obj.SliceFalselyComparableField, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc.go new file mode 100644 index 0000000000..6859a1c5c5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc.go @@ -0,0 +1,81 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package maxbytes + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:maxBytes=0 + Max0Field string `json:"max0Field"` + + // +k8s:maxBytes=0 + Max0PtrField *string `json:"max0PtrField"` + + // +k8s:maxBytes=10 + Max10Field string `json:"max10Field"` + + // +k8s:maxBytes=10 + Max10PtrField *string `json:"max10PtrField"` + + // +k8s:maxBytes=0 + Max0UnvalidatedTypedefField UnvalidatedStringType `json:"max0UnvalidatedTypedefField"` + + // +k8s:maxBytes=0 + Max0UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"max0UnvalidatedTypedefPtrField"` + + // +k8s:maxBytes=10 + Max10UnvalidatedTypedefField UnvalidatedStringType `json:"max10UnvalidatedTypedefField"` + + // +k8s:maxBytes=10 + Max10UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"max10UnvalidatedTypedefPtrField"` + + // Note: no validation here + Max0ValidatedTypedefField Max0Type `json:"max0ValidatedTypedefField"` + + // Note: no validation here + Max0ValidatedTypedefPtrField *Max0Type `json:"max0ValidatedTypedefPtrField"` + + // Note: no validation here + Max10ValidatedTypedefField Max10Type `json:"max10ValidatedTypedefField"` + + // Note: no validation here + Max10ValidatedTypedefPtrField *Max10Type `json:"max10ValidatedTypedefPtrField"` +} + +// Note: no validation here +type UnvalidatedStringType string + +// This tests that markers on type definitions +// are pulled through the validations of fields +// that use the type definition. +// +k8s:maxBytes=0 +type Max0Type string + +// This tests that markers on type definitions +// are pulled through the validations of fields +// that use the type definition. +// +k8s:maxBytes=10 +type Max10Type string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc_test.go new file mode 100644 index 0000000000..47bc3f416d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/doc_test.go @@ -0,0 +1,118 @@ +/* +Copyright 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 maxbytes + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 1), + Max10PtrField: ptr.To(strings.Repeat("x", 1)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 1)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 1))), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 9), + Max10PtrField: ptr.To(strings.Repeat("x", 9)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 9)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 9))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 9)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 9))), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 10), + Max10PtrField: ptr.To(strings.Repeat("x", 10)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 10)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 10))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 10)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 10))), + }).ExpectValid() + + testVal := &Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + } + st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("max0Field"), "", 0), + field.TooLong(field.NewPath("max0PtrField"), "", 0), + field.TooLong(field.NewPath("max10Field"), "", 10), + field.TooLong(field.NewPath("max10PtrField"), "", 10), + field.TooLong(field.NewPath("max0UnvalidatedTypedefField"), "", 0), + field.TooLong(field.NewPath("max0UnvalidatedTypedefPtrField"), "", 0), + field.TooLong(field.NewPath("max10UnvalidatedTypedefField"), "", 10), + field.TooLong(field.NewPath("max10UnvalidatedTypedefPtrField"), "", 10), + field.TooLong(field.NewPath("max0ValidatedTypedefField"), "", 0), + field.TooLong(field.NewPath("max0ValidatedTypedefPtrField"), "", 0), + field.TooLong(field.NewPath("max10ValidatedTypedefField"), "", 10), + field.TooLong(field.NewPath("max10ValidatedTypedefPtrField"), "", 10), + }) + + // Test validation ratcheting + st.Value(&Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + }).OldValue(&Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/zz_generated.validations.go new file mode 100644 index 0000000000..2235d12c35 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxbytes/zz_generated.validations.go @@ -0,0 +1,373 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maxbytes + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Max0Type validates an instance of Max0Type according +// to declarative validation rules in the API schema. +func Validate_Max0Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Max0Type) (errs field.ErrorList) { + + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Max10Type validates an instance of Max10Type according +// to declarative validation rules in the API schema. +func Validate_Max10Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Max10Type) (errs field.ErrorList) { + + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Max0Field + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.Max0Field + }) + errs = append(errs, fn(fldPath.Child("max0Field"), &obj.Max0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Max0PtrField + }) + errs = append(errs, fn(fldPath.Child("max0PtrField"), obj.Max0PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10Field + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.Max10Field + }) + errs = append(errs, fn(fldPath.Child("max10Field"), &obj.Max10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Max10PtrField + }) + errs = append(errs, fn(fldPath.Child("max10PtrField"), obj.Max10PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0UnvalidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return &oldObj.Max0UnvalidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("max0UnvalidatedTypedefField"), &obj.Max0UnvalidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0UnvalidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return oldObj.Max0UnvalidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("max0UnvalidatedTypedefPtrField"), obj.Max0UnvalidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10UnvalidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return &oldObj.Max10UnvalidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("max10UnvalidatedTypedefField"), &obj.Max10UnvalidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10UnvalidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxBytes(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return oldObj.Max10UnvalidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("max10UnvalidatedTypedefPtrField"), obj.Max10UnvalidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *Max0Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max0Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Max0Type { + return &oldObj.Max0ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("max0ValidatedTypedefField"), &obj.Max0ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0ValidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Max0Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max0Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Max0Type { + return oldObj.Max0ValidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("max0ValidatedTypedefPtrField"), obj.Max0ValidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *Max10Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max10Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Max10Type { + return &oldObj.Max10ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("max10ValidatedTypedefField"), &obj.Max10ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10ValidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Max10Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max10Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Max10Type { + return oldObj.Max10ValidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("max10ValidatedTypedefPtrField"), obj.Max10ValidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc.go new file mode 100644 index 0000000000..e2a6bfae1f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc.go @@ -0,0 +1,62 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package maximum + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:maximum=1 + IntField int `json:"intField"` + // +k8s:maximum=1 + IntPtrField *int `json:"intPtrField"` + + // "int8" becomes "byte" somewhere in gengo. We don't need it so just skip it. + + // +k8s:maximum=1 + Int16Field int16 `json:"int16Field"` + // +k8s:maximum=1 + Int32Field int32 `json:"int32Field"` + // +k8s:maximum=1 + Int64Field int64 `json:"int64Field"` + + // +k8s:maximum=1 + UintField uint `json:"uintField"` + // +k8s:maximum=1 + UintPtrField *uint `json:"uintPtrField"` + + // +k8s:maximum=1 + Uint16Field uint16 `json:"uint16Field"` + // +k8s:maximum=1 + Uint32Field uint32 `json:"uint32Field"` + // +k8s:maximum=1 + Uint64Field uint64 `json:"uint64Field"` + + TypedefField IntType `json:"typedefField"` + TypedefPtrField *IntType `json:"typedefPtrField"` +} + +// +k8s:maximum=1 +type IntType int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc_test.go new file mode 100644 index 0000000000..fcc154d0a4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/doc_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2024 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 maximum + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // invalid values (greater than maximum=1) + IntField: 2, + IntPtrField: ptr.To(2), + Int16Field: 2, + Int32Field: 2, + Int64Field: 2, + UintField: 2, + Uint16Field: 2, + Uint32Field: 2, + Uint64Field: 2, + UintPtrField: ptr.To(uint(2)), + TypedefField: IntType(2), + TypedefPtrField: ptr.To(IntType(2)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), nil, ""), + field.Invalid(field.NewPath("intPtrField"), nil, ""), + field.Invalid(field.NewPath("int16Field"), nil, ""), + field.Invalid(field.NewPath("int32Field"), nil, ""), + field.Invalid(field.NewPath("int64Field"), nil, ""), + field.Invalid(field.NewPath("uintField"), nil, ""), + field.Invalid(field.NewPath("uintPtrField"), nil, ""), + field.Invalid(field.NewPath("uint16Field"), nil, ""), + field.Invalid(field.NewPath("uint32Field"), nil, ""), + field.Invalid(field.NewPath("uint64Field"), nil, ""), + field.Invalid(field.NewPath("typedefField"), nil, ""), + field.Invalid(field.NewPath("typedefPtrField"), nil, ""), + }) + // Test validation ratcheting + st.Value(&Struct{ + IntField: 2, + IntPtrField: ptr.To(2), + Int16Field: 2, + Int32Field: 2, + Int64Field: 2, + UintField: 2, + Uint16Field: 2, + Uint32Field: 2, + Uint64Field: 2, + UintPtrField: ptr.To(uint(2)), + TypedefField: IntType(2), + TypedefPtrField: ptr.To(IntType(2)), + }).OldValue(&Struct{ + IntField: 2, + IntPtrField: ptr.To(2), + Int16Field: 2, + Int32Field: 2, + Int64Field: 2, + UintField: 2, + Uint16Field: 2, + Uint32Field: 2, + Uint64Field: 2, + UintPtrField: ptr.To(uint(2)), + TypedefField: IntType(2), + TypedefPtrField: ptr.To(IntType(2)), + }).ExpectValid() + + st.Value(&Struct{ + IntField: 1, + IntPtrField: ptr.To(1), + Int16Field: 1, + Int32Field: 1, + Int64Field: 1, + UintField: 1, + Uint16Field: 1, + Uint32Field: 1, + Uint64Field: 1, + UintPtrField: ptr.To(uint(1)), + TypedefField: IntType(1), + TypedefPtrField: ptr.To(IntType(1)), + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/zz_generated.validations.go new file mode 100644 index 0000000000..0e745e3ee8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maximum/zz_generated.validations.go @@ -0,0 +1,364 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maximum + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_IntType validates an instance of IntType according +// to declarative validation rules in the API schema. +func Validate_IntType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *IntType) (errs field.ErrorList) { + + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Int16Field + fn := func( + fldPath *field.Path, + obj, oldObj *int16, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int16 { + return &oldObj.Int16Field + }) + errs = append(errs, fn(fldPath.Child("int16Field"), &obj.Int16Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Int32Field + fn := func( + fldPath *field.Path, + obj, oldObj *int32, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int32 { + return &oldObj.Int32Field + }) + errs = append(errs, fn(fldPath.Child("int32Field"), &obj.Int32Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Int64Field + fn := func( + fldPath *field.Path, + obj, oldObj *int64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int64 { + return &oldObj.Int64Field + }) + errs = append(errs, fn(fldPath.Child("int64Field"), &obj.Int64Field, oldVal, oldObj != nil)...) + } + + { // field Struct.UintField + fn := func( + fldPath *field.Path, + obj, oldObj *uint, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *uint { + return &oldObj.UintField + }) + errs = append(errs, fn(fldPath.Child("uintField"), &obj.UintField, oldVal, oldObj != nil)...) + } + + { // field Struct.UintPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *uint, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *uint { + return oldObj.UintPtrField + }) + errs = append(errs, fn(fldPath.Child("uintPtrField"), obj.UintPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Uint16Field + fn := func( + fldPath *field.Path, + obj, oldObj *uint16, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *uint16 { + return &oldObj.Uint16Field + }) + errs = append(errs, fn(fldPath.Child("uint16Field"), &obj.Uint16Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Uint32Field + fn := func( + fldPath *field.Path, + obj, oldObj *uint32, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *uint32 { + return &oldObj.Uint32Field + }) + errs = append(errs, fn(fldPath.Child("uint32Field"), &obj.Uint32Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Uint64Field + fn := func( + fldPath *field.Path, + obj, oldObj *uint64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *uint64 { + return &oldObj.Uint64Field + }) + errs = append(errs, fn(fldPath.Child("uint64Field"), &obj.Uint64Field, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return &oldObj.TypedefField + }) + errs = append(errs, fn(fldPath.Child("typedefField"), &obj.TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.TypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return oldObj.TypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("typedefPtrField"), obj.TypedefPtrField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc.go new file mode 100644 index 0000000000..fb2b2607f8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc.go @@ -0,0 +1,44 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofprimitive + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:maxItems=0 + Max0Field []int `json:"max0Field"` + + // +k8s:maxItems=10 + Max10Field []int `json:"max10Field"` + + // +k8s:maxItems=0 + Max0TypedefField []IntType `json:"max0TypedefField"` + + // +k8s:maxItems=10 + Max10TypedefField []IntType `json:"max10TypedefField"` +} + +type IntType int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc_test.go new file mode 100644 index 0000000000..6611443de0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/doc_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2024 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 sliceofprimitive + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + Max0Field: make([]int, 0), + Max10Field: make([]int, 0), + Max0TypedefField: make([]IntType, 0), + Max10TypedefField: make([]IntType, 0), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: make([]int, 1), + Max10TypedefField: make([]IntType, 1), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: make([]int, 9), + Max10TypedefField: make([]IntType, 9), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: make([]int, 10), + Max10TypedefField: make([]IntType, 10), + }).ExpectValid() + + testVal := &Struct{ + Max0Field: make([]int, 1), + Max10Field: make([]int, 11), + Max0TypedefField: make([]IntType, 1), + Max10TypedefField: make([]IntType, 11), + } + st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooMany(field.NewPath("max0Field"), 1, 0), + field.TooMany(field.NewPath("max10Field"), 11, 10), + field.TooMany(field.NewPath("max0TypedefField"), 1, 0), + field.TooMany(field.NewPath("max10TypedefField"), 11, 10), + }) + // Test validation ratcheting + st.Value(&Struct{ + Max0Field: make([]int, 1), + Max10Field: make([]int, 11), + Max0TypedefField: make([]IntType, 1), + Max10TypedefField: make([]IntType, 11), + }).OldValue(&Struct{ + Max0Field: make([]int, 1), + Max10Field: make([]int, 11), + Max0TypedefField: make([]IntType, 1), + Max10TypedefField: make([]IntType, 11), + }).ExpectValid() + +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/zz_generated.validations.go new file mode 100644 index 0000000000..02a2bc11d1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_primitive/zz_generated.validations.go @@ -0,0 +1,184 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofprimitive + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Max0Field + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []int { + return oldObj.Max0Field + }) + errs = append(errs, fn(fldPath.Child("max0Field"), obj.Max0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10Field + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []int { + return oldObj.Max10Field + }) + errs = append(errs, fn(fldPath.Child("max10Field"), obj.Max10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []IntType { + return oldObj.Max0TypedefField + }) + errs = append(errs, fn(fldPath.Child("max0TypedefField"), obj.Max0TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []IntType { + return oldObj.Max10TypedefField + }) + errs = append(errs, fn(fldPath.Child("max10TypedefField"), obj.Max10TypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc.go new file mode 100644 index 0000000000..88c53d05a7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc.go @@ -0,0 +1,46 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofstruct + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:maxItems=0 + Max0Field []OtherStruct `json:"max0Field"` + + // +k8s:maxItems=10 + Max10Field []OtherStruct `json:"max10Field"` + + // +k8s:maxItems=0 + Max0TypedefField []OtherTypedefStruct `json:"max0TypedefField"` + + // +k8s:maxItems=10 + Max10TypedefField []OtherTypedefStruct `json:"max10TypedefField"` +} + +type OtherStruct struct{} + +type OtherTypedefStruct OtherStruct diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc_test.go new file mode 100644 index 0000000000..ce38b50a75 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/doc_test.go @@ -0,0 +1,78 @@ +/* +Copyright 2024 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 sliceofstruct + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + Max0Field: make([]OtherStruct, 0), + Max10Field: make([]OtherStruct, 0), + Max0TypedefField: make([]OtherTypedefStruct, 0), + Max10TypedefField: make([]OtherTypedefStruct, 0), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: make([]OtherStruct, 1), + Max10TypedefField: make([]OtherTypedefStruct, 1), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: make([]OtherStruct, 9), + Max10TypedefField: make([]OtherTypedefStruct, 9), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: make([]OtherStruct, 10), + Max10TypedefField: make([]OtherTypedefStruct, 10), + }).ExpectValid() + + testVal := &Struct{ + Max0Field: make([]OtherStruct, 1), + Max10Field: make([]OtherStruct, 11), + Max0TypedefField: make([]OtherTypedefStruct, 1), + Max10TypedefField: make([]OtherTypedefStruct, 11), + } + st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooMany(field.NewPath("max0Field"), 1, 0), + field.TooMany(field.NewPath("max10Field"), 11, 10), + field.TooMany(field.NewPath("max0TypedefField"), 1, 0), + field.TooMany(field.NewPath("max10TypedefField"), 11, 10), + }) + // Test validation ratcheting + st.Value(&Struct{ + Max0Field: make([]OtherStruct, 1), + Max10Field: make([]OtherStruct, 11), + Max0TypedefField: make([]OtherTypedefStruct, 1), + Max10TypedefField: make([]OtherTypedefStruct, 11), + }).OldValue(&Struct{ + Max0Field: make([]OtherStruct, 1), + Max10Field: make([]OtherStruct, 11), + Max0TypedefField: make([]OtherTypedefStruct, 1), + Max10TypedefField: make([]OtherTypedefStruct, 11), + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/zz_generated.validations.go new file mode 100644 index 0000000000..2151d39a4b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/slice_of_struct/zz_generated.validations.go @@ -0,0 +1,184 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofstruct + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Max0Field + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.Max0Field + }) + errs = append(errs, fn(fldPath.Child("max0Field"), obj.Max0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10Field + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.Max10Field + }) + errs = append(errs, fn(fldPath.Child("max10Field"), obj.Max10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherTypedefStruct { + return oldObj.Max0TypedefField + }) + errs = append(errs, fn(fldPath.Child("max0TypedefField"), obj.Max0TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherTypedefStruct { + return oldObj.Max10TypedefField + }) + errs = append(errs, fn(fldPath.Child("max10TypedefField"), obj.Max10TypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc.go new file mode 100644 index 0000000000..9cdc452269 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package typedeftoslice + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: no validation here +type UnvalidatedType []int + +// +k8s:maxItems=0 +type Max0Type []int + +// +k8s:maxItems=10 +type Max10Type []int + +// Note: no validation here +type UnvalidatedPtrType []*int + +type SliceType []int + +// +k8s:maxItems=0 +type Max0TypedefType SliceType + +// +k8s:maxItems=10 +type Max10TypedefType SliceType + +type Struct struct { + TypeMeta int + + UnvalidatedField UnvalidatedType `json:"unvalidatedField"` + + Max0Field Max0Type `json:"max0Field"` + + Max10Field Max10Type `json:"max10Field"` + + Max0TypedefField Max0TypedefType `json:"max0TypedefField"` + + Max10TypedefField Max10TypedefType `json:"max10TypedefField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc_test.go new file mode 100644 index 0000000000..b5fe133e2d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/doc_test.go @@ -0,0 +1,85 @@ +/* +Copyright 2024 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 typedeftoslice + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + UnvalidatedField: make(UnvalidatedType, 0), + Max0Field: make(Max0Type, 0), + Max10Field: make(Max10Type, 0), + Max0TypedefField: make(Max0TypedefType, 0), + Max10TypedefField: make(Max10TypedefType, 0), + }).ExpectValid() + + st.Value(&Struct{ + UnvalidatedField: make(UnvalidatedType, 1), + Max10Field: make(Max10Type, 1), + Max10TypedefField: make(Max10TypedefType, 1), + }).ExpectValid() + + st.Value(&Struct{ + UnvalidatedField: make(UnvalidatedType, 9), + Max10Field: make(Max10Type, 9), + Max10TypedefField: make(Max10TypedefType, 9), + }).ExpectValid() + + st.Value(&Struct{ + UnvalidatedField: make(UnvalidatedType, 10), + Max10Field: make(Max10Type, 10), + Max10TypedefField: make(Max10TypedefType, 10), + }).ExpectValid() + + testVal := &Struct{ + UnvalidatedField: make(UnvalidatedType, 11), + Max0Field: make(Max0Type, 1), + Max10Field: make(Max10Type, 11), + Max0TypedefField: make(Max0TypedefType, 1), + Max10TypedefField: make(Max10TypedefType, 11), + } + st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooMany(field.NewPath("max0Field"), 1, 0), + field.TooMany(field.NewPath("max10Field"), 11, 10), + field.TooMany(field.NewPath("max0TypedefField"), 1, 0), + field.TooMany(field.NewPath("max10TypedefField"), 11, 10), + }) + // Test validation ratcheting + st.Value(&Struct{ + UnvalidatedField: make(UnvalidatedType, 1), + Max0Field: make(Max0Type, 1), + Max10Field: make(Max10Type, 11), + Max0TypedefField: make(Max0TypedefType, 1), + Max10TypedefField: make(Max10TypedefType, 11), + }).OldValue(&Struct{ + UnvalidatedField: make(UnvalidatedType, 1), + Max0Field: make(Max0Type, 1), + Max10Field: make(Max10Type, 11), + Max0TypedefField: make(Max0TypedefType, 1), + Max10TypedefField: make(Max10TypedefType, 11), + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/zz_generated.validations.go new file mode 100644 index 0000000000..633b156ccd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxitems/typedef_to_slice/zz_generated.validations.go @@ -0,0 +1,229 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftoslice + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Max0Type validates an instance of Max0Type according +// to declarative validation rules in the API schema. +func Validate_Max0Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj Max0Type) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + + return errs +} + +// Validate_Max0TypedefType validates an instance of Max0TypedefType according +// to declarative validation rules in the API schema. +func Validate_Max0TypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj Max0TypedefType) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 0).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + + return errs +} + +// Validate_Max10Type validates an instance of Max10Type according +// to declarative validation rules in the API schema. +func Validate_Max10Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj Max10Type) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + + return errs +} + +// Validate_Max10TypedefType validates an instance of Max10TypedefType according +// to declarative validation rules in the API schema. +func Validate_Max10TypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj Max10TypedefType) (errs field.ErrorList) { + + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 10).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + // field Struct.UnvalidatedField has no validation + + { // field Struct.Max0Field + fn := func( + fldPath *field.Path, + obj, oldObj Max0Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max0Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) Max0Type { + return oldObj.Max0Field + }) + errs = append(errs, fn(fldPath.Child("max0Field"), obj.Max0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10Field + fn := func( + fldPath *field.Path, + obj, oldObj Max10Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max10Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) Max10Type { + return oldObj.Max10Field + }) + errs = append(errs, fn(fldPath.Child("max10Field"), obj.Max10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj Max0TypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max0TypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) Max0TypedefType { + return oldObj.Max0TypedefField + }) + errs = append(errs, fn(fldPath.Child("max0TypedefField"), obj.Max0TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj Max10TypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max10TypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) Max10TypedefType { + return oldObj.Max10TypedefField + }) + errs = append(errs, fn(fldPath.Child("max10TypedefField"), obj.Max10TypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc.go new file mode 100644 index 0000000000..0dad754cde --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc.go @@ -0,0 +1,75 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package maxlength + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:maxLength=0 + Max0Field string `json:"max0Field"` + + // +k8s:maxLength=0 + Max0PtrField *string `json:"max0PtrField"` + + // +k8s:maxLength=10 + Max10Field string `json:"max10Field"` + + // +k8s:maxLength=10 + Max10PtrField *string `json:"max10PtrField"` + + // +k8s:maxLength=0 + Max0UnvalidatedTypedefField UnvalidatedStringType `json:"max0UnvalidatedTypedefField"` + + // +k8s:maxLength=0 + Max0UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"max0UnvalidatedTypedefPtrField"` + + // +k8s:maxLength=10 + Max10UnvalidatedTypedefField UnvalidatedStringType `json:"max10UnvalidatedTypedefField"` + + // +k8s:maxLength=10 + Max10UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"max10UnvalidatedTypedefPtrField"` + + // Note: no validation here + Max0ValidatedTypedefField Max0Type `json:"max0ValidatedTypedefField"` + + // Note: no validation here + Max0ValidatedTypedefPtrField *Max0Type `json:"max0ValidatedTypedefPtrField"` + + // Note: no validation here + Max10ValidatedTypedefField Max10Type `json:"max10ValidatedTypedefField"` + + // Note: no validation here + Max10ValidatedTypedefPtrField *Max10Type `json:"max10ValidatedTypedefPtrField"` +} + +// Note: no validation here +type UnvalidatedStringType string + +// +k8s:maxLength=0 +type Max0Type string + +// +k8s:maxLength=10 +type Max10Type string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc_test.go new file mode 100644 index 0000000000..88b489db08 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/doc_test.go @@ -0,0 +1,118 @@ +/* +Copyright 2025 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 maxlength + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 1), + Max10PtrField: ptr.To(strings.Repeat("x", 1)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 1)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 1))), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 9), + Max10PtrField: ptr.To(strings.Repeat("x", 9)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 9)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 9))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 9)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 9))), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: strings.Repeat("x", 10), + Max10PtrField: ptr.To(strings.Repeat("x", 10)), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 10)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 10))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 10)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 10))), + }).ExpectValid() + + testVal := &Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + } + st.Value(testVal).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLongCharacters(field.NewPath("max0Field"), "", 0), + field.TooLongCharacters(field.NewPath("max0PtrField"), "", 0), + field.TooLongCharacters(field.NewPath("max10Field"), "", 10), + field.TooLongCharacters(field.NewPath("max10PtrField"), "", 10), + field.TooLongCharacters(field.NewPath("max0UnvalidatedTypedefField"), "", 0), + field.TooLongCharacters(field.NewPath("max0UnvalidatedTypedefPtrField"), "", 0), + field.TooLongCharacters(field.NewPath("max10UnvalidatedTypedefField"), "", 10), + field.TooLongCharacters(field.NewPath("max10UnvalidatedTypedefPtrField"), "", 10), + field.TooLongCharacters(field.NewPath("max0ValidatedTypedefField"), "", 0), + field.TooLongCharacters(field.NewPath("max0ValidatedTypedefPtrField"), "", 0), + field.TooLongCharacters(field.NewPath("max10ValidatedTypedefField"), "", 10), + field.TooLongCharacters(field.NewPath("max10ValidatedTypedefPtrField"), "", 10), + }) + + // Test validation ratcheting + st.Value(&Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + }).OldValue(&Struct{ + Max0Field: strings.Repeat("x", 1), + Max0PtrField: ptr.To(strings.Repeat("x", 1)), + Max10Field: strings.Repeat("x", 11), + Max10PtrField: ptr.To(strings.Repeat("x", 11)), + Max0UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Max0UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Max10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Max10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Max0ValidatedTypedefField: Max0Type(strings.Repeat("x", 1)), + Max0ValidatedTypedefPtrField: ptr.To(Max0Type(strings.Repeat("x", 1))), + Max10ValidatedTypedefField: Max10Type(strings.Repeat("x", 11)), + Max10ValidatedTypedefPtrField: ptr.To(Max10Type(strings.Repeat("x", 11))), + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/zz_generated.validations.go new file mode 100644 index 0000000000..4af518a536 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxlength/zz_generated.validations.go @@ -0,0 +1,373 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maxlength + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Max0Type validates an instance of Max0Type according +// to declarative validation rules in the API schema. +func Validate_Max0Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Max0Type) (errs field.ErrorList) { + + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Max10Type validates an instance of Max10Type according +// to declarative validation rules in the API schema. +func Validate_Max10Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Max10Type) (errs field.ErrorList) { + + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Max0Field + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.Max0Field + }) + errs = append(errs, fn(fldPath.Child("max0Field"), &obj.Max0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Max0PtrField + }) + errs = append(errs, fn(fldPath.Child("max0PtrField"), obj.Max0PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10Field + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.Max10Field + }) + errs = append(errs, fn(fldPath.Child("max10Field"), &obj.Max10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Max10PtrField + }) + errs = append(errs, fn(fldPath.Child("max10PtrField"), obj.Max10PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0UnvalidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return &oldObj.Max0UnvalidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("max0UnvalidatedTypedefField"), &obj.Max0UnvalidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0UnvalidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return oldObj.Max0UnvalidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("max0UnvalidatedTypedefPtrField"), obj.Max0UnvalidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10UnvalidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return &oldObj.Max10UnvalidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("max10UnvalidatedTypedefField"), &obj.Max10UnvalidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10UnvalidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return oldObj.Max10UnvalidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("max10UnvalidatedTypedefPtrField"), obj.Max10UnvalidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *Max0Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max0Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Max0Type { + return &oldObj.Max0ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("max0ValidatedTypedefField"), &obj.Max0ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0ValidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Max0Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max0Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Max0Type { + return oldObj.Max0ValidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("max0ValidatedTypedefPtrField"), obj.Max0ValidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *Max10Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max10Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Max10Type { + return &oldObj.Max10ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("max10ValidatedTypedefField"), &obj.Max10ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10ValidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Max10Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Max10Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Max10Type { + return oldObj.Max10ValidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("max10ValidatedTypedefPtrField"), obj.Max10ValidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/doc.go new file mode 100644 index 0000000000..8009ab4020 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/doc.go @@ -0,0 +1,44 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-nolint + +// This is a test package. +package maxproperties + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:maxProperties=0 + Max0Field map[string]string `json:"max0Field"` + + // +k8s:maxProperties=10 + Max10Field map[string]string `json:"max10Field"` + + // +k8s:maxProperties=0 + Max0TypedefKeyField map[StringKey]string `json:"max0TypedefKeyField"` + + // +k8s:maxProperties=10 + Max10TypedefKeyField map[StringKey]string `json:"max10TypedefKeyField"` +} + +type StringKey string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/doc_test.go new file mode 100644 index 0000000000..825cb64e77 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/doc_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2025 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 maxproperties + +import ( + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + Max0Field: generateMapStringStringWithLength(0), + Max10Field: generateMapStringStringWithLength(0), + Max0TypedefKeyField: generateMapStringKeyStringWithLength(0), + Max10TypedefKeyField: generateMapStringKeyStringWithLength(0), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: generateMapStringStringWithLength(1), + Max10TypedefKeyField: generateMapStringKeyStringWithLength(1), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: generateMapStringStringWithLength(9), + Max10TypedefKeyField: generateMapStringKeyStringWithLength(9), + }).ExpectValid() + + st.Value(&Struct{ + Max10Field: generateMapStringStringWithLength(10), + Max10TypedefKeyField: generateMapStringKeyStringWithLength(10), + }).ExpectValid() + + st.Value(&Struct{ + Max0Field: generateMapStringStringWithLength(1), + Max10Field: generateMapStringStringWithLength(11), + Max0TypedefKeyField: generateMapStringKeyStringWithLength(1), + Max10TypedefKeyField: generateMapStringKeyStringWithLength(11), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooMany(field.NewPath("max0Field"), 1, 0), + field.TooMany(field.NewPath("max10Field"), 11, 10), + field.TooMany(field.NewPath("max0TypedefKeyField"), 1, 0), + field.TooMany(field.NewPath("max10TypedefKeyField"), 11, 10), + }) + + // Test validation ratcheting + st.Value(&Struct{ + Max0Field: generateMapStringStringWithLength(1), + Max10Field: generateMapStringStringWithLength(11), + Max0TypedefKeyField: generateMapStringKeyStringWithLength(1), + Max10TypedefKeyField: generateMapStringKeyStringWithLength(11), + }).OldValue(&Struct{ + Max0Field: generateMapStringStringWithLength(1), + Max10Field: generateMapStringStringWithLength(11), + Max0TypedefKeyField: generateMapStringKeyStringWithLength(1), + Max10TypedefKeyField: generateMapStringKeyStringWithLength(11), + }).ExpectValid() +} + +func generateMapStringStringWithLength(n int) map[string]string { + out := make(map[string]string) + for i := range n { + str := fmt.Sprintf("%d", i) + out[str] = str + } + return out +} + +func generateMapStringKeyStringWithLength(n int) map[StringKey]string { + out := make(map[StringKey]string) + for i := range n { + str := fmt.Sprintf("%d", i) + out[StringKey(str)] = str + } + return out +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/zz_generated.validations.go new file mode 100644 index 0000000000..9ded68564f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/maxproperties/zz_generated.validations.go @@ -0,0 +1,184 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maxproperties + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Max0Field + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxProperties(ctx, op, fldPath, obj, oldObj, 0).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.Max0Field + }) + errs = append(errs, fn(fldPath.Child("max0Field"), obj.Max0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10Field + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxProperties(ctx, op, fldPath, obj, oldObj, 10).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.Max10Field + }) + errs = append(errs, fn(fldPath.Child("max10Field"), obj.Max10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Max0TypedefKeyField + fn := func( + fldPath *field.Path, + obj, oldObj map[StringKey]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxProperties(ctx, op, fldPath, obj, oldObj, 0).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[StringKey]string { + return oldObj.Max0TypedefKeyField + }) + errs = append(errs, fn(fldPath.Child("max0TypedefKeyField"), obj.Max0TypedefKeyField, oldVal, oldObj != nil)...) + } + + { // field Struct.Max10TypedefKeyField + fn := func( + fldPath *field.Path, + obj, oldObj map[StringKey]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxProperties(ctx, op, fldPath, obj, oldObj, 10).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[StringKey]string { + return oldObj.Max10TypedefKeyField + }) + errs = append(errs, fn(fldPath.Child("max10TypedefKeyField"), obj.Max10TypedefKeyField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc.go new file mode 100644 index 0000000000..2b18dc7af1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc.go @@ -0,0 +1,124 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package minimum + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type BasicStruct struct { + TypeMeta int + + // +k8s:minimum=1 + IntField int `json:"intField"` + // +k8s:minimum=1 + IntPtrField *int `json:"intPtrField"` + + // "int8" becomes "byte" somewhere in gengo. We don't need it so just skip it. + + // +k8s:minimum=1 + Int16Field int16 `json:"int16Field"` + // +k8s:minimum=1 + Int32Field int32 `json:"int32Field"` + // +k8s:minimum=1 + Int64Field int64 `json:"int64Field"` + + // +k8s:minimum=1 + UintField uint `json:"uintField"` + // +k8s:minimum=1 + UintPtrField *uint `json:"uintPtrField"` + + // +k8s:minimum=1 + Uint16Field uint16 `json:"uint16Field"` + // +k8s:minimum=1 + Uint32Field uint32 `json:"uint32Field"` + // +k8s:minimum=1 + Uint64Field uint64 `json:"uint64Field"` + + TypedefField IntType `json:"typedefField"` + TypedefPtrField *IntType `json:"typedefPtrField"` +} + +type OptionalStruct struct { + TypeMeta int + + // +k8s:optional + // +k8s:minimum=1 + OptionalIntField int `json:"optionalIntField"` + + // +k8s:optional + // +k8s:minimum=1 + OptionalIntPtrField *int `json:"optionalIntPtrField"` + + // +k8s:optional + OptionalTypedefField IntType `json:"optionalTypedefField"` + + // +k8s:optional + OptionalTypedefPtrField *IntType `json:"optionalTypedefPtrField"` +} + +type RequiredStruct struct { + TypeMeta int + + // +k8s:required + // +k8s:minimum=1 + RequiredIntField int `json:"requiredIntField"` + + // +k8s:required + // +k8s:minimum=1 + RequiredIntPtrField *int `json:"requiredIntPtrField"` + + // +k8s:required + RequiredTypedefField IntType `json:"requiredTypedefField"` + + // +k8s:required + RequiredTypedefPtrField *IntType `json:"requiredTypedefPtrField"` +} + +type NegativeMinimumStruct struct { + TypeMeta int + + // +k8s:minimum=-10 + NegativeMinimumField int `json:"negativeMinimumField"` + + // +k8s:minimum=-10 + NegativeMinimumPtrField *int `json:"negativeMinimumPtrField"` + + // +k8s:optional + // +k8s:minimum=-10 + OptionalNegativeMinimumField int `json:"optionalNegativeMinimumField"` + + // +k8s:optional + // +k8s:minimum=-10 + OptionalNegativeMinimumPtrField *int `json:"optionalNegativeMinimumPtrField"` + + // +k8s:required + // +k8s:minimum=-10 + RequiredNegativeMinimumField int `json:"requiredNegativeMinimumField"` + + // +k8s:required + // +k8s:minimum=-10 + RequiredNegativeMinimumPtrField *int `json:"requiredNegativeMinimumPtrField"` +} + +// +k8s:minimum=1 +type IntType int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc_test.go new file mode 100644 index 0000000000..6f6425e4ac --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/doc_test.go @@ -0,0 +1,340 @@ +/* +Copyright 2024 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 minimum + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestBasicStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Test that zero values are rejected because they are below the minimum of 1. + st.Value(&BasicStruct{ + // all zero values + IntPtrField: ptr.To(0), + UintPtrField: ptr.To(uint(0)), + TypedefPtrField: ptr.To(IntType(0)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("intPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("int16Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("int32Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("int64Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("uintField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("uintPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("uint16Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("uint32Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("uint64Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("typedefField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("typedefPtrField"), nil, "").WithOrigin("minimum"), + }) + + // Test validation ratcheting: unchanged invalid data is allowed. + st.Value(&BasicStruct{ + IntPtrField: ptr.To(0), + UintPtrField: ptr.To(uint(0)), + TypedefPtrField: ptr.To(IntType(0)), + }).OldValue(&BasicStruct{ + IntPtrField: ptr.To(0), + UintPtrField: ptr.To(uint(0)), + TypedefPtrField: ptr.To(IntType(0)), + }).ExpectValid() + + // Changed invalid data is still rejected. + st.Value(&BasicStruct{ + IntField: -1, + IntPtrField: ptr.To(-1), + Int16Field: -1, + Int32Field: -1, + Int64Field: -1, + TypedefField: IntType(-1), + TypedefPtrField: ptr.To(IntType(-1)), + }).OldValue(&BasicStruct{ + IntField: 0, + IntPtrField: ptr.To(0), + Int16Field: 0, + Int32Field: 0, + Int64Field: 0, + TypedefField: IntType(0), + TypedefPtrField: ptr.To(IntType(0)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("intPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("int16Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("int32Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("int64Field"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("typedefField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("typedefPtrField"), nil, "").WithOrigin("minimum"), + }) + + // Test that values meeting the minimum of 1 are valid. + st.Value(&BasicStruct{ + IntField: 1, + IntPtrField: ptr.To(1), + Int16Field: 1, + Int32Field: 1, + Int64Field: 1, + UintField: 1, + Uint16Field: 1, + Uint32Field: 1, + Uint64Field: 1, + UintPtrField: ptr.To(uint(1)), + TypedefField: IntType(1), + TypedefPtrField: ptr.To(IntType(1)), + }).ExpectValid() +} + +func TestOptionalStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Test that explicitly provided zero values for optional pointers are still validated against minimum. + st.Value(&OptionalStruct{ + // zero values + OptionalIntPtrField: ptr.To(0), + OptionalTypedefPtrField: ptr.To(IntType(0)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("optionalIntPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalTypedefPtrField"), nil, "").WithOrigin("minimum"), + }) + + // Test that omitting an optional pointer field (nil) short-circuits minimum validation. + st.Value(&OptionalStruct{ + // OptionalIntField is zero and OptionalIntPtrField is nil, so optional + // short-circuits before minimum runs. + }).ExpectValid() + + // Test validation ratcheting: unchanged invalid data is allowed. + st.Value(&OptionalStruct{ + OptionalIntField: -1, + OptionalIntPtrField: ptr.To(-1), + OptionalTypedefField: IntType(-1), + OptionalTypedefPtrField: ptr.To(IntType(-1)), + }).OldValue(&OptionalStruct{ + OptionalIntField: -1, + OptionalIntPtrField: ptr.To(-1), + OptionalTypedefField: IntType(-1), + OptionalTypedefPtrField: ptr.To(IntType(-1)), + }).ExpectValid() + + // Changed invalid data is still rejected. + st.Value(&OptionalStruct{ + OptionalIntField: -2, + OptionalIntPtrField: ptr.To(-2), + OptionalTypedefField: IntType(-2), + OptionalTypedefPtrField: ptr.To(IntType(-2)), + }).OldValue(&OptionalStruct{ + OptionalIntField: -1, + OptionalIntPtrField: ptr.To(-1), + OptionalTypedefField: IntType(-1), + OptionalTypedefPtrField: ptr.To(IntType(-1)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("optionalIntField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalIntPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalTypedefField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalTypedefPtrField"), nil, "").WithOrigin("minimum"), + }) + + // Test that values meeting the minimum of 1 are valid. + st.Value(&OptionalStruct{ + OptionalIntField: 1, + OptionalIntPtrField: ptr.To(1), + OptionalTypedefField: IntType(1), + OptionalTypedefPtrField: ptr.To(IntType(1)), + }).ExpectValid() + + // Test that invalid values below the minimum are rejected. + st.Value(&OptionalStruct{ + OptionalIntField: -1, + OptionalIntPtrField: ptr.To(-1), + OptionalTypedefField: IntType(-1), + OptionalTypedefPtrField: ptr.To(IntType(-1)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("optionalIntField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalIntPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalTypedefField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalTypedefPtrField"), nil, "").WithOrigin("minimum"), + }) +} + +func TestRequiredStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Test that explicitly provided zero values for pointers are validated against minimum, + // while zero values for non-pointers trigger required validation. + st.Value(&RequiredStruct{ + // zero values + RequiredIntField: 0, + RequiredIntPtrField: ptr.To(0), + RequiredTypedefField: IntType(0), + RequiredTypedefPtrField: ptr.To(IntType(0)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Required(field.NewPath("requiredIntField"), ""), + field.Invalid(field.NewPath("requiredIntPtrField"), nil, "").WithOrigin("minimum"), + field.Required(field.NewPath("requiredTypedefField"), ""), + field.Invalid(field.NewPath("requiredTypedefPtrField"), nil, "").WithOrigin("minimum"), + }) + + // Test that omitted required pointer fields (nil) emit field.Required and short-circuit minimum validation. + st.Value(&RequiredStruct{ + // RequiredIntField is zero and RequiredIntPtrField is nil. + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Required(field.NewPath("requiredIntField"), ""), + field.Required(field.NewPath("requiredIntPtrField"), ""), + field.Required(field.NewPath("requiredTypedefField"), ""), + field.Required(field.NewPath("requiredTypedefPtrField"), ""), + }) + + // Test validation ratcheting: unchanged invalid data is allowed. + st.Value(&RequiredStruct{ + RequiredIntField: 0, + RequiredIntPtrField: ptr.To(0), + RequiredTypedefField: IntType(0), + RequiredTypedefPtrField: ptr.To(IntType(0)), + }).OldValue(&RequiredStruct{ + RequiredIntField: 0, + RequiredIntPtrField: ptr.To(0), + RequiredTypedefField: IntType(0), + RequiredTypedefPtrField: ptr.To(IntType(0)), + }).ExpectValid() + + // Changed invalid data is still rejected. + st.Value(&RequiredStruct{ + RequiredIntField: -1, + RequiredIntPtrField: ptr.To(-1), + RequiredTypedefField: IntType(-1), + RequiredTypedefPtrField: ptr.To(IntType(-1)), + }).OldValue(&RequiredStruct{ + RequiredIntField: 0, + RequiredIntPtrField: ptr.To(0), + RequiredTypedefField: IntType(0), + RequiredTypedefPtrField: ptr.To(IntType(0)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("requiredIntField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredIntPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredTypedefField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredTypedefPtrField"), nil, "").WithOrigin("minimum"), + }) + + // Test that values meeting the minimum of 1 are valid. + st.Value(&RequiredStruct{ + RequiredIntField: 1, + RequiredIntPtrField: ptr.To(1), + RequiredTypedefField: IntType(1), + RequiredTypedefPtrField: ptr.To(IntType(1)), + }).ExpectValid() + + // Test that invalid values below the minimum are rejected. + st.Value(&RequiredStruct{ + RequiredIntField: -1, + RequiredIntPtrField: ptr.To(-1), + RequiredTypedefField: IntType(-1), + RequiredTypedefPtrField: ptr.To(IntType(-1)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("requiredIntField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredIntPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredTypedefField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredTypedefPtrField"), nil, "").WithOrigin("minimum"), + }) +} + +func TestNegativeMinimumStruct(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Test that zero values for required pointers are validated against the negative minimum, + // while zero values for non-pointers trigger required validation. + st.Value(&NegativeMinimumStruct{ + // zero values (valid for -10) + NegativeMinimumPtrField: ptr.To(0), + OptionalNegativeMinimumPtrField: ptr.To(0), + RequiredNegativeMinimumField: 0, + RequiredNegativeMinimumPtrField: ptr.To(0), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Required(field.NewPath("requiredNegativeMinimumField"), ""), + }) + + // Test that valid values at the negative minimum boundary (-10) are accepted. + st.Value(&NegativeMinimumStruct{ + NegativeMinimumField: -10, + NegativeMinimumPtrField: ptr.To(-10), + OptionalNegativeMinimumField: -10, + OptionalNegativeMinimumPtrField: ptr.To(-10), + RequiredNegativeMinimumField: -10, + RequiredNegativeMinimumPtrField: ptr.To(-10), + }).ExpectValid() + + // Test validation ratcheting: unchanged invalid data is allowed. + st.Value(&NegativeMinimumStruct{ + NegativeMinimumField: -11, + NegativeMinimumPtrField: ptr.To(-11), + OptionalNegativeMinimumField: -11, + OptionalNegativeMinimumPtrField: ptr.To(-11), + RequiredNegativeMinimumField: -11, + RequiredNegativeMinimumPtrField: ptr.To(-11), + }).OldValue(&NegativeMinimumStruct{ + NegativeMinimumField: -11, + NegativeMinimumPtrField: ptr.To(-11), + OptionalNegativeMinimumField: -11, + OptionalNegativeMinimumPtrField: ptr.To(-11), + RequiredNegativeMinimumField: -11, + RequiredNegativeMinimumPtrField: ptr.To(-11), + }).ExpectValid() + + // Changed invalid data is still rejected. + st.Value(&NegativeMinimumStruct{ + NegativeMinimumField: -12, + NegativeMinimumPtrField: ptr.To(-12), + OptionalNegativeMinimumField: -12, + OptionalNegativeMinimumPtrField: ptr.To(-12), + RequiredNegativeMinimumField: -12, + RequiredNegativeMinimumPtrField: ptr.To(-12), + }).OldValue(&NegativeMinimumStruct{ + NegativeMinimumField: -11, + NegativeMinimumPtrField: ptr.To(-11), + OptionalNegativeMinimumField: -11, + OptionalNegativeMinimumPtrField: ptr.To(-11), + RequiredNegativeMinimumField: -11, + RequiredNegativeMinimumPtrField: ptr.To(-11), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("negativeMinimumField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("negativeMinimumPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalNegativeMinimumField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalNegativeMinimumPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredNegativeMinimumField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredNegativeMinimumPtrField"), nil, "").WithOrigin("minimum"), + }) + + // Test that invalid values below the negative minimum (-10) are rejected. + st.Value(&NegativeMinimumStruct{ + NegativeMinimumField: -11, + NegativeMinimumPtrField: ptr.To(-11), + OptionalNegativeMinimumField: -11, + OptionalNegativeMinimumPtrField: ptr.To(-11), + RequiredNegativeMinimumField: -11, + RequiredNegativeMinimumPtrField: ptr.To(-11), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("negativeMinimumField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("negativeMinimumPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalNegativeMinimumField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("optionalNegativeMinimumPtrField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredNegativeMinimumField"), nil, "").WithOrigin("minimum"), + field.Invalid(field.NewPath("requiredNegativeMinimumPtrField"), nil, "").WithOrigin("minimum"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/zz_generated.validations.go new file mode 100644 index 0000000000..22218842f3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minimum/zz_generated.validations.go @@ -0,0 +1,864 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package minimum + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type BasicStruct + scheme.AddValidationFunc( + (*BasicStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_BasicStruct( + ctx, op, nil, /* fldPath */ + obj.(*BasicStruct), + safe.Cast[*BasicStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type NegativeMinimumStruct + scheme.AddValidationFunc( + (*NegativeMinimumStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_NegativeMinimumStruct( + ctx, op, nil, /* fldPath */ + obj.(*NegativeMinimumStruct), + safe.Cast[*NegativeMinimumStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type OptionalStruct + scheme.AddValidationFunc( + (*OptionalStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_OptionalStruct( + ctx, op, nil, /* fldPath */ + obj.(*OptionalStruct), + safe.Cast[*OptionalStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type RequiredStruct + scheme.AddValidationFunc( + (*RequiredStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_RequiredStruct( + ctx, op, nil, /* fldPath */ + obj.(*RequiredStruct), + safe.Cast[*RequiredStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_BasicStruct validates an instance of BasicStruct according +// to declarative validation rules in the API schema. +func Validate_BasicStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *BasicStruct) (errs field.ErrorList) { + + // field BasicStruct.TypeMeta has no validation + + { // field BasicStruct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.Int16Field + fn := func( + fldPath *field.Path, + obj, oldObj *int16, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *int16 { + return &oldObj.Int16Field + }) + errs = append(errs, fn(fldPath.Child("int16Field"), &obj.Int16Field, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.Int32Field + fn := func( + fldPath *field.Path, + obj, oldObj *int32, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *int32 { + return &oldObj.Int32Field + }) + errs = append(errs, fn(fldPath.Child("int32Field"), &obj.Int32Field, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.Int64Field + fn := func( + fldPath *field.Path, + obj, oldObj *int64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *int64 { + return &oldObj.Int64Field + }) + errs = append(errs, fn(fldPath.Child("int64Field"), &obj.Int64Field, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.UintField + fn := func( + fldPath *field.Path, + obj, oldObj *uint, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *uint { + return &oldObj.UintField + }) + errs = append(errs, fn(fldPath.Child("uintField"), &obj.UintField, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.UintPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *uint, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *uint { + return oldObj.UintPtrField + }) + errs = append(errs, fn(fldPath.Child("uintPtrField"), obj.UintPtrField, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.Uint16Field + fn := func( + fldPath *field.Path, + obj, oldObj *uint16, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *uint16 { + return &oldObj.Uint16Field + }) + errs = append(errs, fn(fldPath.Child("uint16Field"), &obj.Uint16Field, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.Uint32Field + fn := func( + fldPath *field.Path, + obj, oldObj *uint32, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *uint32 { + return &oldObj.Uint32Field + }) + errs = append(errs, fn(fldPath.Child("uint32Field"), &obj.Uint32Field, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.Uint64Field + fn := func( + fldPath *field.Path, + obj, oldObj *uint64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *uint64 { + return &oldObj.Uint64Field + }) + errs = append(errs, fn(fldPath.Child("uint64Field"), &obj.Uint64Field, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *IntType { + return &oldObj.TypedefField + }) + errs = append(errs, fn(fldPath.Child("typedefField"), &obj.TypedefField, oldVal, oldObj != nil)...) + } + + { // field BasicStruct.TypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BasicStruct) *IntType { + return oldObj.TypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("typedefPtrField"), obj.TypedefPtrField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_IntType validates an instance of IntType according +// to declarative validation rules in the API schema. +func Validate_IntType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *IntType) (errs field.ErrorList) { + + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_NegativeMinimumStruct validates an instance of NegativeMinimumStruct according +// to declarative validation rules in the API schema. +func Validate_NegativeMinimumStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NegativeMinimumStruct) (errs field.ErrorList) { + + // field NegativeMinimumStruct.TypeMeta has no validation + + { // field NegativeMinimumStruct.NegativeMinimumField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, -10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NegativeMinimumStruct) *int { + return &oldObj.NegativeMinimumField + }) + errs = append(errs, fn(fldPath.Child("negativeMinimumField"), &obj.NegativeMinimumField, oldVal, oldObj != nil)...) + } + + { // field NegativeMinimumStruct.NegativeMinimumPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, -10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NegativeMinimumStruct) *int { + return oldObj.NegativeMinimumPtrField + }) + errs = append(errs, fn(fldPath.Child("negativeMinimumPtrField"), obj.NegativeMinimumPtrField, oldVal, oldObj != nil)...) + } + + { // field NegativeMinimumStruct.OptionalNegativeMinimumField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, -10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NegativeMinimumStruct) *int { + return &oldObj.OptionalNegativeMinimumField + }) + errs = append(errs, fn(fldPath.Child("optionalNegativeMinimumField"), &obj.OptionalNegativeMinimumField, oldVal, oldObj != nil)...) + } + + { // field NegativeMinimumStruct.OptionalNegativeMinimumPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, -10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NegativeMinimumStruct) *int { + return oldObj.OptionalNegativeMinimumPtrField + }) + errs = append(errs, fn(fldPath.Child("optionalNegativeMinimumPtrField"), obj.OptionalNegativeMinimumPtrField, oldVal, oldObj != nil)...) + } + + { // field NegativeMinimumStruct.RequiredNegativeMinimumField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, -10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NegativeMinimumStruct) *int { + return &oldObj.RequiredNegativeMinimumField + }) + errs = append(errs, fn(fldPath.Child("requiredNegativeMinimumField"), &obj.RequiredNegativeMinimumField, oldVal, oldObj != nil)...) + } + + { // field NegativeMinimumStruct.RequiredNegativeMinimumPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, -10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *NegativeMinimumStruct) *int { + return oldObj.RequiredNegativeMinimumPtrField + }) + errs = append(errs, fn(fldPath.Child("requiredNegativeMinimumPtrField"), obj.RequiredNegativeMinimumPtrField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_OptionalStruct validates an instance of OptionalStruct according +// to declarative validation rules in the API schema. +func Validate_OptionalStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OptionalStruct) (errs field.ErrorList) { + + // field OptionalStruct.TypeMeta has no validation + + { // field OptionalStruct.OptionalIntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *OptionalStruct) *int { + return &oldObj.OptionalIntField + }) + errs = append(errs, fn(fldPath.Child("optionalIntField"), &obj.OptionalIntField, oldVal, oldObj != nil)...) + } + + { // field OptionalStruct.OptionalIntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *OptionalStruct) *int { + return oldObj.OptionalIntPtrField + }) + errs = append(errs, fn(fldPath.Child("optionalIntPtrField"), obj.OptionalIntPtrField, oldVal, oldObj != nil)...) + } + + { // field OptionalStruct.OptionalTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *OptionalStruct) *IntType { + return &oldObj.OptionalTypedefField + }) + errs = append(errs, fn(fldPath.Child("optionalTypedefField"), &obj.OptionalTypedefField, oldVal, oldObj != nil)...) + } + + { // field OptionalStruct.OptionalTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *OptionalStruct) *IntType { + return oldObj.OptionalTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("optionalTypedefPtrField"), obj.OptionalTypedefPtrField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_RequiredStruct validates an instance of RequiredStruct according +// to declarative validation rules in the API schema. +func Validate_RequiredStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *RequiredStruct) (errs field.ErrorList) { + + // field RequiredStruct.TypeMeta has no validation + + { // field RequiredStruct.RequiredIntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *RequiredStruct) *int { + return &oldObj.RequiredIntField + }) + errs = append(errs, fn(fldPath.Child("requiredIntField"), &obj.RequiredIntField, oldVal, oldObj != nil)...) + } + + { // field RequiredStruct.RequiredIntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *RequiredStruct) *int { + return oldObj.RequiredIntPtrField + }) + errs = append(errs, fn(fldPath.Child("requiredIntPtrField"), obj.RequiredIntPtrField, oldVal, oldObj != nil)...) + } + + { // field RequiredStruct.RequiredTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *RequiredStruct) *IntType { + return &oldObj.RequiredTypedefField + }) + errs = append(errs, fn(fldPath.Child("requiredTypedefField"), &obj.RequiredTypedefField, oldVal, oldObj != nil)...) + } + + { // field RequiredStruct.RequiredTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *RequiredStruct) *IntType { + return oldObj.RequiredTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("requiredTypedefPtrField"), obj.RequiredTypedefPtrField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc.go new file mode 100644 index 0000000000..a59a296bf6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc.go @@ -0,0 +1,44 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofprimitive + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:minItems=0 + Min0Field []int `json:"min0Field"` + + // +k8s:minItems=10 + Min10Field []int `json:"min10Field"` + + // +k8s:minItems=0 + Min0TypedefField []IntType `json:"min0TypedefField"` + + // +k8s:minItems=10 + Min10TypedefField []IntType `json:"min10TypedefField"` +} + +type IntType int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc_test.go new file mode 100644 index 0000000000..12b092016d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/doc_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2024 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 sliceofprimitive + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min0Field: make([]int, 0), + Min10Field: make([]int, 0), + Min0TypedefField: make([]IntType, 0), + Min10TypedefField: make([]IntType, 0), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]int, 1), + Min10TypedefField: make([]IntType, 1), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 1, 10), + field.TooFew(field.NewPath("min10TypedefField"), 1, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]int, 9), + Min10TypedefField: make([]IntType, 9), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 9, 10), + field.TooFew(field.NewPath("min10TypedefField"), 9, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]int, 10), + Min10TypedefField: make([]IntType, 10), + }).ExpectValid() + + testVal := &Struct{ + Min0Field: make([]int, 1), + Min10Field: make([]int, 11), + Min0TypedefField: make([]IntType, 1), + Min10TypedefField: make([]IntType, 11), + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min0Field: make([]int, 1), + Min10Field: make([]int, 1), + Min0TypedefField: make([]IntType, 1), + Min10TypedefField: make([]IntType, 1), + }).OldValue(&Struct{ + Min0Field: make([]int, 1), + Min10Field: make([]int, 1), + Min0TypedefField: make([]IntType, 1), + Min10TypedefField: make([]IntType, 1), + }).ExpectValid() + +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/zz_generated.validations.go new file mode 100644 index 0000000000..936c8efef3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_primitive/zz_generated.validations.go @@ -0,0 +1,164 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofprimitive + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Min0Field + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []int { + return oldObj.Min0Field + }) + errs = append(errs, fn(fldPath.Child("min0Field"), obj.Min0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10Field + fn := func( + fldPath *field.Path, + obj, oldObj []int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []int { + return oldObj.Min10Field + }) + errs = append(errs, fn(fldPath.Child("min10Field"), obj.Min10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min0TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []IntType { + return oldObj.Min0TypedefField + }) + errs = append(errs, fn(fldPath.Child("min0TypedefField"), obj.Min0TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []IntType { + return oldObj.Min10TypedefField + }) + errs = append(errs, fn(fldPath.Child("min10TypedefField"), obj.Min10TypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc.go new file mode 100644 index 0000000000..d2c18683f7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc.go @@ -0,0 +1,46 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sliceofstruct + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:minItems=0 + Min0Field []OtherStruct `json:"min0Field"` + + // +k8s:minItems=10 + Min10Field []OtherStruct `json:"min10Field"` + + // +k8s:minItems=0 + Min0TypedefField []OtherTypedefStruct `json:"min0TypedefField"` + + // +k8s:minItems=10 + Min10TypedefField []OtherTypedefStruct `json:"min10TypedefField"` +} + +type OtherStruct struct{} + +type OtherTypedefStruct OtherStruct diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc_test.go new file mode 100644 index 0000000000..110b293761 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/doc_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2024 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 sliceofstruct + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min0Field: make([]OtherStruct, 0), + Min10Field: make([]OtherStruct, 0), + Min0TypedefField: make([]OtherTypedefStruct, 0), + Min10TypedefField: make([]OtherTypedefStruct, 0), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]OtherStruct, 1), + Min10TypedefField: make([]OtherTypedefStruct, 1), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 1, 10), + field.TooFew(field.NewPath("min10TypedefField"), 1, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]OtherStruct, 9), + Min10TypedefField: make([]OtherTypedefStruct, 9), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 9, 10), + field.TooFew(field.NewPath("min10TypedefField"), 9, 10), + }) + + st.Value(&Struct{ + Min10Field: make([]OtherStruct, 10), + Min10TypedefField: make([]OtherTypedefStruct, 10), + }).ExpectValid() + + testVal := &Struct{ + Min0Field: make([]OtherStruct, 1), + Min10Field: make([]OtherStruct, 11), + Min0TypedefField: make([]OtherTypedefStruct, 1), + Min10TypedefField: make([]OtherTypedefStruct, 11), + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min0Field: make([]OtherStruct, 1), + Min10Field: make([]OtherStruct, 1), + Min0TypedefField: make([]OtherTypedefStruct, 1), + Min10TypedefField: make([]OtherTypedefStruct, 1), + }).OldValue(&Struct{ + Min0Field: make([]OtherStruct, 1), + Min10Field: make([]OtherStruct, 1), + Min0TypedefField: make([]OtherTypedefStruct, 1), + Min10TypedefField: make([]OtherTypedefStruct, 1), + }).ExpectValid() + +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/zz_generated.validations.go new file mode 100644 index 0000000000..0e7aae045a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/slice_of_struct/zz_generated.validations.go @@ -0,0 +1,164 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sliceofstruct + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Min0Field + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.Min0Field + }) + errs = append(errs, fn(fldPath.Child("min0Field"), obj.Min0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10Field + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.Min10Field + }) + errs = append(errs, fn(fldPath.Child("min10Field"), obj.Min10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min0TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherTypedefStruct { + return oldObj.Min0TypedefField + }) + errs = append(errs, fn(fldPath.Child("min0TypedefField"), obj.Min0TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherTypedefStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherTypedefStruct { + return oldObj.Min10TypedefField + }) + errs = append(errs, fn(fldPath.Child("min10TypedefField"), obj.Min10TypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc.go new file mode 100644 index 0000000000..1c73fa6e91 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package typedeftoslice + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: no validation here +type UnvalidatedType []int + +// +k8s:minItems=0 +type Min0Type []int + +// +k8s:minItems=10 +type Min10Type []int + +// Note: no validation here +type UnvalidatedPtrType []*int + +type SliceType []int + +// +k8s:minItems=0 +type Min0TypedefType SliceType + +// +k8s:minItems=10 +type Min10TypedefType SliceType + +type Struct struct { + TypeMeta int + + UnvalidatedField UnvalidatedType `json:"unvalidatedField"` + + Min0Field Min0Type `json:"min0Field"` + + Min10Field Min10Type `json:"min10Field"` + + Min0TypedefField Min0TypedefType `json:"min0TypedefField"` + + Min10TypedefField Min10TypedefType `json:"min10TypedefField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc_test.go new file mode 100644 index 0000000000..e82ebcb7e3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/doc_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2024 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 typedeftoslice + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min0Field: make(Min0Type, 0), + Min10Field: make(Min10Type, 0), + Min0TypedefField: make(Min0TypedefType, 0), + Min10TypedefField: make(Min10TypedefType, 0), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min10Field: make(Min10Type, 1), + Min10TypedefField: make(Min10TypedefType, 1), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 1, 10), + field.TooFew(field.NewPath("min10TypedefField"), 1, 10), + }) + + st.Value(&Struct{ + Min10Field: make(Min10Type, 9), + Min10TypedefField: make(Min10TypedefType, 9), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 9, 10), + field.TooFew(field.NewPath("min10TypedefField"), 9, 10), + }) + + st.Value(&Struct{ + Min10Field: make(Min10Type, 10), + Min10TypedefField: make(Min10TypedefType, 10), + }).ExpectValid() + + testVal := &Struct{ + Min0Field: make(Min0Type, 1), + Min10Field: make(Min10Type, 11), + Min0TypedefField: make(Min0TypedefType, 1), + Min10TypedefField: make(Min10TypedefType, 11), + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min0Field: make(Min0Type, 1), + Min10Field: make(Min10Type, 1), + Min0TypedefField: make(Min0TypedefType, 1), + Min10TypedefField: make(Min10TypedefType, 1), + }).OldValue(&Struct{ + Min0Field: make(Min0Type, 1), + Min10Field: make(Min10Type, 1), + Min0TypedefField: make(Min0TypedefType, 1), + Min10TypedefField: make(Min10TypedefType, 1), + }).ExpectValid() + +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/zz_generated.validations.go new file mode 100644 index 0000000000..cb10223c31 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minitems/typedef_to_slice/zz_generated.validations.go @@ -0,0 +1,209 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedeftoslice + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Min0Type validates an instance of Min0Type according +// to declarative validation rules in the API schema. +func Validate_Min0Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj Min0Type) (errs field.ErrorList) { + + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Min0TypedefType validates an instance of Min0TypedefType according +// to declarative validation rules in the API schema. +func Validate_Min0TypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj Min0TypedefType) (errs field.ErrorList) { + + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Min10Type validates an instance of Min10Type according +// to declarative validation rules in the API schema. +func Validate_Min10Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj Min10Type) (errs field.ErrorList) { + + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Min10TypedefType validates an instance of Min10TypedefType according +// to declarative validation rules in the API schema. +func Validate_Min10TypedefType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj Min10TypedefType) (errs field.ErrorList) { + + if e := validate.MinItems(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + // field Struct.UnvalidatedField has no validation + + { // field Struct.Min0Field + fn := func( + fldPath *field.Path, + obj, oldObj Min0Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Min0Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) Min0Type { + return oldObj.Min0Field + }) + errs = append(errs, fn(fldPath.Child("min0Field"), obj.Min0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10Field + fn := func( + fldPath *field.Path, + obj, oldObj Min10Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Min10Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) Min10Type { + return oldObj.Min10Field + }) + errs = append(errs, fn(fldPath.Child("min10Field"), obj.Min10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min0TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj Min0TypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Min0TypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) Min0TypedefType { + return oldObj.Min0TypedefField + }) + errs = append(errs, fn(fldPath.Child("min0TypedefField"), obj.Min0TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj Min10TypedefType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Min10TypedefType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) Min10TypedefType { + return oldObj.Min10TypedefField + }) + errs = append(errs, fn(fldPath.Child("min10TypedefField"), obj.Min10TypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc.go new file mode 100644 index 0000000000..ec68de8ec0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc.go @@ -0,0 +1,105 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +package minlength + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:alpha=+k8s:minLength=0 + // +k8s:optional + Min0PtrField *string `json:"min0PtrField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2Field string `json:"min2Field"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2PtrField *string `json:"min2PtrField"` + + // +k8s:alpha=+k8s:minLength=10 + // +k8s:optional + Min10Field string `json:"min10Field"` + + // +k8s:alpha=+k8s:minLength=10 + // +k8s:optional + Min10PtrField *string `json:"min10PtrField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2UnvalidatedTypedefField UnvalidatedStringType `json:"min2UnvalidatedTypedefField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"min2UnvalidatedTypedefPtrField"` + + // +k8s:alpha=+k8s:minLength=10 + // +k8s:optional + Min10UnvalidatedTypedefField UnvalidatedStringType `json:"min10UnvalidatedTypedefField"` + + // +k8s:alpha=+k8s:minLength=10 + // +k8s:optional + Min10UnvalidatedTypedefPtrField *UnvalidatedStringType `json:"min10UnvalidatedTypedefPtrField"` + + // Note: no minlength validation here + // +k8s:optional + Min2ValidatedTypedefField Min2Type `json:"min2ValidatedTypedefField"` + + // Note: no minlength validation here + // +k8s:optional + Min2ValidatedTypedefPtrField *Min2Type `json:"min2ValidatedTypedefPtrField"` + + // Note: no minlength validation here + // +k8s:optional + Min10ValidatedTypedefField Min10Type `json:"min10ValidatedTypedefField"` + + // Note: no minlength validation here + // +k8s:optional + Min10ValidatedTypedefPtrField *Min10Type `json:"min10ValidatedTypedefPtrField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2UnvalidatedStringAliasField UnvalidatedStringAlias `json:"min2UnvalidatedStringAliasField"` + + // +k8s:alpha=+k8s:minLength=2 + // +k8s:optional + Min2UnvalidatedStringAliasPtrField *UnvalidatedStringAlias `json:"min2UnvalidatedStringAliasPtrField"` +} + +// Note: no validation here +type UnvalidatedStringType string + +// Note: no validation here +type UnvalidatedStringAlias = string + +// Tests that min length markers on typedefs +// appropriately propagate to fields that use this type +// +k8s:alpha=+k8s:minLength=2 +type Min2Type string + +// Tests that min length markers on typedefs +// appropriately propagate to fields that use this type +// +k8s:alpha=+k8s:minLength=10 +type Min10Type string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc_test.go new file mode 100644 index 0000000000..408a47cc63 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/doc_test.go @@ -0,0 +1,160 @@ +/* +Copyright 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 minlength + +import ( + "strings" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectValid() + + st.Value(&Struct{ + Min2Field: strings.Repeat("x", 1), + Min2PtrField: ptr.To(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 1)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 1))), + Min10Field: strings.Repeat("x", 1), + Min10PtrField: ptr.To(strings.Repeat("x", 1)), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 1)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 1))), + Min2UnvalidatedStringAliasField: strings.Repeat("x", 1), + Min2UnvalidatedStringAliasPtrField: ptr.To(strings.Repeat("x", 1)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooShort(field.NewPath("min10Field"), "", 10), + field.TooShort(field.NewPath("min10PtrField"), "", 10), + field.TooShort(field.NewPath("min10UnvalidatedTypedefField"), "", 10), + field.TooShort(field.NewPath("min10UnvalidatedTypedefPtrField"), "", 10), + field.TooShort(field.NewPath("min10ValidatedTypedefField"), "", 10), + field.TooShort(field.NewPath("min10ValidatedTypedefPtrField"), "", 10), + field.TooShort(field.NewPath("min2Field"), "x", 2), + field.TooShort(field.NewPath("min2PtrField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedTypedefField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedTypedefPtrField"), "x", 2), + field.TooShort(field.NewPath("min2ValidatedTypedefField"), "x", 2), + field.TooShort(field.NewPath("min2ValidatedTypedefPtrField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedStringAliasField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedStringAliasPtrField"), "x", 2), + }) + + st.Value(&Struct{ + Min2Field: strings.Repeat("x", 1), + Min2PtrField: ptr.To(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 1)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 1))), + Min10Field: strings.Repeat("x", 9), + Min10PtrField: ptr.To(strings.Repeat("x", 9)), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 9)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 9))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 9)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 9))), + Min2UnvalidatedStringAliasField: strings.Repeat("x", 1), + Min2UnvalidatedStringAliasPtrField: ptr.To(strings.Repeat("x", 1)), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooShort(field.NewPath("min10Field"), "", 10), + field.TooShort(field.NewPath("min10PtrField"), "", 10), + field.TooShort(field.NewPath("min10UnvalidatedTypedefField"), "", 10), + field.TooShort(field.NewPath("min10UnvalidatedTypedefPtrField"), "", 10), + field.TooShort(field.NewPath("min10ValidatedTypedefField"), "", 10), + field.TooShort(field.NewPath("min10ValidatedTypedefPtrField"), "", 10), + field.TooShort(field.NewPath("min2Field"), "x", 2), + field.TooShort(field.NewPath("min2PtrField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedTypedefField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedTypedefPtrField"), "x", 2), + field.TooShort(field.NewPath("min2ValidatedTypedefField"), "x", 2), + field.TooShort(field.NewPath("min2ValidatedTypedefPtrField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedStringAliasField"), "x", 2), + field.TooShort(field.NewPath("min2UnvalidatedStringAliasPtrField"), "x", 2), + }) + + st.Value(&Struct{ + Min2Field: strings.Repeat("x", 2), + Min2PtrField: ptr.To(strings.Repeat("x", 2)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 2)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 2))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 2)), + Min10Field: strings.Repeat("x", 10), + Min10PtrField: ptr.To(strings.Repeat("x", 10)), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 10)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 10))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 10)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 10))), + Min2UnvalidatedStringAliasField: strings.Repeat("x", 2), + Min2UnvalidatedStringAliasPtrField: ptr.To(strings.Repeat("x", 2)), + }).ExpectValid() + + testVal := &Struct{ + Min2Field: strings.Repeat("x", 3), + Min2PtrField: ptr.To(strings.Repeat("x", 3)), + Min10Field: strings.Repeat("x", 11), + Min10PtrField: ptr.To(strings.Repeat("x", 11)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 3)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 3))), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 3)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 3))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 11)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 11))), + Min2UnvalidatedStringAliasField: strings.Repeat("x", 3), + Min2UnvalidatedStringAliasPtrField: ptr.To(strings.Repeat("x", 3)), + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min2Field: strings.Repeat("x", 2), + Min2PtrField: ptr.To(strings.Repeat("x", 2)), + Min10Field: strings.Repeat("x", 11), + Min10PtrField: ptr.To(strings.Repeat("x", 11)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 2)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 2))), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 11)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 11))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 2)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 2))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 11)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 11))), + }).OldValue(&Struct{ + Min2Field: strings.Repeat("x", 1), + Min2PtrField: ptr.To(strings.Repeat("x", 1)), + Min10Field: strings.Repeat("x", 9), + Min10PtrField: ptr.To(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 1)), + Min2UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 1))), + Min10UnvalidatedTypedefField: UnvalidatedStringType(strings.Repeat("x", 9)), + Min10UnvalidatedTypedefPtrField: ptr.To(UnvalidatedStringType(strings.Repeat("x", 9))), + Min2ValidatedTypedefField: Min2Type(strings.Repeat("x", 1)), + Min2ValidatedTypedefPtrField: ptr.To(Min2Type(strings.Repeat("x", 1))), + Min10ValidatedTypedefField: Min10Type(strings.Repeat("x", 9)), + Min10ValidatedTypedefPtrField: ptr.To(Min10Type(strings.Repeat("x", 9))), + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/zz_generated.validations.go new file mode 100644 index 0000000000..e5a87695cd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minlength/zz_generated.validations.go @@ -0,0 +1,551 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package minlength + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Min10Type validates an instance of Min10Type according +// to declarative validation rules in the API schema. +func Validate_Min10Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Min10Type) (errs field.ErrorList) { + + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Min2Type validates an instance of Min2Type according +// to declarative validation rules in the API schema. +func Validate_Min2Type( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Min2Type) (errs field.ErrorList) { + + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Min0PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Min0PtrField + }) + errs = append(errs, fn(fldPath.Child("min0PtrField"), obj.Min0PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min2Field + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.Min2Field + }) + errs = append(errs, fn(fldPath.Child("min2Field"), &obj.Min2Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min2PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Min2PtrField + }) + errs = append(errs, fn(fldPath.Child("min2PtrField"), obj.Min2PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10Field + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.Min10Field + }) + errs = append(errs, fn(fldPath.Child("min10Field"), &obj.Min10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10PtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Min10PtrField + }) + errs = append(errs, fn(fldPath.Child("min10PtrField"), obj.Min10PtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min2UnvalidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return &oldObj.Min2UnvalidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("min2UnvalidatedTypedefField"), &obj.Min2UnvalidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min2UnvalidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return oldObj.Min2UnvalidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("min2UnvalidatedTypedefPtrField"), obj.Min2UnvalidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10UnvalidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return &oldObj.Min10UnvalidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("min10UnvalidatedTypedefField"), &obj.Min10UnvalidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10UnvalidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *UnvalidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 10).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnvalidatedStringType { + return oldObj.Min10UnvalidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("min10UnvalidatedTypedefPtrField"), obj.Min10UnvalidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min2ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *Min2Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Min2Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Min2Type { + return &oldObj.Min2ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("min2ValidatedTypedefField"), &obj.Min2ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min2ValidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Min2Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Min2Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Min2Type { + return oldObj.Min2ValidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("min2ValidatedTypedefPtrField"), obj.Min2ValidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *Min10Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Min10Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Min10Type { + return &oldObj.Min10ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("min10ValidatedTypedefField"), &obj.Min10ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10ValidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Min10Type, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_Min10Type(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Min10Type { + return oldObj.Min10ValidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("min10ValidatedTypedefPtrField"), obj.Min10ValidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min2UnvalidatedStringAliasField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.Min2UnvalidatedStringAliasField + }) + errs = append(errs, fn(fldPath.Child("min2UnvalidatedStringAliasField"), &obj.Min2UnvalidatedStringAliasField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min2UnvalidatedStringAliasPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MinLength(ctx, op, fldPath, obj, oldObj, 2).MarkAlpha(); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.Min2UnvalidatedStringAliasPtrField + }) + errs = append(errs, fn(fldPath.Child("min2UnvalidatedStringAliasPtrField"), obj.Min2UnvalidatedStringAliasPtrField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/doc.go new file mode 100644 index 0000000000..d551a30e23 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/doc.go @@ -0,0 +1,44 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package minproperties + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:minProperties=0 + Min0Field map[string]string `json:"min0Field"` + + // +k8s:minProperties=10 + Min10Field map[string]string `json:"min10Field"` + + // +k8s:minProperties=0 + Min0TypedefField map[string]StringType `json:"min0TypedefField"` + + // +k8s:minProperties=10 + Min10TypedefField map[string]StringType `json:"min10TypedefField"` +} + +type StringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/doc_test.go new file mode 100644 index 0000000000..2af306b0f3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/doc_test.go @@ -0,0 +1,123 @@ +/* +Copyright 2025 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 minproperties + +import ( + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + st.Value(&Struct{ + Min0Field: make(map[string]string, 0), + Min10Field: make(map[string]string, 0), + Min0TypedefField: make(map[string]StringType, 0), + Min10TypedefField: make(map[string]StringType, 0), + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 0, 10), + field.TooFew(field.NewPath("min10TypedefField"), 0, 10), + }) + + min10Field1 := make(map[string]string) + min10TypedefField1 := make(map[string]StringType) + for i := range 1 { + min10Field1[fmt.Sprintf("k%d", i)] = "v" + min10TypedefField1[fmt.Sprintf("k%d", i)] = "v" + } + + st.Value(&Struct{ + Min10Field: min10Field1, + Min10TypedefField: min10TypedefField1, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 1, 10), + field.TooFew(field.NewPath("min10TypedefField"), 1, 10), + }) + + min10Field9 := make(map[string]string) + min10TypedefField9 := make(map[string]StringType) + for i := range 9 { + min10Field9[fmt.Sprintf("k%d", i)] = "v" + min10TypedefField9[fmt.Sprintf("k%d", i)] = "v" + } + + st.Value(&Struct{ + Min10Field: min10Field9, + Min10TypedefField: min10TypedefField9, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooFew(field.NewPath("min10Field"), 9, 10), + field.TooFew(field.NewPath("min10TypedefField"), 9, 10), + }) + + min10Field10 := make(map[string]string) + min10TypedefField10 := make(map[string]StringType) + for i := range 10 { + min10Field10[fmt.Sprintf("k%d", i)] = "v" + min10TypedefField10[fmt.Sprintf("k%d", i)] = "v" + } + + st.Value(&Struct{ + Min10Field: min10Field10, + Min10TypedefField: min10TypedefField10, + }).ExpectValid() + + min0Field1 := make(map[string]string) + min10Field11 := make(map[string]string) + min0TypedefField1 := make(map[string]StringType) + min10TypedefField11 := make(map[string]StringType) + + for i := range 1 { + min0Field1[fmt.Sprintf("k%d", i)] = "v" + min0TypedefField1[fmt.Sprintf("k%d", i)] = "v" + } + for i := range 11 { + min10Field11[fmt.Sprintf("k%d", i)] = "v" + min10TypedefField11[fmt.Sprintf("k%d", i)] = "v" + } + + testVal := &Struct{ + Min0Field: min0Field1, + Min10Field: min10Field11, + Min0TypedefField: min0TypedefField1, + Min10TypedefField: min10TypedefField11, + } + st.Value(testVal).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{ + Min0Field: min0Field1, + Min10Field: min10Field11, + Min0TypedefField: min0TypedefField1, + Min10TypedefField: min10TypedefField11, + }).OldValue(&Struct{ + Min0Field: min0Field1, + Min10Field: min10Field1, + Min0TypedefField: min0TypedefField1, + Min10TypedefField: min10TypedefField1, + }).ExpectValid() + +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/zz_generated.validations.go new file mode 100644 index 0000000000..2488fbeda4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/minproperties/zz_generated.validations.go @@ -0,0 +1,164 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package minproperties + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Min0Field + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinProperties(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.Min0Field + }) + errs = append(errs, fn(fldPath.Child("min0Field"), obj.Min0Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10Field + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinProperties(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.Min10Field + }) + errs = append(errs, fn(fldPath.Child("min10Field"), obj.Min10Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Min0TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinProperties(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]StringType { + return oldObj.Min0TypedefField + }) + errs = append(errs, fn(fldPath.Child("min0TypedefField"), obj.Min0TypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.Min10TypedefField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.MinProperties(ctx, op, fldPath, obj, oldObj, 10); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]StringType { + return oldObj.Min10TypedefField + }) + errs = append(errs, fn(fldPath.Child("min10TypedefField"), obj.Min10TypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/doc.go new file mode 100644 index 0000000000..c97bdf4e73 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/doc.go @@ -0,0 +1,115 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package mode + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type StrictUnion struct { + TypeMeta int + + // +k8s:modeDiscriminator + D1 string `json:"d1"` + + // +k8s:ifMode("A")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:ifMode("B")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type SharedField struct { + TypeMeta int + + // +k8s:modeDiscriminator + D1 string `json:"d1"` + + // Valid in A and B, implicitly forbidden in C. + // +k8s:ifMode("A")=+k8s:optional + // +k8s:ifMode("B")=+k8s:optional + FieldA *string `json:"fieldA,omitempty"` +} + +type ChainedValidation struct { + TypeMeta int + + // +k8s:modeDiscriminator + D1 string `json:"d1"` + + // In mode A, it is required AND must have maxLength 5. + // +k8s:ifMode("A")=+k8s:required + // +k8s:ifMode("A")=+k8s:maxLength=5 + FieldA *string `json:"fieldA,omitempty"` +} + +type ImplicitForbidden struct { + TypeMeta int + + // +k8s:modeDiscriminator + D1 string `json:"d1"` + + // Field is only mentioned for mode A. Mode B should implicitly forbid it. + // +k8s:ifMode("A")=+k8s:optional + FieldA *string `json:"fieldA,omitempty"` +} + +type NonStringDiscriminator struct { + TypeMeta int + + // +k8s:modeDiscriminator(modality:"Bool") + D1 bool `json:"d1"` + + // +k8s:ifMode(modality:"Bool", mode:"true")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` +} + +type MultipleDiscriminators struct { + TypeMeta int + + // +k8s:modeDiscriminator(modality:"D1") + D1 string `json:"d1"` + + // +k8s:modeDiscriminator(modality:"D2") + D2 string `json:"d2"` + + // +k8s:ifMode(modality:"D1", mode:"A")=+k8s:required + FieldA *string `json:"fieldA,omitempty"` + + // +k8s:ifMode(modality:"D2", mode:"B")=+k8s:required + FieldB *string `json:"fieldB,omitempty"` +} + +type Collections struct { + TypeMeta int + + // +k8s:modeDiscriminator + D1 string `json:"d1"` + + // +k8s:ifMode("A")=+k8s:optional + ListField []string `json:"listField,omitempty"` + + // +k8s:ifMode("A")=+k8s:optional + MapField map[string]string `json:"mapField,omitempty"` +} + +type TypeMeta int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/doc_test.go new file mode 100644 index 0000000000..94cfbc66a4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/doc_test.go @@ -0,0 +1,195 @@ +/* +Copyright 2025 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 mode + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestStrictUnion(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Mode A: FieldA required, FieldB implicitly forbidden + st.Value(&StrictUnion{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + st.Value(&StrictUnion{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), ""), + }) + st.Value(&StrictUnion{D1: "A", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldB"), ""), + }) + + // Mode B: FieldA implicitly forbidden, FieldB required + st.Value(&StrictUnion{D1: "B", FieldB: ptr.To("val")}).ExpectValid() + st.Value(&StrictUnion{D1: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldB"), ""), + }) + st.Value(&StrictUnion{D1: "B", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestSharedField(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid (optional) in A and B + st.Value(&SharedField{D1: "A"}).ExpectValid() + st.Value(&SharedField{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + st.Value(&SharedField{D1: "B"}).ExpectValid() + st.Value(&SharedField{D1: "B", FieldA: ptr.To("val")}).ExpectValid() + + // Forbidden in C + st.Value(&SharedField{D1: "C", FieldA: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestChainedValidation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Mode A: Required AND maxLength 5 + st.Value(&ChainedValidation{D1: "A", FieldA: ptr.To("abc")}).ExpectValid() + st.Value(&ChainedValidation{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), ""), + }) + st.Value(&ChainedValidation{D1: "A", FieldA: ptr.To("too-long")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("fieldA"), "too-long", 5), + }) + + // Mode B: Unlisted, so implicitly forbidden + st.Value(&ChainedValidation{D1: "B", FieldA: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestImplicitForbidden(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Mode A: Optional + st.Value(&ImplicitForbidden{D1: "A"}).ExpectValid() + st.Value(&ImplicitForbidden{D1: "A", FieldA: ptr.To("val")}).ExpectValid() + + // Mode B: Not listed, so implicitly Forbidden + st.Value(&ImplicitForbidden{D1: "B", FieldA: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestNonStringDiscriminator(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Bool mode + st.Value(&NonStringDiscriminator{D1: true, FieldA: ptr.To("val")}).ExpectValid() + st.Value(&NonStringDiscriminator{D1: true}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), ""), + }) + st.Value(&NonStringDiscriminator{D1: false, FieldA: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("fieldA"), ""), + }) +} + +func TestMultipleDiscriminators(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&MultipleDiscriminators{ + D1: "A", + D2: "B", + FieldA: ptr.To("valA"), + FieldB: ptr.To("valB"), + }).ExpectValid() + + st.Value(&MultipleDiscriminators{ + D1: "A", + D2: "B", + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("fieldA"), ""), + field.Required(field.NewPath("fieldB"), ""), + }) +} + +func TestCollections(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Mode A: Collections are valid (optional) + st.Value(&Collections{ + D1: "A", + }).ExpectValid() + + st.Value(&Collections{ + D1: "A", + ListField: []string{"item"}, + MapField: map[string]string{"key": "val"}, + }).ExpectValid() + + // Mode B: Unlisted, so implicitly forbidden + st.Value(&Collections{ + D1: "B", + ListField: []string{"item"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("listField"), ""), + }) + + st.Value(&Collections{ + D1: "B", + MapField: map[string]string{"key": "val"}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Forbidden(field.NewPath("mapField"), ""), + }) +} + +func TestRatcheting(t *testing.T) { + mkTest := func() *ChainedValidation { + return &ChainedValidation{ + D1: "A", + FieldA: ptr.To("too-long-string"), + } + } + + st := localSchemeBuilder.Test(t) + + // 1. New object is invalid + st.Value(mkTest()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("fieldA"), "too-long-string", 5), + }) + + // 2. Unchanged update is valid (ratcheting) + st.Value(mkTest()).OldValue(mkTest()).ExpectValid() + + // 3. Changed value re-validates (and fails) + mkDifferent := func() *ChainedValidation { + return &ChainedValidation{ + D1: "A", + FieldA: ptr.To("also-too-long"), + } + } + st.Value(mkTest()).OldValue(mkDifferent()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("fieldA"), "too-long-string", 5), + }) + + // 4. Changed discriminator re-validates (and fails) + mkDifferentDisc := func() *ChainedValidation { + return &ChainedValidation{ + D1: "B", // Discriminator changed from B -> A + FieldA: ptr.To("too-long-string"), + } + } + st.Value(mkTest()).OldValue(mkDifferentDisc()).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.TooLong(field.NewPath("fieldA"), "too-long-string", 5), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/zz_generated.validations.go new file mode 100644 index 0000000000..d3968299c2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/mode/zz_generated.validations.go @@ -0,0 +1,539 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package mode + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ChainedValidation + scheme.AddValidationFunc( + (*ChainedValidation)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ChainedValidation( + ctx, op, nil, /* fldPath */ + obj.(*ChainedValidation), + safe.Cast[*ChainedValidation](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Collections + scheme.AddValidationFunc( + (*Collections)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Collections( + ctx, op, nil, /* fldPath */ + obj.(*Collections), + safe.Cast[*Collections](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ImplicitForbidden + scheme.AddValidationFunc( + (*ImplicitForbidden)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ImplicitForbidden( + ctx, op, nil, /* fldPath */ + obj.(*ImplicitForbidden), + safe.Cast[*ImplicitForbidden](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type MultipleDiscriminators + scheme.AddValidationFunc( + (*MultipleDiscriminators)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_MultipleDiscriminators( + ctx, op, nil, /* fldPath */ + obj.(*MultipleDiscriminators), + safe.Cast[*MultipleDiscriminators](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type NonStringDiscriminator + scheme.AddValidationFunc( + (*NonStringDiscriminator)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_NonStringDiscriminator( + ctx, op, nil, /* fldPath */ + obj.(*NonStringDiscriminator), + safe.Cast[*NonStringDiscriminator](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type SharedField + scheme.AddValidationFunc( + (*SharedField)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_SharedField( + ctx, op, nil, /* fldPath */ + obj.(*SharedField), + safe.Cast[*SharedField](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StrictUnion + scheme.AddValidationFunc( + (*StrictUnion)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StrictUnion( + ctx, op, nil, /* fldPath */ + obj.(*StrictUnion), + safe.Cast[*StrictUnion](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ChainedValidation validates an instance of ChainedValidation according +// to declarative validation rules in the API schema. +func Validate_ChainedValidation( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ChainedValidation) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *ChainedValidation) *string { return obj.FieldA }, + func(obj *ChainedValidation) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5)...) + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field ChainedValidation.TypeMeta has no validation + // field ChainedValidation.D1 has no validation + // field ChainedValidation.FieldA has no validation + return errs +} + +// Validate_Collections validates an instance of Collections according +// to declarative validation rules in the API schema. +func Validate_Collections( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Collections) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "listField", + func(obj *Collections) []string { return obj.ListField }, + func(obj *Collections) string { return obj.D1 }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[[]string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "mapField", + func(obj *Collections) map[string]string { return obj.MapField }, + func(obj *Collections) string { return obj.D1 }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenMap(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[map[string]string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Collections.TypeMeta has no validation + // field Collections.D1 has no validation + // field Collections.ListField has no validation + // field Collections.MapField has no validation + return errs +} + +// Validate_ImplicitForbidden validates an instance of ImplicitForbidden according +// to declarative validation rules in the API schema. +func Validate_ImplicitForbidden( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ImplicitForbidden) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *ImplicitForbidden) *string { return obj.FieldA }, + func(obj *ImplicitForbidden) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field ImplicitForbidden.TypeMeta has no validation + // field ImplicitForbidden.D1 has no validation + // field ImplicitForbidden.FieldA has no validation + return errs +} + +// Validate_MultipleDiscriminators validates an instance of MultipleDiscriminators according +// to declarative validation rules in the API schema. +func Validate_MultipleDiscriminators( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MultipleDiscriminators) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *MultipleDiscriminators) *string { return obj.FieldA }, + func(obj *MultipleDiscriminators) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", + func(obj *MultipleDiscriminators) *string { return obj.FieldB }, + func(obj *MultipleDiscriminators) string { return obj.D2 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field MultipleDiscriminators.TypeMeta has no validation + // field MultipleDiscriminators.D1 has no validation + // field MultipleDiscriminators.D2 has no validation + // field MultipleDiscriminators.FieldA has no validation + // field MultipleDiscriminators.FieldB has no validation + return errs +} + +// Validate_NonStringDiscriminator validates an instance of NonStringDiscriminator according +// to declarative validation rules in the API schema. +func Validate_NonStringDiscriminator( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *NonStringDiscriminator) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *NonStringDiscriminator) *string { return obj.FieldA }, + func(obj *NonStringDiscriminator) bool { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, bool]{ + + { + Value: true, + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field NonStringDiscriminator.TypeMeta has no validation + // field NonStringDiscriminator.D1 has no validation + // field NonStringDiscriminator.FieldA has no validation + return errs +} + +// Validate_SharedField validates an instance of SharedField according +// to declarative validation rules in the API schema. +func Validate_SharedField( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *SharedField) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *SharedField) *string { return obj.FieldA }, + func(obj *SharedField) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field SharedField.TypeMeta has no validation + // field SharedField.D1 has no validation + // field SharedField.FieldA has no validation + return errs +} + +// Validate_StrictUnion validates an instance of StrictUnion according +// to declarative validation rules in the API schema. +func Validate_StrictUnion( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StrictUnion) (errs field.ErrorList) { + + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *StrictUnion) *string { return obj.FieldA }, + func(obj *StrictUnion) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", + func(obj *StrictUnion) *string { return obj.FieldB }, + func(obj *StrictUnion) string { return obj.D1 }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field StrictUnion.TypeMeta has no validation + // field StrictUnion.D1 has no validation + // field StrictUnion.FieldA has no validation + // field StrictUnion.FieldB has no validation + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/doc.go new file mode 100644 index 0000000000..f72f2a9ebf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/doc.go @@ -0,0 +1,82 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package monotonic + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:minimum=0 + // +k8s:monotonic + IntField int `json:"intField"` + + // +k8s:minimum=0 + // +k8s:monotonic + Int64Field int64 `json:"int64Field"` + + // +k8s:minimum=0 + // +k8s:monotonic + Uint64Field uint64 `json:"uint64Field"` + + // +k8s:optional + // +k8s:minimum=0 + // +k8s:update=NoUnset + // +k8s:monotonic + IntPtrField *int `json:"intPtrField"` + + MonotonicField MonotonicType `json:"monotonicField"` + + MonotonicPtrField *MonotonicType `json:"monotonicPtrField"` + + // +k8s:optional + // +k8s:minimum=0 + // +k8s:update=NoUnset + // +k8s:monotonic + OptionalInt int `json:"optionalInt,omitempty"` + + // +k8s:required + // +k8s:minimum=0 + // +k8s:monotonic + RequiredInt int `json:"requiredInt"` + + // +k8s:optional + // +k8s:minimum=0 + // +k8s:update=NoUnset + // +k8s:monotonic + OptionalIntPtr *int `json:"optionalIntPtr"` + + // +k8s:required + // +k8s:minimum=0 + // +k8s:monotonic + RequiredIntPtr *int `json:"requiredIntPtr"` + + // +k8s:minimum=-10 + // +k8s:monotonic + NegativeInt int `json:"negativeInt"` +} + +// +k8s:minimum=0 +// +k8s:monotonic +type MonotonicType int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/doc_test.go new file mode 100644 index 0000000000..bc24167466 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/doc_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2025 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 monotonic + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + structOrig := Struct{ + IntField: 10, + Int64Field: 20, + Uint64Field: 30, + IntPtrField: new(40), + MonotonicField: 50, + MonotonicPtrField: new(MonotonicType(60)), + OptionalInt: 10, + RequiredInt: 10, + OptionalIntPtr: new(10), + RequiredIntPtr: new(10), + NegativeInt: 0, + } + + structIncrease := Struct{ + IntField: 11, + Int64Field: 21, + Uint64Field: 31, + IntPtrField: new(41), + MonotonicField: 51, + MonotonicPtrField: new(MonotonicType(61)), + OptionalInt: 11, + RequiredInt: 11, + OptionalIntPtr: new(11), + RequiredIntPtr: new(11), + NegativeInt: 5, + } + + structDecrease := Struct{ + IntField: 9, + Int64Field: 19, + Uint64Field: 29, + IntPtrField: new(39), + MonotonicField: 49, + MonotonicPtrField: new(MonotonicType(59)), + OptionalInt: 9, + RequiredInt: 9, + OptionalIntPtr: new(9), + RequiredIntPtr: new(9), + NegativeInt: 0, + } + + // Valid updates + st.Value(&structOrig).OldValue(&structOrig).ExpectValid() + st.Value(&structIncrease).OldValue(&structOrig).ExpectValid() + + // Invalid updates (decreases) + st.Value(&structDecrease).OldValue(&structOrig).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("int64Field"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("uint64Field"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("intPtrField"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("monotonicField"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("monotonicPtrField"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("optionalInt"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("requiredInt"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("optionalIntPtr"), nil, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("requiredIntPtr"), nil, "").WithOrigin("monotonic"), + }) + + // Test special cases: zero and nil + structZero := structOrig + structZero.OptionalInt = 0 + structZero.RequiredInt = 0 + structZero.OptionalIntPtr = new(0) + structZero.RequiredIntPtr = new(0) + + // OptionalInt (non-pointer) -> 0 is INVALID (Fails because +k8s:update=NoUnset is present). + // RequiredInt -> 0 is INVALID (Fails because of +k8s:required). + // OptionalIntPtr -> 0 is INVALID (Monotonic check detects decreased value). + // RequiredIntPtr -> 0 is INVALID (Monotonic check detects decreased value). + st.Value(&structZero).OldValue(&structOrig).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("optionalInt"), 0, "").WithOrigin("update"), + field.Required(field.NewPath("requiredInt"), ""), + field.Invalid(field.NewPath("optionalIntPtr"), 0, "").WithOrigin("monotonic"), + field.Invalid(field.NewPath("requiredIntPtr"), 0, "").WithOrigin("monotonic"), + }) + + // OptionalIntPtr -> nil is INVALID because of +k8s:update=NoUnset + structNil := structOrig + structNil.OptionalIntPtr = nil + st.Value(&structNil).OldValue(&structOrig).ExpectMatches(field.ErrorMatcher{}.ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("optionalIntPtr"), nil, "").WithOrigin("update"), + }) + + // Create should be valid (monotonic validation is update-only) + st.Value(&structDecrease).ExpectValid() + + // OptionalIntPtr unset -> set should be valid (transition from nil to value) + structSet := structOrig + structSet.OptionalIntPtr = new(10) + oldObjectSet := structSet + oldObjectSet.OptionalIntPtr = nil + st.Value(&structSet).OldValue(&oldObjectSet).ExpectValid() + + // OptionalIntPtr set -> unset is now INVALID due to +k8s:update=NoUnset + structUnset := structOrig + structUnset.OptionalIntPtr = nil + st.Value(&structUnset).OldValue(&structOrig).ExpectMatches(field.ErrorMatcher{}.ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("optionalIntPtr"), nil, "").WithOrigin("update"), + }) + + // Invalid because it violates the minimum constraint (origin = "minimum") + structBad := Struct{RequiredInt: 1, RequiredIntPtr: new(int)} + structBad.NegativeInt = -11 // below the declared -10 + + st.Value(&structBad).ExpectMatches( + field.ErrorMatcher{}.ByOrigin(), + field.ErrorList{ + field.Invalid(field.NewPath("negativeInt"), -11, ""). + WithOrigin("minimum"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/zz_generated.validations.go new file mode 100644 index 0000000000..120b2c9072 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/monotonic/zz_generated.validations.go @@ -0,0 +1,420 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package monotonic + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_MonotonicType validates an instance of MonotonicType according +// to declarative validation rules in the API schema. +func Validate_MonotonicType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *MonotonicType) (errs field.ErrorList) { + + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.Int64Field + fn := func( + fldPath *field.Path, + obj, oldObj *int64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int64 { + return &oldObj.Int64Field + }) + errs = append(errs, fn(fldPath.Child("int64Field"), &obj.Int64Field, oldVal, oldObj != nil)...) + } + + { // field Struct.Uint64Field + fn := func( + fldPath *field.Path, + obj, oldObj *uint64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *uint64 { + return &oldObj.Uint64Field + }) + errs = append(errs, fn(fldPath.Child("uint64Field"), &obj.Uint64Field, oldVal, oldObj != nil)...) + } + + { // field Struct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoUnset).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.MonotonicField + fn := func( + fldPath *field.Path, + obj, oldObj *MonotonicType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_MonotonicType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *MonotonicType { + return &oldObj.MonotonicField + }) + errs = append(errs, fn(fldPath.Child("monotonicField"), &obj.MonotonicField, oldVal, oldObj != nil)...) + } + + { // field Struct.MonotonicPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *MonotonicType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_MonotonicType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *MonotonicType { + return oldObj.MonotonicPtrField + }) + errs = append(errs, fn(fldPath.Child("monotonicPtrField"), obj.MonotonicPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.OptionalInt + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a int, b int) bool { return a == b }, validate.NoUnset).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.OptionalInt + }) + errs = append(errs, fn(fldPath.Child("optionalInt"), &obj.OptionalInt, oldVal, oldObj != nil)...) + } + + { // field Struct.RequiredInt + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.RequiredInt + }) + errs = append(errs, fn(fldPath.Child("requiredInt"), &obj.RequiredInt, oldVal, oldObj != nil)...) + } + + { // field Struct.OptionalIntPtr + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoUnset).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.OptionalIntPtr + }) + errs = append(errs, fn(fldPath.Child("optionalIntPtr"), obj.OptionalIntPtr, oldVal, oldObj != nil)...) + } + + { // field Struct.RequiredIntPtr + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.RequiredIntPtr + }) + errs = append(errs, fn(fldPath.Child("requiredIntPtr"), obj.RequiredIntPtr, oldVal, oldObj != nil)...) + } + + { // field Struct.NegativeInt + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, -10); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Monotonic(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.NegativeInt + }) + errs = append(errs, fn(fldPath.Child("negativeInt"), &obj.NegativeInt, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc.go new file mode 100644 index 0000000000..9b2063ebd1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc.go @@ -0,0 +1,60 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package for complex neq compositions. +// +k8s:validation-gen-nolint +package neqchained + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:subfield(stringField)=+k8s:neq="disallowed-subfield" + StructField InnerStruct `json:"structField"` + + // +k8s:optional + // +k8s:subfield(stringField)=+k8s:neq="disallowed-subfield-ptr" + StructPtrField *InnerStruct `json:"structPtrField"` + + // +k8s:eachVal=+k8s:neq="disallowed-slice" + StringSliceField []string `json:"stringSliceField"` + + // +k8s:eachVal=+k8s:neq="disallowed-map-val" + StringMapField map[string]string `json:"stringMapField"` + + // +k8s:eachKey=+k8s:neq="disallowed-key" + StringMapKeyField map[string]string `json:"stringMapKeyField"` + + ValidatedSliceField ValidatedStringSlice `json:"validatedSliceField"` + + ValidatedStructField ValidatedInnerStruct `json:"validatedStructField"` +} + +type InnerStruct struct { + StringField string `json:"stringField"` +} + +// +k8s:eachVal=+k8s:neq="disallowed-typedef" +type ValidatedStringSlice []string + +// +k8s:subfield(stringField)=+k8s:neq="disallowed-typedef-struct" +type ValidatedInnerStruct InnerStruct diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc_test.go new file mode 100644 index 0000000000..5b7454b93f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/doc_test.go @@ -0,0 +1,75 @@ +/* +Copyright 2025 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 neqchained + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + StructField: InnerStruct{StringField: "allowed"}, + StructPtrField: &InnerStruct{StringField: "valid"}, + StringSliceField: []string{"allowed", "valid"}, + StringMapField: map[string]string{"k1": "allowed", "k2": "valid"}, + StringMapKeyField: map[string]string{"allowed-key": "v1", "valid-key": "v2"}, + ValidatedSliceField: []string{"allowed", "valid"}, + ValidatedStructField: ValidatedInnerStruct{StringField: "allowed"}, + }).ExpectValid() + + // Test empty collections and unset. + st.Value(&Struct{ + StructField: InnerStruct{}, + StructPtrField: &InnerStruct{StringField: "valid"}, + StringSliceField: []string{}, + StringMapField: map[string]string{}, + StringMapKeyField: map[string]string{}, + ValidatedSliceField: []string{}, + ValidatedStructField: ValidatedInnerStruct{StringField: "allowed"}, + }).ExpectValid() + + // Test invalid values trigger all expected validation errors + invalidStruct := &Struct{ + StructField: InnerStruct{StringField: "disallowed-subfield"}, + StructPtrField: &InnerStruct{StringField: "disallowed-subfield-ptr"}, + StringSliceField: []string{"valid", "disallowed-slice", "disallowed-slice"}, + StringMapField: map[string]string{"a": "disallowed-map-val", "b": "valid", "c": "disallowed-map-val"}, + StringMapKeyField: map[string]string{"disallowed-key": "value", "allowed": "ok"}, + ValidatedSliceField: []string{"valid", "disallowed-typedef", "disallowed-typedef"}, + ValidatedStructField: ValidatedInnerStruct{StringField: "disallowed-typedef-struct"}, + } + + st.Value(invalidStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("structField", "stringField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("structPtrField", "stringField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("stringSliceField").Index(1), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("stringSliceField").Index(2), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("stringMapField").Key("a"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("stringMapField").Key("c"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("stringMapKeyField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("validatedSliceField").Index(1), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("validatedSliceField").Index(2), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("validatedStructField", "stringField"), nil, "").WithOrigin("neq"), + }) + + // Test validation ratcheting allows existing invalid values + st.Value(invalidStruct).OldValue(invalidStruct).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/zz_generated.validations.go new file mode 100644 index 0000000000..10f2ef008b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neq/neqchained/zz_generated.validations.go @@ -0,0 +1,296 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package neqchained + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StructField + fn := func( + fldPath *field.Path, + obj, oldObj *InnerStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "stringField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *InnerStruct) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-subfield") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *InnerStruct { + return &oldObj.StructField + }) + errs = append(errs, fn(fldPath.Child("structField"), &obj.StructField, oldVal, oldObj != nil)...) + } + + { // field Struct.StructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *InnerStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + func() { // cohort = "stringField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *InnerStruct) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-subfield-ptr") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *InnerStruct { + return oldObj.StructPtrField + }) + errs = append(errs, fn(fldPath.Child("structPtrField"), obj.StructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringSliceField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-slice") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.StringSliceField + }) + errs = append(errs, fn(fldPath.Child("stringSliceField"), obj.StringSliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringMapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-map-val") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.StringMapField + }) + errs = append(errs, fn(fldPath.Child("stringMapField"), obj.StringMapField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringMapKeyField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-key") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.StringMapKeyField + }) + errs = append(errs, fn(fldPath.Child("stringMapKeyField"), obj.StringMapKeyField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedSliceField + fn := func( + fldPath *field.Path, + obj, oldObj ValidatedStringSlice, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ValidatedStringSlice(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) ValidatedStringSlice { + return oldObj.ValidatedSliceField + }) + errs = append(errs, fn(fldPath.Child("validatedSliceField"), obj.ValidatedSliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedStructField + fn := func( + fldPath *field.Path, + obj, oldObj *ValidatedInnerStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ValidatedInnerStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ValidatedInnerStruct { + return &oldObj.ValidatedStructField + }) + errs = append(errs, fn(fldPath.Child("validatedStructField"), &obj.ValidatedStructField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ValidatedInnerStruct validates an instance of ValidatedInnerStruct according +// to declarative validation rules in the API schema. +func Validate_ValidatedInnerStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ValidatedInnerStruct) (errs field.ErrorList) { + + func() { // cohort = "stringField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *ValidatedInnerStruct) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-typedef-struct") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + + // field ValidatedInnerStruct.StringField has no validation + return errs +} + +// Validate_ValidatedStringSlice validates an instance of ValidatedStringSlice according +// to declarative validation rules in the API schema. +func Validate_ValidatedStringSlice( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj ValidatedStringSlice) (errs field.ErrorList) { + + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-typedef") + }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc.go new file mode 100644 index 0000000000..88bd3d1637 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc.go @@ -0,0 +1,41 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package neqbool + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:neq=true + NeqTrueField bool `json:"neqTrueField"` + + // +k8s:neq=false + NeqFalsePtrField *bool `json:"neqFalsePtrField"` + + ValidatedTypedefField ValidatedBoolType `json:"validatedTypedefField"` +} + +// +k8s:neq=true +type ValidatedBoolType bool diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc_test.go new file mode 100644 index 0000000000..3cf62820bd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/doc_test.go @@ -0,0 +1,55 @@ +/* +Copyright 2025 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 neqbool + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + NeqTrueField: false, + NeqFalsePtrField: ptr.To(true), + ValidatedTypedefField: false, + }).ExpectValid() + + st.Value(&Struct{ + NeqTrueField: false, + NeqFalsePtrField: nil, + ValidatedTypedefField: false, + }).ExpectValid() + + invalid := &Struct{ + NeqTrueField: true, + NeqFalsePtrField: ptr.To(false), + ValidatedTypedefField: true, + } + + st.Value(invalid).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("neqTrueField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("neqFalsePtrField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("validatedTypedefField"), nil, "").WithOrigin("neq"), + }) + + // Test validation ratcheting. + st.Value(invalid).OldValue(invalid).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/zz_generated.validations.go new file mode 100644 index 0000000000..2d1994054e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqbool/zz_generated.validations.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package neqbool + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.NeqTrueField + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, true); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return &oldObj.NeqTrueField + }) + errs = append(errs, fn(fldPath.Child("neqTrueField"), &obj.NeqTrueField, oldVal, oldObj != nil)...) + } + + { // field Struct.NeqFalsePtrField + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, false); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return oldObj.NeqFalsePtrField + }) + errs = append(errs, fn(fldPath.Child("neqFalsePtrField"), obj.NeqFalsePtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *ValidatedBoolType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ValidatedBoolType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ValidatedBoolType { + return &oldObj.ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("validatedTypedefField"), &obj.ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ValidatedBoolType validates an instance of ValidatedBoolType according +// to declarative validation rules in the API schema. +func Validate_ValidatedBoolType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ValidatedBoolType) (errs field.ErrorList) { + + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, true); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc.go new file mode 100644 index 0000000000..019e5f9f8e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc.go @@ -0,0 +1,46 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package neqint + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:neq=0 + IntField int `json:"intField"` + + // +k8s:neq=-1 + IntPtrField *int `json:"intPtrField"` + + // +k8s:neq=42 + IntTypedefField IntType `json:"intTypedefField"` + + ValidatedTypedefField ValidatedIntType `json:"validatedTypedefField"` +} + +type IntType int + +// +k8s:neq=100 +type ValidatedIntType int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc_test.go new file mode 100644 index 0000000000..ab0ee8696d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/doc_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2025 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 neqint + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + IntField: 1, + IntPtrField: ptr.To(0), + IntTypedefField: 41, + ValidatedTypedefField: 99, + }).ExpectValid() + + st.Value(&Struct{ + IntField: 1, + IntPtrField: nil, + IntTypedefField: 41, + ValidatedTypedefField: 99, + }).ExpectValid() + + invalid := &Struct{ + IntField: 0, + IntPtrField: ptr.To(-1), + IntTypedefField: 42, + ValidatedTypedefField: 100, + } + + st.Value(invalid).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("intField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("intPtrField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("intTypedefField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("validatedTypedefField"), nil, "").WithOrigin("neq"), + }) + + // Test validation ratcheting. + st.Value(invalid).OldValue(invalid).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/zz_generated.validations.go new file mode 100644 index 0000000000..acc9bf0039 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqint/zz_generated.validations.go @@ -0,0 +1,174 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package neqint + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, 0); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, -1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, 42); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return &oldObj.IntTypedefField + }) + errs = append(errs, fn(fldPath.Child("intTypedefField"), &obj.IntTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *ValidatedIntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ValidatedIntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ValidatedIntType { + return &oldObj.ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("validatedTypedefField"), &obj.ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ValidatedIntType validates an instance of ValidatedIntType according +// to declarative validation rules in the API schema. +func Validate_ValidatedIntType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ValidatedIntType) (errs field.ErrorList) { + + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, 100); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc.go new file mode 100644 index 0000000000..e31d05a9e1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc.go @@ -0,0 +1,50 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package neqstring + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:neq="disallowed-string" + StringField string `json:"stringField"` + + // +k8s:neq="disallowed-pointer" + StringPtrField *string `json:"stringPtrField"` + + // +k8s:neq="disallowed-typedef" + StringTypedefField StringType `json:"stringTypedefField"` + + // +k8s:neq="disallowed-typedef-pointer" + StringTypedefPtrField *StringType `json:"stringTypedefPtrField"` + + ValidatedTypedefField ValidatedStringType `json:"validatedTypedefField"` + ValidatedTypedefPtrField *ValidatedStringType `json:"validatedTypedefPtrField"` +} + +type StringType string + +// +k8s:neq="disallowed-on-type" +type ValidatedStringType string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc_test.go new file mode 100644 index 0000000000..5c2578384d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/doc_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2025 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 neqstring + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + StringField: "allowed-string", + StringPtrField: ptr.To("allowed-pointer"), + StringTypedefField: "allowed-typedef", + StringTypedefPtrField: ptr.To(StringType("allowed-typedef-pointer")), + ValidatedTypedefField: "allowed-on-type", + ValidatedTypedefPtrField: ptr.To(ValidatedStringType("allowed-on-type-ptr")), + }).ExpectValid() + + st.Value(&Struct{ + StringField: "allowed-string", + StringPtrField: nil, + StringTypedefField: "allowed-typedef", + StringTypedefPtrField: nil, + ValidatedTypedefField: "allowed-on-type", + ValidatedTypedefPtrField: nil, + }).ExpectValid() + + invalid := &Struct{ + StringField: "disallowed-string", + StringPtrField: ptr.To("disallowed-pointer"), + StringTypedefField: "disallowed-typedef", + StringTypedefPtrField: ptr.To(StringType("disallowed-typedef-pointer")), + ValidatedTypedefField: "disallowed-on-type", + ValidatedTypedefPtrField: ptr.To(ValidatedStringType("disallowed-on-type")), + } + + st.Value(invalid).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("stringPtrField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("stringTypedefField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("stringTypedefPtrField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("validatedTypedefField"), nil, "").WithOrigin("neq"), + field.Invalid(field.NewPath("validatedTypedefPtrField"), nil, "").WithOrigin("neq"), + }) + + // Test validation ratcheting. + st.Value(invalid).OldValue(invalid).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/zz_generated.validations.go new file mode 100644 index 0000000000..01f451a9c3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/neq/neqstring/zz_generated.validations.go @@ -0,0 +1,220 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package neqstring + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-string"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-pointer"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.StringPtrField + }) + errs = append(errs, fn(fldPath.Child("stringPtrField"), obj.StringPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-typedef"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return &oldObj.StringTypedefField + }) + errs = append(errs, fn(fldPath.Child("stringTypedefField"), &obj.StringTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-typedef-pointer"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return oldObj.StringTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("stringTypedefPtrField"), obj.StringTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *ValidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ValidatedStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ValidatedStringType { + return &oldObj.ValidatedTypedefField + }) + errs = append(errs, fn(fldPath.Child("validatedTypedefField"), &obj.ValidatedTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.ValidatedTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *ValidatedStringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ValidatedStringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ValidatedStringType { + return oldObj.ValidatedTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("validatedTypedefPtrField"), obj.ValidatedTypedefPtrField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ValidatedStringType validates an instance of ValidatedStringType according +// to declarative validation rules in the API schema. +func Validate_ValidatedStringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ValidatedStringType) (errs field.ErrorList) { + + if e := validate.NEQ(ctx, op, fldPath, obj, oldObj, "disallowed-on-type"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc.go new file mode 100644 index 0000000000..33985040b5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc.go @@ -0,0 +1,188 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-targets + +// This is a test package. +// +k8s:validation-gen-nolint +package opaque + +import ( + "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.StructField" + StructField OtherStruct `json:"structField"` + + // +k8s:validateFalse="field Struct.StructPtrField" + // +k8s:required + StructPtrField *OtherStruct `json:"structPtrField"` + + // +k8s:validateFalse="field Struct.OpaqueStructField" + // +k8s:opaqueType + OpaqueStructField OtherStruct `json:"opaqueStructField"` + + // +k8s:validateFalse="field Struct.OpaqueStructPtrField" + // +k8s:required + // +k8s:opaqueType + OpaqueStructPtrField *OtherStruct `json:"opaqueStructPtrField"` + + // +k8s:validateFalse="field Struct.SliceOfStructField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.SliceOfStructField vals" + SliceOfStructField []OtherStruct `json:"sliceOfStructField"` + + // +k8s:validateFalse="field Struct.SliceOfOpaqueStructField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.SliceOfOpaqueStructField vals" + // +k8s:eachVal=+k8s:opaqueType + SliceOfOpaqueStructField []OtherStruct `json:"sliceOfOpaqueStructField"` + + // +k8s:validateFalse="field Struct.ListMapOfStructField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListMapOfStructField vals" + // +k8s:listType=map + // +k8s:listMapKey=stringField + ListMapOfStructField []OtherStruct `json:"listMapOfStructField"` + + // +k8s:validateFalse="field Struct.ListMapOfOpaqueStructField" + // +k8s:eachVal=+k8s:validateFalse="field Struct.ListMapOfOpaqueStructField vals" + // +k8s:listType=map + // +k8s:listMapKey=stringField + // +k8s:eachVal=+k8s:opaqueType + ListMapOfOpaqueStructField []OtherStruct `json:"listMapOfOpaqueStructField"` + + // +k8s:validateFalse="field Struct.MapOfStringToStructField" + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapOfStringToStructField keys" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapOfStringToStructField vals" + MapOfStringToStructField map[OtherString]OtherStruct `json:"mapOfStringToStructField"` + + // +k8s:validateFalse="field Struct.MapOfStringToOpaqueStructField" + // +k8s:eachKey=+k8s:validateFalse="field Struct.MapOfStringToOpaqueStructField keys" + // +k8s:eachVal=+k8s:validateFalse="field Struct.MapOfStringToOpaqueStructField vals" + // +k8s:eachKey=+k8s:opaqueType + // +k8s:eachVal=+k8s:opaqueType + MapOfStringToOpaqueStructField map[OtherString]OtherStruct `json:"mapOfStringToOpaqueStructField"` +} + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct { + // +k8s:validateFalse="field OtherStruct.StringField" + StringField string `json:"stringField"` +} + +// OpaqueFieldsStruct contains fields with the opaque markers, contains no field validations. +// Validations should not be generated for these fields. +// +k8s:validateTrue="type OpaqueFieldsStruct" +type OpaqueFieldsStruct struct { + // +k8s:opaqueType + OtherStruct + + // +k8s:eachVal=+k8s:opaqueType + OpaqueSliceField []OtherStruct `json:"opaqueSliceField"` + + // +k8s:eachKey=+k8s:opaqueType + // +k8s:eachVal=+k8s:opaqueType + OpaqueMapField map[OtherString]OtherStruct `json:"opaqueMapField"` + + TypedefOpaqueStructField TypedefOpaqueStruct `json:"typedefOpaqueStructField"` + + TypedefOpaqueSliceField TypedefOpaqueSlice `json:"typedefOpaqueSliceField"` + + TypedefOpaqueMapField TypedefOpaqueMap `json:"typedefOpaqueMapField"` + + // +k8s:opaqueType + IsolatedOpaqueStructField OtherStruct `json:"isolatedOpaqueStructField"` +} + +// +k8s:validateFalse="type OtherString" +type OtherString string + +// TODO: the validateFalse test fixture doesn't handle map and slice types, and +// fixing it requires fixing randfill. That is a tomorrow problem. For now, the +// following types have been tested to generate correct code with +// +k8s:opaqueType. + +// +k8s:opaqueType +type TypedefOpaqueStruct struct { + // +k8s:required + StringField string `json:"stringField"` +} + +// +k8s:eachVal=+k8s:opaqueType +type TypedefOpaqueSlice []OtherStruct + +// +k8s:eachKey=+k8s:opaqueType +// +k8s:eachVal=+k8s:opaqueType +type TypedefOpaqueMap map[OtherString]OtherStruct + +// +k8s:validateTrue="type TypedefSliceOther" +// +k8s:eachVal=+k8s:opaqueType +type TypedefSliceOther []OtherStruct + +// +k8s:validateTrue="type TypedefMapOther" +// +k8s:eachKey=+k8s:opaqueType +// +k8s:eachVal=+k8s:opaqueType +type TypedefMapOther map[OtherString]OtherStruct + +type NoValidationStruct struct { + StringField string `json:"stringField"` +} + +type NoValidationString string + +// OpaqueNoValidationFieldsStruct tests that when a type/key/val is opaque but the opaque type +// does not have any validation, we correctly do not emit any validation calls. +// +k8s:validateTrue="type OpaqueNoValidationFieldsStruct" +type OpaqueNoValidationFieldsStruct struct { + // +k8s:opaqueType + NoValidationStruct + + // +k8s:eachVal=+k8s:opaqueType + OpaqueSliceField []NoValidationStruct `json:"opaqueSliceField"` + + // +k8s:eachKey=+k8s:opaqueType + // +k8s:eachVal=+k8s:opaqueType + OpaqueMapField map[NoValidationString]NoValidationStruct `json:"opaqueMapField"` + + // +k8s:opaqueType + IsolatedOpaqueStructField NoValidationStruct `json:"isolatedOpaqueStructField"` +} + +// +k8s:opaqueType +// +k8s:validateFalse="type OpaqueStructWithValidation" +type OpaqueStructWithValidation struct { + // +k8s:validateFalse="field OpaqueStructWithValidation.StringField" + StringField string `json:"stringField"` +} + +type BaseStruct struct { + // +k8s:validateFalse="field BaseStruct.Field" + Field string `json:"field"` +} + +// +k8s:opaqueType +// +k8s:validateFalse="type OpaqueAliasWithValidation" +type OpaqueAliasWithValidation BaseStruct + +type ParentWithOpaqueAliasWithValidation struct { + TypeMeta int `json:"typeMeta"` + Field OpaqueAliasWithValidation `json:"field"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc_test.go new file mode 100644 index 0000000000..d41b5c926e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/doc_test.go @@ -0,0 +1,153 @@ +/* +Copyright 2025 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 opaque + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + StructField: OtherStruct{}, + StructPtrField: &OtherStruct{}, + OpaqueStructField: OtherStruct{}, + OpaqueStructPtrField: &OtherStruct{}, + SliceOfStructField: []OtherStruct{{}, {}}, + SliceOfOpaqueStructField: []OtherStruct{{}, {}}, + ListMapOfStructField: []OtherStruct{{"foo"}, {"bar"}}, + ListMapOfOpaqueStructField: []OtherStruct{{"foo"}, {"bar"}}, + MapOfStringToStructField: map[OtherString]OtherStruct{"a": {"foo"}, "b": {"bar"}}, + MapOfStringToOpaqueStructField: map[OtherString]OtherStruct{"a": {"foo"}, "b": {"bar"}}, + }).ExpectValidateFalseByPath(map[string][]string{ + "structField": {"field Struct.StructField", "type OtherStruct"}, + "structPtrField": {"field Struct.StructPtrField", "type OtherStruct"}, + "structField.stringField": {"field OtherStruct.StringField"}, + "structPtrField.stringField": {"field OtherStruct.StringField"}, + "opaqueStructField": {"field Struct.OpaqueStructField"}, + "opaqueStructPtrField": {"field Struct.OpaqueStructPtrField"}, + "sliceOfStructField": {"field Struct.SliceOfStructField"}, + "sliceOfStructField[0]": {"field Struct.SliceOfStructField vals", "type OtherStruct"}, + "sliceOfStructField[0].stringField": {"field OtherStruct.StringField"}, + "sliceOfStructField[1]": {"field Struct.SliceOfStructField vals", "type OtherStruct"}, + "sliceOfStructField[1].stringField": {"field OtherStruct.StringField"}, + "sliceOfOpaqueStructField": {"field Struct.SliceOfOpaqueStructField"}, + "sliceOfOpaqueStructField[0]": {"field Struct.SliceOfOpaqueStructField vals"}, + "sliceOfOpaqueStructField[1]": {"field Struct.SliceOfOpaqueStructField vals"}, + "listMapOfStructField": {"field Struct.ListMapOfStructField"}, + "listMapOfStructField[0]": {"field Struct.ListMapOfStructField vals", "type OtherStruct"}, + "listMapOfStructField[0].stringField": {"field OtherStruct.StringField"}, + "listMapOfStructField[1]": {"field Struct.ListMapOfStructField vals", "type OtherStruct"}, + "listMapOfStructField[1].stringField": {"field OtherStruct.StringField"}, + "listMapOfOpaqueStructField": {"field Struct.ListMapOfOpaqueStructField"}, + "listMapOfOpaqueStructField[0]": {"field Struct.ListMapOfOpaqueStructField vals"}, + "listMapOfOpaqueStructField[1]": {"field Struct.ListMapOfOpaqueStructField vals"}, + "mapOfStringToStructField": { + "field Struct.MapOfStringToStructField", + "field Struct.MapOfStringToStructField keys", + "field Struct.MapOfStringToStructField keys", + "type OtherString", + "type OtherString", + }, + "mapOfStringToStructField[a]": {"field Struct.MapOfStringToStructField vals", "type OtherStruct"}, + "mapOfStringToStructField[a].stringField": {"field OtherStruct.StringField"}, + "mapOfStringToStructField[b]": {"field Struct.MapOfStringToStructField vals", "type OtherStruct"}, + "mapOfStringToStructField[b].stringField": {"field OtherStruct.StringField"}, + "mapOfStringToOpaqueStructField": { + "field Struct.MapOfStringToOpaqueStructField", + "field Struct.MapOfStringToOpaqueStructField keys", + "field Struct.MapOfStringToOpaqueStructField keys", + }, + "mapOfStringToOpaqueStructField[a]": {"field Struct.MapOfStringToOpaqueStructField vals"}, + "mapOfStringToOpaqueStructField[b]": {"field Struct.MapOfStringToOpaqueStructField vals"}, + }) + + st.Value(&Struct{ + ListMapOfStructField: []OtherStruct{{"foo"}, {"foo"}}, + ListMapOfOpaqueStructField: []OtherStruct{{"foo"}, {"foo"}}, + }).ExpectValidateFalseByPath(map[string][]string{ + "structField": {"field Struct.StructField", "type OtherStruct"}, + "structField.stringField": {"field OtherStruct.StringField"}, + "opaqueStructField": {"field Struct.OpaqueStructField"}, + "listMapOfStructField": {"field Struct.ListMapOfStructField"}, + "listMapOfStructField[0]": {"field Struct.ListMapOfStructField vals", "type OtherStruct"}, + "listMapOfStructField[0].stringField": {"field OtherStruct.StringField"}, + "listMapOfStructField[1]": {"field Struct.ListMapOfStructField vals", "type OtherStruct"}, + "listMapOfStructField[1].stringField": {"field OtherStruct.StringField"}, + "listMapOfOpaqueStructField": {"field Struct.ListMapOfOpaqueStructField"}, + "listMapOfOpaqueStructField[0]": {"field Struct.ListMapOfOpaqueStructField vals"}, + "listMapOfOpaqueStructField[1]": {"field Struct.ListMapOfOpaqueStructField vals"}, + "mapOfStringToOpaqueStructField": {"field Struct.MapOfStringToOpaqueStructField"}, + "mapOfStringToStructField": {"field Struct.MapOfStringToStructField"}, + "sliceOfOpaqueStructField": {"field Struct.SliceOfOpaqueStructField"}, + "sliceOfStructField": {"field Struct.SliceOfStructField"}, + }) + + str := OtherString("foo") + st.Value(&str).ExpectValidateFalseByPath(map[string][]string{ + "": {"type OtherString"}, + }) + + st.Value(&OtherStruct{}).ExpectValidateFalseByPath(map[string][]string{ + "": {"type OtherStruct"}, + "stringField": {"field OtherStruct.StringField"}, + }) + + st.Value(&OpaqueFieldsStruct{ + OtherStruct: OtherStruct{"foo"}, + OpaqueSliceField: []OtherStruct{{"foo"}}, + OpaqueMapField: map[OtherString]OtherStruct{"a": {"foo"}}, + TypedefOpaqueStructField: TypedefOpaqueStruct{"foo"}, + TypedefOpaqueSliceField: []OtherStruct{{"foo"}}, + TypedefOpaqueMapField: map[OtherString]OtherStruct{"a": {"foo"}}, + IsolatedOpaqueStructField: OtherStruct{"foo"}, + }).ExpectValid() + + st.Value(&OpaqueNoValidationFieldsStruct{ + NoValidationStruct: NoValidationStruct{"foo"}, + OpaqueSliceField: []NoValidationStruct{{"foo"}}, + OpaqueMapField: map[NoValidationString]NoValidationStruct{"a": {"foo"}}, + IsolatedOpaqueStructField: NoValidationStruct{"foo"}, + }).ExpectValid() + + st.Value(&OpaqueStructWithValidation{ + StringField: "foo", + }).ExpectValidateFalseByPath(map[string][]string{ + "": {"type OpaqueStructWithValidation"}, + }) + + st.Value(&ParentWithOpaqueAliasWithValidation{ + Field: OpaqueAliasWithValidation{ + Field: "foo", + }, + }).ExpectValidateFalseByPath(map[string][]string{ + "field": {"type OpaqueAliasWithValidation"}, + }) + + st.Value(&OpaqueAliasWithValidation{ + Field: "foo", + }).ExpectValidateFalseByPath(map[string][]string{ + "": {"type OpaqueAliasWithValidation"}, + }) + + st.Value(&BaseStruct{ + Field: "foo", + }).ExpectValidateFalseByPath(map[string][]string{ + "field": {"field BaseStruct.Field"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/zz_generated.validations.go new file mode 100644 index 0000000000..162e3adeb8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/zz_generated.validations.go @@ -0,0 +1,760 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package opaque + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type BaseStruct + scheme.AddValidationFunc( + (*BaseStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_BaseStruct( + ctx, op, nil, /* fldPath */ + obj.(*BaseStruct), + safe.Cast[*BaseStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type OpaqueAliasWithValidation + scheme.AddValidationFunc( + (*OpaqueAliasWithValidation)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_OpaqueAliasWithValidation( + ctx, op, nil, /* fldPath */ + obj.(*OpaqueAliasWithValidation), + safe.Cast[*OpaqueAliasWithValidation](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type OpaqueFieldsStruct + scheme.AddValidationFunc( + (*OpaqueFieldsStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_OpaqueFieldsStruct( + ctx, op, nil, /* fldPath */ + obj.(*OpaqueFieldsStruct), + safe.Cast[*OpaqueFieldsStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type OpaqueNoValidationFieldsStruct + scheme.AddValidationFunc( + (*OpaqueNoValidationFieldsStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_OpaqueNoValidationFieldsStruct( + ctx, op, nil, /* fldPath */ + obj.(*OpaqueNoValidationFieldsStruct), + safe.Cast[*OpaqueNoValidationFieldsStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type OpaqueStructWithValidation + scheme.AddValidationFunc( + (*OpaqueStructWithValidation)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_OpaqueStructWithValidation( + ctx, op, nil, /* fldPath */ + obj.(*OpaqueStructWithValidation), + safe.Cast[*OpaqueStructWithValidation](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type OtherString + scheme.AddValidationFunc( + (*OtherString)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_OtherString( + ctx, op, nil, /* fldPath */ + obj.(*OtherString), + safe.Cast[*OtherString](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type OtherStruct + scheme.AddValidationFunc( + (*OtherStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_OtherStruct( + ctx, op, nil, /* fldPath */ + obj.(*OtherStruct), + safe.Cast[*OtherStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithOpaqueAliasWithValidation + scheme.AddValidationFunc( + (*ParentWithOpaqueAliasWithValidation)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithOpaqueAliasWithValidation( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithOpaqueAliasWithValidation), + safe.Cast[*ParentWithOpaqueAliasWithValidation](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type TypedefMapOther + scheme.AddValidationFunc( + (TypedefMapOther)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_TypedefMapOther( + ctx, op, nil, /* fldPath */ + obj.(TypedefMapOther), + safe.Cast[TypedefMapOther](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type TypedefSliceOther + scheme.AddValidationFunc( + (TypedefSliceOther)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_TypedefSliceOther( + ctx, op, nil, /* fldPath */ + obj.(TypedefSliceOther), + safe.Cast[TypedefSliceOther](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_BaseStruct validates an instance of BaseStruct according +// to declarative validation rules in the API schema. +func Validate_BaseStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *BaseStruct) (errs field.ErrorList) { + + { // field BaseStruct.Field + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field BaseStruct.Field"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *BaseStruct) *string { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_OpaqueAliasWithValidation validates an instance of OpaqueAliasWithValidation according +// to declarative validation rules in the API schema. +func Validate_OpaqueAliasWithValidation( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OpaqueAliasWithValidation) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OpaqueAliasWithValidation"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_OpaqueFieldsStruct validates an instance of OpaqueFieldsStruct according +// to declarative validation rules in the API schema. +func Validate_OpaqueFieldsStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OpaqueFieldsStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "type OpaqueFieldsStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field OpaqueFieldsStruct.OtherStruct has no validation + // field OpaqueFieldsStruct.OpaqueSliceField has no validation + // field OpaqueFieldsStruct.OpaqueMapField has no validation + // field OpaqueFieldsStruct.TypedefOpaqueStructField has no validation + // field OpaqueFieldsStruct.TypedefOpaqueSliceField has no validation + // field OpaqueFieldsStruct.TypedefOpaqueMapField has no validation + // field OpaqueFieldsStruct.IsolatedOpaqueStructField has no validation + return errs +} + +// Validate_OpaqueNoValidationFieldsStruct validates an instance of OpaqueNoValidationFieldsStruct according +// to declarative validation rules in the API schema. +func Validate_OpaqueNoValidationFieldsStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OpaqueNoValidationFieldsStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "type OpaqueNoValidationFieldsStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + // field OpaqueNoValidationFieldsStruct.NoValidationStruct has no validation + // field OpaqueNoValidationFieldsStruct.OpaqueSliceField has no validation + // field OpaqueNoValidationFieldsStruct.OpaqueMapField has no validation + // field OpaqueNoValidationFieldsStruct.IsolatedOpaqueStructField has no validation + return errs +} + +// Validate_OpaqueStructWithValidation validates an instance of OpaqueStructWithValidation according +// to declarative validation rules in the API schema. +func Validate_OpaqueStructWithValidation( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OpaqueStructWithValidation) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OpaqueStructWithValidation"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_OtherString validates an instance of OtherString according +// to declarative validation rules in the API schema. +func Validate_OtherString( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherString) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherString"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field OtherStruct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field OtherStruct.StringField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *OtherStruct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithOpaqueAliasWithValidation validates an instance of ParentWithOpaqueAliasWithValidation according +// to declarative validation rules in the API schema. +func Validate_ParentWithOpaqueAliasWithValidation( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithOpaqueAliasWithValidation) (errs field.ErrorList) { + + // field ParentWithOpaqueAliasWithValidation.TypeMeta has no validation + + { // field ParentWithOpaqueAliasWithValidation.Field + fn := func( + fldPath *field.Path, + obj, oldObj *OpaqueAliasWithValidation, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_OpaqueAliasWithValidation(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithOpaqueAliasWithValidation) *OpaqueAliasWithValidation { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StructField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StructField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_OtherStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return &oldObj.StructField + }) + errs = append(errs, fn(fldPath.Child("structField"), &obj.StructField, oldVal, oldObj != nil)...) + } + + { // field Struct.StructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StructPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_OtherStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return oldObj.StructPtrField + }) + errs = append(errs, fn(fldPath.Child("structPtrField"), obj.StructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.OpaqueStructField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.OpaqueStructField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return &oldObj.OpaqueStructField + }) + errs = append(errs, fn(fldPath.Child("opaqueStructField"), &obj.OpaqueStructField, oldVal, oldObj != nil)...) + } + + { // field Struct.OpaqueStructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.OpaqueStructPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return oldObj.OpaqueStructPtrField + }) + errs = append(errs, fn(fldPath.Child("opaqueStructPtrField"), obj.OpaqueStructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceOfStructField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceOfStructField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceOfStructField vals") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.SliceOfStructField + }) + errs = append(errs, fn(fldPath.Child("sliceOfStructField"), obj.SliceOfStructField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceOfOpaqueStructField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceOfOpaqueStructField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceOfOpaqueStructField vals") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.SliceOfOpaqueStructField + }) + errs = append(errs, fn(fldPath.Child("sliceOfOpaqueStructField"), obj.SliceOfOpaqueStructField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListMapOfStructField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListMapOfStructField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.StringField == b.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListMapOfStructField vals") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.StringField == b.StringField }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.StringField == b.StringField }, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.ListMapOfStructField + }) + errs = append(errs, fn(fldPath.Child("listMapOfStructField"), obj.ListMapOfStructField, oldVal, oldObj != nil)...) + } + + { // field Struct.ListMapOfOpaqueStructField + fn := func( + fldPath *field.Path, + obj, oldObj []OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListMapOfOpaqueStructField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.StringField == b.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListMapOfOpaqueStructField vals") + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *OtherStruct, b *OtherStruct) bool { return a.StringField == b.StringField }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []OtherStruct { + return oldObj.ListMapOfOpaqueStructField + }) + errs = append(errs, fn(fldPath.Child("listMapOfOpaqueStructField"), obj.ListMapOfOpaqueStructField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapOfStringToStructField + fn := func( + fldPath *field.Path, + obj, oldObj map[OtherString]OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherString) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapOfStringToStructField keys") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapOfStringToStructField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapOfStringToStructField vals") + }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the key type's validation function + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, Validate_OtherString); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_OtherStruct); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[OtherString]OtherStruct { + return oldObj.MapOfStringToStructField + }) + errs = append(errs, fn(fldPath.Child("mapOfStringToStructField"), obj.MapOfStringToStructField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapOfStringToOpaqueStructField + fn := func( + fldPath *field.Path, + obj, oldObj map[OtherString]OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherString) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapOfStringToOpaqueStructField keys") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapOfStringToOpaqueStructField"); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *OtherStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapOfStringToOpaqueStructField vals") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[OtherString]OtherStruct { + return oldObj.MapOfStringToOpaqueStructField + }) + errs = append(errs, fn(fldPath.Child("mapOfStringToOpaqueStructField"), obj.MapOfStringToOpaqueStructField, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TypedefMapOther validates an instance of TypedefMapOther according +// to declarative validation rules in the API schema. +func Validate_TypedefMapOther( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj TypedefMapOther) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "type TypedefMapOther"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_TypedefSliceOther validates an instance of TypedefSliceOther according +// to declarative validation rules in the API schema. +func Validate_TypedefSliceOther( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj TypedefSliceOther) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "type TypedefSliceOther"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/zz_generated.validations_coverage_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/zz_generated.validations_coverage_test.go new file mode 100644 index 0000000000..a4ec923029 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque/zz_generated.validations_coverage_test.go @@ -0,0 +1,194 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package opaque + +import ( + fmt "fmt" + os "os" + testing "testing" + + schema "k8s.io/apimachinery/pkg/runtime/schema" + coverage "k8s.io/apimachinery/pkg/test/coverage" +) + +func init() { + coverage.RegisterDeclaredRules( + schema.GroupVersionKind{Group: "k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque", Version: "opaque", Kind: "BaseStruct"}, + coverage.FieldRules{ + "field": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + }, + ) +} + +func init() { + coverage.RegisterDeclaredRules( + schema.GroupVersionKind{Group: "k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque", Version: "opaque", Kind: "OpaqueAliasWithValidation"}, + coverage.FieldRules{ + "": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + }, + ) +} + +func init() { + coverage.RegisterDeclaredRules( + schema.GroupVersionKind{Group: "k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque", Version: "opaque", Kind: "OpaqueStructWithValidation"}, + coverage.FieldRules{ + "": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + }, + ) +} + +func init() { + coverage.RegisterDeclaredRules( + schema.GroupVersionKind{Group: "k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque", Version: "opaque", Kind: "OtherString"}, + coverage.FieldRules{ + "": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + }, + ) +} + +func init() { + coverage.RegisterDeclaredRules( + schema.GroupVersionKind{Group: "k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque", Version: "opaque", Kind: "OtherStruct"}, + coverage.FieldRules{ + "": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "stringField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + }, + ) +} + +func init() { + coverage.RegisterDeclaredRules( + schema.GroupVersionKind{Group: "k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque", Version: "opaque", Kind: "ParentWithOpaqueAliasWithValidation"}, + coverage.FieldRules{ + "field": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + }, + ) +} + +func init() { + coverage.RegisterDeclaredRules( + schema.GroupVersionKind{Group: "k8s.io/code-generator/cmd/validation-gen/output_tests/tags/opaque", Version: "opaque", Kind: "Struct"}, + coverage.FieldRules{ + "listMapOfOpaqueStructField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "listMapOfOpaqueStructField[*]": { + {ErrorType: "FieldValueDuplicate"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "listMapOfStructField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "listMapOfStructField[*]": { + {ErrorType: "FieldValueDuplicate"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "listMapOfStructField[*].stringField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "mapOfStringToOpaqueStructField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "mapOfStringToOpaqueStructField[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "mapOfStringToStructField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "mapOfStringToStructField[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "mapOfStringToStructField[*].stringField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "opaqueStructField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "opaqueStructPtrField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueRequired"}, + }, + "sliceOfOpaqueStructField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "sliceOfOpaqueStructField[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "sliceOfStructField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "sliceOfStructField[*]": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "sliceOfStructField[*].stringField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "structField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "structField.stringField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + "structPtrField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + {ErrorType: "FieldValueRequired"}, + }, + "structPtrField.stringField": { + {ErrorType: "FieldValueInvalid", Origin: "validateFalse"}, + }, + }, + ) +} + +func TestMain(m *testing.M) { + code := m.Run() + if err := coverage.AssertDeclarativeCoverage(); err != nil { + fmt.Fprintln(os.Stderr, err) + if code == 0 { + code = 1 + } + } + os.Exit(code) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc.go new file mode 100644 index 0000000000..e39eb11102 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc.go @@ -0,0 +1,99 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package optional + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:optional + // +k8s:validateFalse="field Struct.StringField" + StringField string `json:"stringField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.StringPtrField" + StringPtrField *string `json:"stringPtrField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.StringTypedefField" + StringTypedefField StringType `json:"stringTypedefField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.StringTypedefPtrField" + StringTypedefPtrField *StringType `json:"stringTypedefPtrField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.IntField" + IntField int `json:"intField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.IntPtrField" + IntPtrField *int `json:"intPtrField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.IntTypedefField" + IntTypedefField IntType `json:"intTypedefField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.IntTypedefPtrField" + IntTypedefPtrField *IntType `json:"intTypedefPtrField"` + + // non-pointer struct fields cannot be optional + + // +k8s:optional + // +k8s:validateFalse="field Struct.OtherStructPtrField" + OtherStructPtrField *OtherStruct `json:"otherStructPtrField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.SliceField" + SliceField []string `json:"sliceField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.SliceTypedefField" + SliceTypedefField SliceType `json:"sliceTypedefField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.MapField" + MapField map[string]string `json:"mapField"` + + // +k8s:optional + // +k8s:validateFalse="field Struct.MapTypedefField" + MapTypedefField MapType `json:"mapTypedefField"` +} + +// +k8s:validateFalse="type StringType" +type StringType string + +// +k8s:validateFalse="type IntType" +type IntType int + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct{} + +// +k8s:validateFalse="type SliceType" +type SliceType []string + +// +k8s:validateFalse="type MapType" +type MapType map[string]string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc_test.go new file mode 100644 index 0000000000..a3dc93ae5a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/doc_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 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 optional + +import ( + "testing" + + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + StringPtrField: ptr.To("xyz"), + StringTypedefField: StringType("abc"), + StringTypedefPtrField: ptr.To(StringType("xyz")), + IntField: 123, + IntPtrField: ptr.To(456), + IntTypedefField: IntType(123), + IntTypedefPtrField: ptr.To(IntType(456)), + OtherStructPtrField: &OtherStruct{}, + SliceField: []string{"a", "b"}, + SliceTypedefField: SliceType([]string{"a", "b"}), + MapField: map[string]string{"a": "b", "c": "d"}, + MapTypedefField: MapType(map[string]string{"a": "b", "c": "d"}), + }).ExpectValidateFalseByPath(map[string][]string{ + "stringField": {"field Struct.StringField"}, + "stringPtrField": {"field Struct.StringPtrField"}, + "stringTypedefField": {"field Struct.StringTypedefField", "type StringType"}, + "stringTypedefPtrField": {"field Struct.StringTypedefPtrField", "type StringType"}, + "intField": {"field Struct.IntField"}, + "intPtrField": {"field Struct.IntPtrField"}, + "intTypedefField": {"field Struct.IntTypedefField", "type IntType"}, + "intTypedefPtrField": {"field Struct.IntTypedefPtrField", "type IntType"}, + "otherStructPtrField": {"type OtherStruct", "field Struct.OtherStructPtrField"}, + "sliceField": {"field Struct.SliceField"}, + "sliceTypedefField": {"field Struct.SliceTypedefField", "type SliceType"}, + "mapField": {"field Struct.MapField"}, + "mapTypedefField": {"field Struct.MapTypedefField", "type MapType"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go new file mode 100644 index 0000000000..9539eddbf8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc.go @@ -0,0 +1,70 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package nonzerodefaults + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:optional + // +default="foobar" + StringField string `json:"stringField"` + + // +k8s:optional + // +default="foobar" + StringPtrField *string `json:"stringPtrField"` + + // +k8s:optional + // +default=123 + IntField int `json:"intField"` + + // +k8s:optional + // +default=123 + IntPtrField *int `json:"intPtrField"` + + // +k8s:optional + // +default=true + BoolField bool `json:"boolField"` + + // +k8s:optional + // +default=true + BoolPtrField *bool `json:"boolPtrField"` + + // +k8s:optional + // +default={"name": "x"} + StructPtrField *Submarker `json:"structPtrField"` + + // +k8s:optional + // +default=["foo"] + SliceField []string `json:"sliceField"` + + // +k8s:optional + // +default={"k": "v"} + MapField map[string]string `json:"mapField"` +} + +type Submarker struct { + Name string `json:"name"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc_test.go new file mode 100644 index 0000000000..7de239fa3a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/doc_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2024 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 nonzerodefaults + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("stringField"), ""), + field.Required(field.NewPath("stringPtrField"), ""), + field.Required(field.NewPath("intField"), ""), + field.Required(field.NewPath("intPtrField"), ""), + field.Required(field.NewPath("boolField"), ""), + field.Required(field.NewPath("boolPtrField"), ""), + field.Required(field.NewPath("structPtrField"), ""), + field.Required(field.NewPath("sliceField"), ""), + field.Required(field.NewPath("mapField"), ""), + }) + + st.Value(&Struct{ + StringField: "abc", + StringPtrField: ptr.To(""), + IntField: 123, + IntPtrField: ptr.To(0), + BoolField: true, + BoolPtrField: ptr.To(false), + StructPtrField: &Submarker{Name: "x"}, + SliceField: []string{"foo"}, + MapField: map[string]string{"k": "v"}, + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/zz_generated.validations.go new file mode 100644 index 0000000000..6527dd65cf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/nonzero_defaults/zz_generated.validations.go @@ -0,0 +1,338 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package nonzerodefaults + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.StringPtrField + }) + errs = append(errs, fn(fldPath.Child("stringPtrField"), obj.StringPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.BoolField + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return &oldObj.BoolField + }) + errs = append(errs, fn(fldPath.Child("boolField"), &obj.BoolField, oldVal, oldObj != nil)...) + } + + { // field Struct.BoolPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return oldObj.BoolPtrField + }) + errs = append(errs, fn(fldPath.Child("boolPtrField"), obj.BoolPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.StructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *Submarker, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Submarker { + return oldObj.StructPtrField + }) + errs = append(errs, fn(fldPath.Child("structPtrField"), obj.StructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc.go new file mode 100644 index 0000000000..abe128c914 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc.go @@ -0,0 +1,54 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package zerodefaults + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:optional + // +default="" + StringField string `json:"stringField"` + + // +k8s:optional + // +default="" + StringPtrField *string `json:"stringPtrField"` + + // +k8s:optional + // +default=0 + IntField int `json:"intField"` + + // +k8s:optional + // +default=0 + IntPtrField *int `json:"intPtrField"` + + // +k8s:optional + // +default=false + BoolField bool `json:"boolField"` + + // +k8s:optional + // +default=false + BoolPtrField *bool `json:"boolPtrField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc_test.go new file mode 100644 index 0000000000..886b0829e5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/doc_test.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 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 zerodefaults + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + // "stringField": optional value fields with zero defaults are just docs + // "intField": optional value fields with zero defaults are just docs + // "boolField": optional value fields with zero defaults are just docs + field.Required(field.NewPath("stringPtrField"), ""), + field.Required(field.NewPath("intPtrField"), ""), + field.Required(field.NewPath("boolPtrField"), ""), + }) + + st.Value(&Struct{ + StringField: "abc", + StringPtrField: ptr.To(""), + IntField: 123, + IntPtrField: ptr.To(0), + BoolField: true, + BoolPtrField: ptr.To(false), + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/zz_generated.validations.go new file mode 100644 index 0000000000..49514344b3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zero_defaults/zz_generated.validations.go @@ -0,0 +1,202 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package zerodefaults + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // optional value-type fields with zero-value defaults are purely documentation + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.StringPtrField + }) + errs = append(errs, fn(fldPath.Child("stringPtrField"), obj.StringPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // optional value-type fields with zero-value defaults are purely documentation + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.BoolField + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // optional value-type fields with zero-value defaults are purely documentation + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return &oldObj.BoolField + }) + errs = append(errs, fn(fldPath.Child("boolField"), &obj.BoolField, oldVal, oldObj != nil)...) + } + + { // field Struct.BoolPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + // optional fields with default values are effectively required + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return oldObj.BoolPtrField + }) + errs = append(errs, fn(fldPath.Child("boolPtrField"), obj.BoolPtrField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zz_generated.validations.go new file mode 100644 index 0000000000..6fad6d85d9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/optional/zz_generated.validations.go @@ -0,0 +1,550 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package optional + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_IntType validates an instance of IntType according +// to declarative validation rules in the API schema. +func Validate_IntType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *IntType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type IntType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_MapType validates an instance of MapType according +// to declarative validation rules in the API schema. +func Validate_MapType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MapType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_SliceType validates an instance of SliceType according +// to declarative validation rules in the API schema. +func Validate_SliceType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj SliceType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type SliceType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type StringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.StringPtrField + }) + errs = append(errs, fn(fldPath.Child("stringPtrField"), obj.StringPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_StringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return &oldObj.StringTypedefField + }) + errs = append(errs, fn(fldPath.Child("stringTypedefField"), &obj.StringTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringTypedefPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_StringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return oldObj.StringTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("stringTypedefPtrField"), obj.StringTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return &oldObj.IntTypedefField + }) + errs = append(errs, fn(fldPath.Child("intTypedefField"), &obj.IntTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntTypedefPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return oldObj.IntTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("intTypedefPtrField"), obj.IntTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.OtherStructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.OtherStructPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_OtherStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return oldObj.OtherStructPtrField + }) + errs = append(errs, fn(fldPath.Child("otherStructPtrField"), obj.OtherStructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj SliceType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_SliceType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) SliceType { + return oldObj.SliceTypedefField + }) + errs = append(errs, fn(fldPath.Child("sliceTypedefField"), obj.SliceTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj MapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MapType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapType { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/doc.go new file mode 100644 index 0000000000..e7a5fba6f8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/doc.go @@ -0,0 +1,55 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package discriminators + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + DiscriminatorField Discriminator `json:"discriminatorField"` + DiscriminatorFieldDisabled DiscriminatorDisabled `json:"discriminatorFieldDisabled"` +} + +type Discriminator struct { + // +k8s:ifEnabled(FeatureZ)=+k8s:modeDiscriminator + Discriminator string `json:"discriminator"` + + // +k8s:ifEnabled(FeatureZ)=+k8s:ifMode("A")=+k8s:optional + FieldA *string `json:"fieldA"` + + // +k8s:ifEnabled(FeatureZ)=+k8s:ifMode("B")=+k8s:optional + FieldB *string `json:"fieldB"` +} + +type DiscriminatorDisabled struct { + // +k8s:ifDisabled(FeatureZ)=+k8s:modeDiscriminator + Discriminator string `json:"discriminator"` + + // +k8s:ifDisabled(FeatureZ)=+k8s:ifMode("A")=+k8s:optional + FieldA *string `json:"fieldA"` + + // +k8s:ifDisabled(FeatureZ)=+k8s:ifMode("B")=+k8s:optional + FieldB *string `json:"fieldB"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/doc_test.go new file mode 100644 index 0000000000..d8aa582f5a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/doc_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2024 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 discriminators + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).Opts(map[string]bool{"FeatureZ": false}).ExpectValid() + + st.Value(&Struct{ + DiscriminatorField: Discriminator{ + Discriminator: "A", + FieldB: ptr.To("invalid"), // invalid because discriminator is A + }, + DiscriminatorFieldDisabled: DiscriminatorDisabled{ + Discriminator: "A", + FieldB: ptr.To("invalid"), // invalid because discriminator is A + }, + }).Opts(map[string]bool{"FeatureZ": false}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{field.Forbidden(field.NewPath("discriminatorFieldDisabled", "fieldB"), "").WithOrigin("")}, + ) + + st.Value(&Struct{ + DiscriminatorField: Discriminator{ + Discriminator: "A", + FieldB: ptr.To("invalid"), // invalid because discriminator is A + }, + DiscriminatorFieldDisabled: DiscriminatorDisabled{ + Discriminator: "A", + FieldB: ptr.To("invalid"), // invalid because discriminator is A + }, + }).Opts(map[string]bool{"FeatureZ": true}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{field.Forbidden(field.NewPath("discriminatorField", "fieldB"), "").WithOrigin("")}, + ) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/zz_generated.validations.go new file mode 100644 index 0000000000..cf5f92e7ba --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/discriminators/zz_generated.validations.go @@ -0,0 +1,258 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package discriminators + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Discriminator validates an instance of Discriminator according +// to declarative validation rules in the API schema. +func Validate_Discriminator( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Discriminator) (errs field.ErrorList) { + + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureZ", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Discriminator) field.ErrorList { + return validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *Discriminator) *string { return obj.FieldA }, + func(obj *Discriminator) string { return obj.Discriminator }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureZ", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Discriminator) field.ErrorList { + return validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", + func(obj *Discriminator) *string { return obj.FieldB }, + func(obj *Discriminator) string { return obj.Discriminator }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Discriminator.Discriminator has no validation + // field Discriminator.FieldA has no validation + // field Discriminator.FieldB has no validation + return errs +} + +// Validate_DiscriminatorDisabled validates an instance of DiscriminatorDisabled according +// to declarative validation rules in the API schema. +func Validate_DiscriminatorDisabled( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *DiscriminatorDisabled) (errs field.ErrorList) { + + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureZ", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *DiscriminatorDisabled) field.ErrorList { + return validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", + func(obj *DiscriminatorDisabled) *string { return obj.FieldA }, + func(obj *DiscriminatorDisabled) string { return obj.Discriminator }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "A", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureZ", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *DiscriminatorDisabled) field.ErrorList { + return validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", + func(obj *DiscriminatorDisabled) *string { return obj.FieldB }, + func(obj *DiscriminatorDisabled) string { return obj.Discriminator }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...) + return errs + }, + []validate.DiscriminatedRule[*string, string]{ + + { + Value: "B", + Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + errs := field.ErrorList{} + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return errs + } + return errs + }, + }, + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field DiscriminatorDisabled.Discriminator has no validation + // field DiscriminatorDisabled.FieldA has no validation + // field DiscriminatorDisabled.FieldB has no validation + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.DiscriminatorField + fn := func( + fldPath *field.Path, + obj, oldObj *Discriminator, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Discriminator(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Discriminator { + return &oldObj.DiscriminatorField + }) + errs = append(errs, fn(fldPath.Child("discriminatorField"), &obj.DiscriminatorField, oldVal, oldObj != nil)...) + } + + { // field Struct.DiscriminatorFieldDisabled + fn := func( + fldPath *field.Path, + obj, oldObj *DiscriminatorDisabled, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_DiscriminatorDisabled(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *DiscriminatorDisabled { + return &oldObj.DiscriminatorFieldDisabled + }) + errs = append(errs, fn(fldPath.Child("discriminatorFieldDisabled"), &obj.DiscriminatorFieldDisabled, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/doc.go new file mode 100644 index 0000000000..b1939dd843 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/doc.go @@ -0,0 +1,49 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package lists + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:ifEnabled(FeatureX)=+k8s:listType=map + // +k8s:ifEnabled(FeatureX)=+k8s:listMapKey=name + ListMap []ListItem `json:"listMap"` + + // +k8s:ifDisabled(FeatureX)=+k8s:listType=map + // +k8s:ifDisabled(FeatureX)=+k8s:listMapKey=name + ListMapDisabled []ListItem `json:"listMapDisabled"` + + // +k8s:ifEnabled(FeatureX)=+k8s:eachVal=+k8s:validateFalse="field Struct.ListEachVal/val" + ListEachVal []ListItem `json:"listEachVal"` + + // +k8s:ifDisabled(FeatureX)=+k8s:eachVal=+k8s:validateFalse="field Struct.ListEachValDisabled/val" + ListEachValDisabled []ListItem `json:"listEachValDisabled"` +} + +type ListItem struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/doc_test.go new file mode 100644 index 0000000000..1da98453e8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/doc_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2024 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 lists + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + ListMap: []ListItem{ + {Name: "a", Value: "1"}, + {Name: "a", Value: "2"}, + }, + ListMapDisabled: []ListItem{ + {Name: "b", Value: "1"}, + {Name: "b", Value: "2"}, + }, + }).Opts(map[string]bool{"FeatureX": false}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{field.Duplicate(field.NewPath("listMapDisabled").Index(1), ListItem{Name: "b", Value: "2"}).WithOrigin("")}, + ) + + st.Value(&Struct{ + ListMap: []ListItem{ + {Name: "a", Value: "1"}, + {Name: "a", Value: "2"}, + }, + ListMapDisabled: []ListItem{ + {Name: "b", Value: "1"}, + {Name: "b", Value: "2"}, + }, + }).Opts(map[string]bool{"FeatureX": true}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{field.Duplicate(field.NewPath("listMap").Index(1), ListItem{Name: "a", Value: "2"}).WithOrigin("")}, + ) + + st.Value(&Struct{ + ListEachVal: []ListItem{ + {Name: "c", Value: "3"}, + }, + ListEachValDisabled: []ListItem{ + {Name: "d", Value: "4"}, + }, + }).Opts(map[string]bool{"FeatureX": false}).ExpectValidateFalseByPath(map[string][]string{ + "listEachValDisabled[0]": {"field Struct.ListEachValDisabled/val"}, + }) + + st.Value(&Struct{ + ListEachVal: []ListItem{ + {Name: "c", Value: "3"}, + }, + ListEachValDisabled: []ListItem{ + {Name: "d", Value: "4"}, + }, + }).Opts(map[string]bool{"FeatureX": true}).ExpectValidateFalseByPath(map[string][]string{ + "listEachVal[0]": {"field Struct.ListEachVal/val"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/zz_generated.validations.go new file mode 100644 index 0000000000..c0055b7a7e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/lists/zz_generated.validations.go @@ -0,0 +1,184 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package lists + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ListMap + fn := func( + fldPath *field.Path, + obj, oldObj []ListItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []ListItem) field.ErrorList { + return validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *ListItem, b *ListItem) bool { return a.Name == b.Name }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []ListItem { + return oldObj.ListMap + }) + errs = append(errs, fn(fldPath.Child("listMap"), obj.ListMap, oldVal, oldObj != nil)...) + } + + { // field Struct.ListMapDisabled + fn := func( + fldPath *field.Path, + obj, oldObj []ListItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []ListItem) field.ErrorList { + return validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *ListItem, b *ListItem) bool { return a.Name == b.Name }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []ListItem { + return oldObj.ListMapDisabled + }) + errs = append(errs, fn(fldPath.Child("listMapDisabled"), obj.ListMapDisabled, oldVal, oldObj != nil)...) + } + + { // field Struct.ListEachVal + fn := func( + fldPath *field.Path, + obj, oldObj []ListItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []ListItem) field.ErrorList { + return validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ListItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListEachVal/val") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []ListItem { + return oldObj.ListEachVal + }) + errs = append(errs, fn(fldPath.Child("listEachVal"), obj.ListEachVal, oldVal, oldObj != nil)...) + } + + { // field Struct.ListEachValDisabled + fn := func( + fldPath *field.Path, + obj, oldObj []ListItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []ListItem) field.ErrorList { + return validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ListItem) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ListEachValDisabled/val") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []ListItem { + return oldObj.ListEachValDisabled + }) + errs = append(errs, fn(fldPath.Child("listEachValDisabled"), obj.ListEachValDisabled, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/doc.go new file mode 100644 index 0000000000..f2d3fd1092 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/doc.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package maps + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:ifEnabled(FeatureX)=+k8s:validateFalse="field Struct.MapField" + MapField map[string]string `json:"mapField"` + + // +k8s:ifDisabled(FeatureX)=+k8s:validateFalse="field Struct.MapFieldDisabled" + MapFieldDisabled map[string]string `json:"mapFieldDisabled"` + + // +k8s:ifEnabled(FeatureX)=+k8s:eachKey=+k8s:validateFalse="field Struct.MapFieldEachKey/key" + MapFieldEachKey map[string]string `json:"mapFieldEachKey"` + + // +k8s:ifDisabled(FeatureX)=+k8s:eachKey=+k8s:validateFalse="field Struct.MapFieldEachKeyDisabled/key" + MapFieldEachKeyDisabled map[string]string `json:"mapFieldEachKeyDisabled"` + + // +k8s:ifEnabled(FeatureX)=+k8s:eachVal=+k8s:validateFalse="field Struct.MapFieldEachVal/val" + MapFieldEachVal map[string]string `json:"mapFieldEachVal"` + + // +k8s:ifDisabled(FeatureX)=+k8s:eachVal=+k8s:validateFalse="field Struct.MapFieldEachValDisabled/val" + MapFieldEachValDisabled map[string]string `json:"mapFieldEachValDisabled"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/doc_test.go new file mode 100644 index 0000000000..ea0aff8dbf --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/doc_test.go @@ -0,0 +1,75 @@ +/* +Copyright 2024 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 maps + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + MapField: map[string]string{ + "a": "1", + }, + MapFieldDisabled: map[string]string{ + "b": "2", + }, + MapFieldEachKey: map[string]string{ + "a": "1", + }, + MapFieldEachKeyDisabled: map[string]string{ + "b": "2", + }, + MapFieldEachVal: map[string]string{ + "a": "1", + }, + MapFieldEachValDisabled: map[string]string{ + "b": "2", + }, + }).Opts(map[string]bool{"FeatureX": false}).ExpectValidateFalseByPath(map[string][]string{ + "mapFieldDisabled": {"field Struct.MapFieldDisabled"}, + "mapFieldEachKeyDisabled": {"field Struct.MapFieldEachKeyDisabled/key"}, + "mapFieldEachValDisabled[b]": {"field Struct.MapFieldEachValDisabled/val"}, + }) + + st.Value(&Struct{ + MapField: map[string]string{ + "a": "1", + }, + MapFieldDisabled: map[string]string{ + "b": "2", + }, + MapFieldEachKey: map[string]string{ + "a": "1", + }, + MapFieldEachKeyDisabled: map[string]string{ + "b": "2", + }, + MapFieldEachVal: map[string]string{ + "a": "1", + }, + MapFieldEachValDisabled: map[string]string{ + "b": "2", + }, + }).Opts(map[string]bool{"FeatureX": true}).ExpectValidateFalseByPath(map[string][]string{ + "mapField": {"field Struct.MapField"}, + "mapFieldEachKey": {"field Struct.MapFieldEachKey/key"}, + "mapFieldEachVal[a]": {"field Struct.MapFieldEachVal/val"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/zz_generated.validations.go new file mode 100644 index 0000000000..eb6ba12e35 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/maps/zz_generated.validations.go @@ -0,0 +1,242 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maps + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapFieldDisabled + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapFieldDisabled") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapFieldDisabled + }) + errs = append(errs, fn(fldPath.Child("mapFieldDisabled"), obj.MapFieldDisabled, oldVal, oldObj != nil)...) + } + + { // field Struct.MapFieldEachKey + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapFieldEachKey/key") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapFieldEachKey + }) + errs = append(errs, fn(fldPath.Child("mapFieldEachKey"), obj.MapFieldEachKey, oldVal, oldObj != nil)...) + } + + { // field Struct.MapFieldEachKeyDisabled + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapFieldEachKeyDisabled/key") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapFieldEachKeyDisabled + }) + errs = append(errs, fn(fldPath.Child("mapFieldEachKeyDisabled"), obj.MapFieldEachKeyDisabled, oldVal, oldObj != nil)...) + } + + { // field Struct.MapFieldEachVal + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapFieldEachVal/val") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapFieldEachVal + }) + errs = append(errs, fn(fldPath.Child("mapFieldEachVal"), obj.MapFieldEachVal, oldVal, oldObj != nil)...) + } + + { // field Struct.MapFieldEachValDisabled + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapFieldEachValDisabled/val") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapFieldEachValDisabled + }) + errs = append(errs, fn(fldPath.Child("mapFieldEachValDisabled"), obj.MapFieldEachValDisabled, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/doc.go new file mode 100644 index 0000000000..15602823ed --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/doc.go @@ -0,0 +1,50 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package simple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +type MySlice []string + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:ifEnabled(FeatureX)=+k8s:validateFalse="field Struct.XEnabledField" + XEnabledField string `json:"xEnabledField"` + + // +k8s:ifDisabled(FeatureX)=+k8s:validateFalse="field Struct.XDisabledField" + XDisabledField string `json:"xDisabledField"` + + // +k8s:ifEnabled(FeatureY)=+k8s:validateFalse="field Struct.YEnabledField" + YEnabledField string `json:"yEnabledField"` + + // +k8s:ifDisabled(FeatureY)=+k8s:validateFalse="field Struct.YDisabledField" + YDisabledField string `json:"yDisabledField"` + + // +k8s:ifEnabled(FeatureX)=+k8s:validateFalse="field Struct.XYMixedField/X" + // +k8s:ifDisabled(FeatureY)=+k8s:validateFalse="field Struct.XYMixedField/Y" + XYMixedField string `json:"xyMixedField"` + // +k8s:ifEnabled(FeatureX)=+k8s:validateFalse="field Struct.NilableAliasField" + NilableAliasField MySlice `json:"nilableAliasField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/doc_test.go new file mode 100644 index 0000000000..1e852ea1e9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/doc_test.go @@ -0,0 +1,81 @@ +/* +Copyright 2024 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 simple + +import ( + "errors" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).Opts(map[string]bool{"FeatureX": false, "FeatureY": false}).ExpectValidateFalseByPath(map[string][]string{ + // All ifDisabled validations should trigger + "xDisabledField": {"field Struct.XDisabledField"}, + "yDisabledField": {"field Struct.YDisabledField"}, + "xyMixedField": {"field Struct.XYMixedField/Y"}, + }) + + st.Value(&Struct{ + // All zero values + }).Opts(map[string]bool{"FeatureX": true, "FeatureY": true}).ExpectValidateFalseByPath(map[string][]string{ + // All ifEnabled validations should trigger + "xEnabledField": {"field Struct.XEnabledField"}, + "yEnabledField": {"field Struct.YEnabledField"}, + "xyMixedField": {"field Struct.XYMixedField/X"}, + "nilableAliasField": {"field Struct.NilableAliasField"}, + }) + + st.Value(&Struct{ + // All zero values + }).Opts(map[string]bool{"FeatureX": true, "FeatureY": false}).ExpectValidateFalseByPath(map[string][]string{ + // All ifEnabled validations should trigger + "xEnabledField": {"field Struct.XEnabledField"}, + "yDisabledField": {"field Struct.YDisabledField"}, + "xyMixedField": { + "field Struct.XYMixedField/X", + "field Struct.XYMixedField/Y"}, + "nilableAliasField": {"field Struct.NilableAliasField"}, + }) + + st.Value(&Struct{ + // All zero values + }).Opts(map[string]bool{"FeatureX": false, "FeatureY": true}).ExpectValidateFalseByPath(map[string][]string{ + // All ifEnabled validations should trigger + "xDisabledField": {"field Struct.XDisabledField"}, + "yEnabledField": {"field Struct.YEnabledField"}, + }) + + // No options declared: every referenced option is undeclared. + internal := func(p string) *field.Error { + return field.InternalError(field.NewPath(p), errors.New("")) + } + st.Value(&Struct{}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + internal("xEnabledField"), + internal("xDisabledField"), + internal("yEnabledField"), + internal("yDisabledField"), + internal("xyMixedField"), // ifEnabled(FeatureX) + internal("xyMixedField"), // ifDisabled(FeatureY) + internal("nilableAliasField"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/zz_generated.validations.go new file mode 100644 index 0000000000..bf6b6cd428 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/simple/zz_generated.validations.go @@ -0,0 +1,236 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package simple + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.XEnabledField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.XEnabledField") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.XEnabledField + }) + errs = append(errs, fn(fldPath.Child("xEnabledField"), &obj.XEnabledField, oldVal, oldObj != nil)...) + } + + { // field Struct.XDisabledField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.XDisabledField") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.XDisabledField + }) + errs = append(errs, fn(fldPath.Child("xDisabledField"), &obj.XDisabledField, oldVal, oldObj != nil)...) + } + + { // field Struct.YEnabledField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureY", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.YEnabledField") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.YEnabledField + }) + errs = append(errs, fn(fldPath.Child("yEnabledField"), &obj.YEnabledField, oldVal, oldObj != nil)...) + } + + { // field Struct.YDisabledField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureY", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.YDisabledField") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.YDisabledField + }) + errs = append(errs, fn(fldPath.Child("yDisabledField"), &obj.YDisabledField, oldVal, oldObj != nil)...) + } + + { // field Struct.XYMixedField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureY", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.XYMixedField/Y") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.XYMixedField/X") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.XYMixedField + }) + errs = append(errs, fn(fldPath.Child("xyMixedField"), &obj.XYMixedField, oldVal, oldObj != nil)...) + } + + { // field Struct.NilableAliasField + fn := func( + fldPath *field.Path, + obj, oldObj MySlice, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj MySlice) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.NilableAliasField") + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MySlice { + return oldObj.NilableAliasField + }) + errs = append(errs, fn(fldPath.Child("nilableAliasField"), obj.NilableAliasField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/doc.go new file mode 100644 index 0000000000..e70fddf223 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/doc.go @@ -0,0 +1,41 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package subfields + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:ifEnabled(FeatureX)=+k8s:subfield(xEnabledField)=+k8s:validateFalse="field Struct.ObjectMeta.XEnabledField" + ObjectMeta `json:"metadata,omitempty"` + + // +k8s:ifDisabled(FeatureX)=+k8s:subfield(xDisabledField)=+k8s:validateFalse="field Struct.ObjectMetaDisabled.XDisabledField" + ObjectMetaDisabled ObjectMeta `json:"metadataDisabled,omitempty"` +} + +type ObjectMeta struct { + XEnabledField string `json:"xEnabledField"` + XDisabledField string `json:"xDisabledField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/doc_test.go new file mode 100644 index 0000000000..5666d2de0e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/doc_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2024 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 subfields + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).Opts(map[string]bool{"FeatureX": false}).ExpectValidateFalseByPath(map[string][]string{ + "metadataDisabled.xDisabledField": {"field Struct.ObjectMetaDisabled.XDisabledField"}, + }) + + st.Value(&Struct{ + // All zero values + }).Opts(map[string]bool{"FeatureX": true}).ExpectValidateFalseByPath(map[string][]string{ + "metadata.xEnabledField": {"field Struct.ObjectMeta.XEnabledField"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/zz_generated.validations.go new file mode 100644 index 0000000000..f942b769b9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/subfields/zz_generated.validations.go @@ -0,0 +1,129 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package subfields + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ObjectMeta + fn := func( + fldPath *field.Path, + obj, oldObj *ObjectMeta, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ObjectMeta) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "xEnabledField", + func(o *ObjectMeta) *string { return &o.XEnabledField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ObjectMeta.XEnabledField") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ObjectMeta { + return &oldObj.ObjectMeta + }) + errs = append(errs, fn(fldPath.Child("metadata"), &obj.ObjectMeta, oldVal, oldObj != nil)...) + } + + { // field Struct.ObjectMetaDisabled + fn := func( + fldPath *field.Path, + obj, oldObj *ObjectMeta, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ObjectMeta) field.ErrorList { + return validate.Subfield(ctx, op, fldPath, obj, oldObj, "xDisabledField", + func(o *ObjectMeta) *string { return &o.XDisabledField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ObjectMetaDisabled.XDisabledField") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ObjectMeta { + return &oldObj.ObjectMetaDisabled + }) + errs = append(errs, fn(fldPath.Child("metadataDisabled"), &obj.ObjectMetaDisabled, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/doc.go new file mode 100644 index 0000000000..03db34f1f7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/doc.go @@ -0,0 +1,91 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package unions + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + UnionField Union `json:"unionField"` + UnionFieldDisabled UnionDisabled `json:"unionFieldDisabled"` + + ZeroOrOneOfField ZeroOrOneOf `json:"zeroOrOneOfField"` + ZeroOrOneOfFieldDisabled ZeroOrOneOfDisabled `json:"zeroOrOneOfFieldDisabled"` + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:ifEnabled(FeatureX)=+k8s:item(name: "succeeded")=+k8s:zeroOrOneOfMember + // +k8s:ifEnabled(FeatureX)=+k8s:item(name: "failed")=+k8s:zeroOrOneOfMember + ZeroOrOneOfItem []Task `json:"zeroOrOneOfItem"` + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:ifDisabled(FeatureX)=+k8s:item(name: "succeeded")=+k8s:zeroOrOneOfMember + // +k8s:ifDisabled(FeatureX)=+k8s:item(name: "failed")=+k8s:zeroOrOneOfMember + ZeroOrOneOfItemDisabled []Task `json:"zeroOrOneOfItemDisabled"` +} + +type Task struct { + Name string `json:"name"` + State string `json:"state"` +} + +type Union struct { + // +k8s:ifEnabled(FeatureX)=+k8s:unionDiscriminator + Discriminator string `json:"discriminator"` + + // +k8s:ifEnabled(FeatureX)=+k8s:unionMember + XEnabledField string `json:"xEnabledField"` + + // +k8s:ifEnabled(FeatureX)=+k8s:unionMember + XDisabledField string `json:"xDisabledField"` +} + +type UnionDisabled struct { + // +k8s:ifDisabled(FeatureX)=+k8s:unionDiscriminator + Discriminator string `json:"discriminator"` + + // +k8s:ifDisabled(FeatureX)=+k8s:unionMember + XEnabledField string `json:"xEnabledField"` + + // +k8s:ifDisabled(FeatureX)=+k8s:unionMember + XDisabledField string `json:"xDisabledField"` +} + +type ZeroOrOneOf struct { + // +k8s:ifEnabled(FeatureX)=+k8s:zeroOrOneOfMember + XEnabledField string `json:"xEnabledField"` + + // +k8s:ifEnabled(FeatureX)=+k8s:zeroOrOneOfMember + XDisabledField string `json:"xDisabledField"` +} + +type ZeroOrOneOfDisabled struct { + // +k8s:ifDisabled(FeatureX)=+k8s:zeroOrOneOfMember + XEnabledField string `json:"xEnabledField"` + + // +k8s:ifDisabled(FeatureX)=+k8s:zeroOrOneOfMember + XDisabledField string `json:"xDisabledField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/doc_test.go new file mode 100644 index 0000000000..513ba6dd89 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/doc_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2024 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 unions + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero values + }).Opts(map[string]bool{"FeatureX": false}).ExpectValid() + + st.Value(&Struct{ + UnionField: Union{ + Discriminator: "xEnabledField", + XEnabledField: "foo", + XDisabledField: "bar", // Invalid since XEnabledField is the discriminator + }, + ZeroOrOneOfField: ZeroOrOneOf{ + XEnabledField: "foo", + XDisabledField: "bar", + }, + ZeroOrOneOfItem: []Task{ + {Name: "succeeded"}, + {Name: "failed"}, + }, + }).Opts(map[string]bool{"FeatureX": true}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Invalid(field.NewPath("unionField", "xDisabledField"), "", "").WithOrigin("union"), + field.Invalid(field.NewPath("unionField", "xEnabledField"), "", "").WithOrigin("union"), + field.Invalid(field.NewPath("zeroOrOneOfField"), "{XEnabledField:\"foo\", XDisabledField:\"bar\"}", "").WithOrigin("zeroOrOneOf"), + field.Invalid(field.NewPath("zeroOrOneOfItem"), "[{Name:\"succeeded\", State:\"\"} {Name:\"failed\", State:\"\"}]", "").WithOrigin("zeroOrOneOf"), + }, + ) + + st.Value(&Struct{ + UnionField: Union{ + Discriminator: "xEnabledField", + XEnabledField: "foo", + XDisabledField: "bar", + }, + ZeroOrOneOfField: ZeroOrOneOf{ + XEnabledField: "foo", + XDisabledField: "bar", + }, + ZeroOrOneOfItem: []Task{ + {Name: "succeeded"}, + {Name: "failed"}, + }, + }).Opts(map[string]bool{"FeatureX": false}).ExpectValid() + + st.Value(&Struct{ + UnionFieldDisabled: UnionDisabled{ + Discriminator: "xEnabledField", + XEnabledField: "foo", + XDisabledField: "bar", // Invalid since XEnabledField is the discriminator + }, + ZeroOrOneOfFieldDisabled: ZeroOrOneOfDisabled{ + XEnabledField: "foo", + XDisabledField: "bar", + }, + ZeroOrOneOfItemDisabled: []Task{ + {Name: "succeeded"}, + {Name: "failed"}, + }, + }).Opts(map[string]bool{"FeatureX": false}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin(), + field.ErrorList{ + field.Invalid(field.NewPath("unionFieldDisabled", "xDisabledField"), "", "").WithOrigin("union"), + field.Invalid(field.NewPath("unionFieldDisabled", "xEnabledField"), "", "").WithOrigin("union"), + field.Invalid(field.NewPath("zeroOrOneOfFieldDisabled"), "{XEnabledField:\"foo\", XDisabledField:\"bar\"}", "").WithOrigin("zeroOrOneOf"), + field.Invalid(field.NewPath("zeroOrOneOfItemDisabled"), "[{Name:\"succeeded\", State:\"\"} {Name:\"failed\", State:\"\"}]", "").WithOrigin("zeroOrOneOf"), + }, + ) + + st.Value(&Struct{ + UnionFieldDisabled: UnionDisabled{ + Discriminator: "xEnabledField", + XEnabledField: "foo", + XDisabledField: "bar", + }, + ZeroOrOneOfFieldDisabled: ZeroOrOneOfDisabled{ + XEnabledField: "foo", + XDisabledField: "bar", + }, + ZeroOrOneOfItemDisabled: []Task{ + {Name: "succeeded"}, + {Name: "failed"}, + }, + }).Opts(map[string]bool{"FeatureX": true}).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/zz_generated.validations.go new file mode 100644 index 0000000000..ed92643c1b --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/unions/zz_generated.validations.go @@ -0,0 +1,405 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package unions + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_Struct_zeroOrOneOfItem_ = validate.NewUnionMembership(validate.NewUnionMember("zeroOrOneOfItem[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("zeroOrOneOfItem[{\"name\": \"failed\"}]")) +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_Struct_zeroOrOneOfItemDisabled_ = validate.NewUnionMembership(validate.NewUnionMember("zeroOrOneOfItemDisabled[{\"name\": \"succeeded\"}]"), validate.NewUnionMember("zeroOrOneOfItemDisabled[{\"name\": \"failed\"}]")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.UnionField + fn := func( + fldPath *field.Path, + obj, oldObj *Union, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_Union(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *Union { + return &oldObj.UnionField + }) + errs = append(errs, fn(fldPath.Child("unionField"), &obj.UnionField, oldVal, oldObj != nil)...) + } + + { // field Struct.UnionFieldDisabled + fn := func( + fldPath *field.Path, + obj, oldObj *UnionDisabled, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_UnionDisabled(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *UnionDisabled { + return &oldObj.UnionFieldDisabled + }) + errs = append(errs, fn(fldPath.Child("unionFieldDisabled"), &obj.UnionFieldDisabled, oldVal, oldObj != nil)...) + } + + { // field Struct.ZeroOrOneOfField + fn := func( + fldPath *field.Path, + obj, oldObj *ZeroOrOneOf, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ZeroOrOneOf(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ZeroOrOneOf { + return &oldObj.ZeroOrOneOfField + }) + errs = append(errs, fn(fldPath.Child("zeroOrOneOfField"), &obj.ZeroOrOneOfField, oldVal, oldObj != nil)...) + } + + { // field Struct.ZeroOrOneOfFieldDisabled + fn := func( + fldPath *field.Path, + obj, oldObj *ZeroOrOneOfDisabled, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call the type's validation function + errs = append(errs, Validate_ZeroOrOneOfDisabled(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *ZeroOrOneOfDisabled { + return &oldObj.ZeroOrOneOfFieldDisabled + }) + errs = append(errs, fn(fldPath.Child("zeroOrOneOfFieldDisabled"), &obj.ZeroOrOneOfFieldDisabled, oldVal, oldObj != nil)...) + } + + { // field Struct.ZeroOrOneOfItem + fn := func( + fldPath *field.Path, + obj, oldObj []Task, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []Task) field.ErrorList { + return validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_Struct_zeroOrOneOfItem_, + func(list []Task) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, + func(list []Task) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Task, b *Task) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Task { + return oldObj.ZeroOrOneOfItem + }) + errs = append(errs, fn(fldPath.Child("zeroOrOneOfItem"), obj.ZeroOrOneOfItem, oldVal, oldObj != nil)...) + } + + { // field Struct.ZeroOrOneOfItemDisabled + fn := func( + fldPath *field.Path, + obj, oldObj []Task, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []Task) field.ErrorList { + return validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_Struct_zeroOrOneOfItemDisabled_, + func(list []Task) bool { + for i := range list { + if list[i].Name == "failed" { + return true + } + } + return false + }, + func(list []Task) bool { + for i := range list { + if list[i].Name == "succeeded" { + return true + } + } + return false + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Task, b *Task) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Task { + return oldObj.ZeroOrOneOfItemDisabled + }) + errs = append(errs, fn(fldPath.Child("zeroOrOneOfItemDisabled"), obj.ZeroOrOneOfItemDisabled, oldVal, oldObj != nil)...) + } + + return errs +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_Union_ = validate.NewDiscriminatedUnionMembership("discriminator", validate.NewDiscriminatedUnionMember("xEnabledField", "XEnabledField"), validate.NewDiscriminatedUnionMember("xDisabledField", "XDisabledField")) + +// Validate_Union validates an instance of Union according +// to declarative validation rules in the API schema. +func Validate_Union( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Union) (errs field.ErrorList) { + + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Union) field.ErrorList { + return validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_Union_, + func(obj *Union) string { + if obj == nil { + return "" + } + return string(obj.Discriminator) + }, + func(obj *Union) bool { + if obj == nil { + return false + } + var z string + return obj.XEnabledField != z + }, + func(obj *Union) bool { + if obj == nil { + return false + } + var z string + return obj.XDisabledField != z + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Union.Discriminator has no validation + // field Union.XEnabledField has no validation + // field Union.XDisabledField has no validation + return errs +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_UnionDisabled_ = validate.NewDiscriminatedUnionMembership("discriminator", validate.NewDiscriminatedUnionMember("xEnabledField", "XEnabledField"), validate.NewDiscriminatedUnionMember("xDisabledField", "XDisabledField")) + +// Validate_UnionDisabled validates an instance of UnionDisabled according +// to declarative validation rules in the API schema. +func Validate_UnionDisabled( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *UnionDisabled) (errs field.ErrorList) { + + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UnionDisabled) field.ErrorList { + return validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_UnionDisabled_, + func(obj *UnionDisabled) string { + if obj == nil { + return "" + } + return string(obj.Discriminator) + }, + func(obj *UnionDisabled) bool { + if obj == nil { + return false + } + var z string + return obj.XEnabledField != z + }, + func(obj *UnionDisabled) bool { + if obj == nil { + return false + } + var z string + return obj.XDisabledField != z + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field UnionDisabled.Discriminator has no validation + // field UnionDisabled.XEnabledField has no validation + // field UnionDisabled.XDisabledField has no validation + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_ZeroOrOneOf_ = validate.NewUnionMembership(validate.NewUnionMember("xEnabledField"), validate.NewUnionMember("xDisabledField")) + +// Validate_ZeroOrOneOf validates an instance of ZeroOrOneOf according +// to declarative validation rules in the API schema. +func Validate_ZeroOrOneOf( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ZeroOrOneOf) (errs field.ErrorList) { + + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ZeroOrOneOf) field.ErrorList { + return validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_ZeroOrOneOf_, + func(obj *ZeroOrOneOf) bool { + if obj == nil { + return false + } + var z string + return obj.XEnabledField != z + }, + func(obj *ZeroOrOneOf) bool { + if obj == nil { + return false + } + var z string + return obj.XDisabledField != z + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field ZeroOrOneOf.XEnabledField has no validation + // field ZeroOrOneOf.XDisabledField has no validation + return errs +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_ZeroOrOneOfDisabled_ = validate.NewUnionMembership(validate.NewUnionMember("xEnabledField"), validate.NewUnionMember("xDisabledField")) + +// Validate_ZeroOrOneOfDisabled validates an instance of ZeroOrOneOfDisabled according +// to declarative validation rules in the API schema. +func Validate_ZeroOrOneOfDisabled( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ZeroOrOneOfDisabled) (errs field.ErrorList) { + + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ZeroOrOneOfDisabled) field.ErrorList { + return validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_options_unions_ZeroOrOneOfDisabled_, + func(obj *ZeroOrOneOfDisabled) bool { + if obj == nil { + return false + } + var z string + return obj.XEnabledField != z + }, + func(obj *ZeroOrOneOfDisabled) bool { + if obj == nil { + return false + } + var z string + return obj.XDisabledField != z + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field ZeroOrOneOfDisabled.XEnabledField has no validation + // field ZeroOrOneOfDisabled.XDisabledField has no validation + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/doc.go new file mode 100644 index 0000000000..2393c14ec8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/doc.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package update + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:ifEnabled(FeatureX)=+k8s:immutable + ImmutableEnabled string `json:"immutableEnabled"` + + // +k8s:ifDisabled(FeatureX)=+k8s:immutable + ImmutableDisabled string `json:"immutableDisabled"` + + // +k8s:ifEnabled(FeatureX)=+k8s:update=NoModify + UpdateNoModifyEnabled string `json:"updateNoModifyEnabled"` + + // +k8s:ifDisabled(FeatureX)=+k8s:update=NoModify + UpdateNoModifyDisabled string `json:"updateNoModifyDisabled"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/doc_test.go new file mode 100644 index 0000000000..4bb234b9ce --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/doc_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2024 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 update + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + structA := Struct{ + ImmutableEnabled: "aaa", + ImmutableDisabled: "bbb", + UpdateNoModifyEnabled: "ccc", + UpdateNoModifyDisabled: "ddd", + } + + structB := Struct{ + ImmutableEnabled: "eee", + ImmutableDisabled: "fff", + UpdateNoModifyEnabled: "ggg", + UpdateNoModifyDisabled: "hhh", + } + + st.Value(&structA).OldValue(&structA).Opts(map[string]bool{"FeatureX": false}).ExpectValid() + + st.Value(&structB).OldValue(&structA).Opts(map[string]bool{"FeatureX": false}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring(), + field.ErrorList{ + field.Invalid(field.NewPath("immutableDisabled"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("updateNoModifyDisabled"), nil, "field cannot be modified once set").WithOrigin("update"), + }, + ) + + st.Value(&structB).OldValue(&structA).Opts(map[string]bool{"FeatureX": true}).ExpectMatches( + field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailSubstring(), + field.ErrorList{ + field.Invalid(field.NewPath("immutableEnabled"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("updateNoModifyEnabled"), nil, "field cannot be modified once set").WithOrigin("update"), + }, + ) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/zz_generated.validations.go new file mode 100644 index 0000000000..b96320a866 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/options/update/zz_generated.validations.go @@ -0,0 +1,191 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package update + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ImmutableEnabled + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.ImmutableEnabled + }) + errs = append(errs, fn(fldPath.Child("immutableEnabled"), &obj.ImmutableEnabled, oldVal, oldObj != nil)...) + } + + { // field Struct.ImmutableDisabled + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.ImmutableDisabled + }) + errs = append(errs, fn(fldPath.Child("immutableDisabled"), &obj.ImmutableDisabled, oldVal, oldObj != nil)...) + } + + { // field Struct.UpdateNoModifyEnabled + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoModify) + }).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.UpdateNoModifyEnabled + }) + errs = append(errs, fn(fldPath.Child("updateNoModifyEnabled"), &obj.UpdateNoModifyEnabled, oldVal, oldObj != nil)...) + } + + { // field Struct.UpdateNoModifyDisabled + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoModify) + }).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.UpdateNoModifyDisabled + }) + errs = append(errs, fn(fldPath.Child("updateNoModifyDisabled"), &obj.UpdateNoModifyDisabled, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc.go new file mode 100644 index 0000000000..8a57606570 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc.go @@ -0,0 +1,115 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package required + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:required + // +k8s:validateFalse="field Struct.StringField" + StringField string `json:"stringField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.StringPtrField" + StringPtrField *string `json:"stringPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.StringTypedefField" + StringTypedefField StringType `json:"stringTypedefField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.StringTypedefPtrField" + StringTypedefPtrField *StringType `json:"stringTypedefPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.IntField" + IntField int `json:"intField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.IntPtrField" + IntPtrField *int `json:"intPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.IntTypedefField" + IntTypedefField IntType `json:"intTypedefField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.IntTypedefPtrField" + IntTypedefPtrField *IntType `json:"intTypedefPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.BoolField" + BoolField bool `json:"boolField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.FloatField" + FloatField float64 `json:"floatField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.ByteField" + ByteField byte `json:"byteField"` + + // non-pointer struct fields cannot be required or optional + + // +k8s:required + // +k8s:validateFalse="field Struct.OtherStructPtrField" + OtherStructPtrField *OtherStruct `json:"otherStructPtrField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.SliceField" + SliceField []string `json:"sliceField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.SliceTypedefField" + SliceTypedefField SliceType `json:"sliceTypedefField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.ByteArrayField" + ByteArrayField []byte `json:"byteArrayField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.MapField" + MapField map[string]string `json:"mapField"` + + // +k8s:required + // +k8s:validateFalse="field Struct.MapTypedefField" + MapTypedefField MapType `json:"mapTypedefField"` +} + +// +k8s:validateFalse="type StringType" +type StringType string + +// +k8s:validateFalse="type IntType" +type IntType int + +// +k8s:validateFalse="type OtherStruct" +type OtherStruct struct{} + +// +k8s:validateFalse="type SliceType" +type SliceType []string + +// +k8s:validateFalse="type MapType" +type MapType map[string]string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc_test.go new file mode 100644 index 0000000000..175a8c3fd3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/doc_test.go @@ -0,0 +1,127 @@ +/* +Copyright 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 required + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values (nil slices/maps). + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("stringField"), ""), + field.Required(field.NewPath("stringPtrField"), ""), + field.Required(field.NewPath("stringTypedefField"), ""), + field.Required(field.NewPath("stringTypedefPtrField"), ""), + field.Required(field.NewPath("intField"), ""), + field.Required(field.NewPath("intPtrField"), ""), + field.Required(field.NewPath("intTypedefField"), ""), + field.Required(field.NewPath("intTypedefPtrField"), ""), + field.Required(field.NewPath("boolField"), ""), + field.Required(field.NewPath("floatField"), ""), + field.Required(field.NewPath("byteField"), ""), + field.Required(field.NewPath("otherStructPtrField"), ""), + field.Required(field.NewPath("sliceField"), ""), + field.Required(field.NewPath("sliceTypedefField"), ""), + field.Required(field.NewPath("byteArrayField"), ""), + field.Required(field.NewPath("mapField"), ""), + field.Required(field.NewPath("mapTypedefField"), ""), + }) + + st.Value(&Struct{ + // Explicit zero-values and empty slices/maps. + StringField: "", + StringPtrField: nil, + StringTypedefField: "", + StringTypedefPtrField: nil, + IntField: 0, + IntPtrField: nil, + IntTypedefField: 0, + IntTypedefPtrField: nil, + BoolField: false, + FloatField: 0.0, + ByteField: 0, + OtherStructPtrField: nil, + SliceField: []string{}, + SliceTypedefField: SliceType{}, + ByteArrayField: []byte{}, + MapField: map[string]string{}, + MapTypedefField: MapType{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("stringField"), ""), + field.Required(field.NewPath("stringPtrField"), ""), + field.Required(field.NewPath("stringTypedefField"), ""), + field.Required(field.NewPath("stringTypedefPtrField"), ""), + field.Required(field.NewPath("intField"), ""), + field.Required(field.NewPath("intPtrField"), ""), + field.Required(field.NewPath("intTypedefField"), ""), + field.Required(field.NewPath("intTypedefPtrField"), ""), + field.Required(field.NewPath("boolField"), ""), + field.Required(field.NewPath("floatField"), ""), + field.Required(field.NewPath("byteField"), ""), + field.Required(field.NewPath("otherStructPtrField"), ""), + field.Required(field.NewPath("sliceField"), ""), + field.Required(field.NewPath("sliceTypedefField"), ""), + field.Required(field.NewPath("byteArrayField"), ""), + field.Required(field.NewPath("mapField"), ""), + field.Required(field.NewPath("mapTypedefField"), ""), + }) + + st.Value(&Struct{ + StringField: "abc", + StringPtrField: ptr.To("xyz"), + StringTypedefField: StringType("abc"), + StringTypedefPtrField: ptr.To(StringType("xyz")), + IntField: 123, + IntPtrField: ptr.To(456), + IntTypedefField: IntType(123), + IntTypedefPtrField: ptr.To(IntType(456)), + BoolField: true, + FloatField: 1.23, + ByteField: 'a', + OtherStructPtrField: &OtherStruct{}, + SliceField: []string{"a", "b"}, + SliceTypedefField: SliceType([]string{"a", "b"}), + ByteArrayField: []byte("abc"), + MapField: map[string]string{"a": "b", "c": "d"}, + MapTypedefField: MapType(map[string]string{"a": "b", "c": "d"}), + }).ExpectValidateFalseByPath(map[string][]string{ + "stringField": {"field Struct.StringField"}, + "stringPtrField": {"field Struct.StringPtrField"}, + "stringTypedefField": {"field Struct.StringTypedefField", "type StringType"}, + "stringTypedefPtrField": {"field Struct.StringTypedefPtrField", "type StringType"}, + "intField": {"field Struct.IntField"}, + "intPtrField": {"field Struct.IntPtrField"}, + "intTypedefField": {"field Struct.IntTypedefField", "type IntType"}, + "intTypedefPtrField": {"field Struct.IntTypedefPtrField", "type IntType"}, + "boolField": {"field Struct.BoolField"}, + "floatField": {"field Struct.FloatField"}, + "byteField": {"field Struct.ByteField"}, + "otherStructPtrField": {"type OtherStruct", "field Struct.OtherStructPtrField"}, + "sliceField": {"field Struct.SliceField"}, + "sliceTypedefField": {"field Struct.SliceTypedefField", "type SliceType"}, + "byteArrayField": {"field Struct.ByteArrayField"}, + "mapField": {"field Struct.MapField"}, + "mapTypedefField": {"field Struct.MapTypedefField", "type MapType"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/zz_generated.validations.go new file mode 100644 index 0000000000..67dc796f1e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/required/zz_generated.validations.go @@ -0,0 +1,691 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package required + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_IntType validates an instance of IntType according +// to declarative validation rules in the API schema. +func Validate_IntType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *IntType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type IntType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_MapType validates an instance of MapType according +// to declarative validation rules in the API schema. +func Validate_MapType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj MapType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type MapType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_OtherStruct validates an instance of OtherStruct according +// to declarative validation rules in the API schema. +func Validate_OtherStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *OtherStruct) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type OtherStruct"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_SliceType validates an instance of SliceType according +// to declarative validation rules in the API schema. +func Validate_SliceType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj SliceType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type SliceType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_StringType validates an instance of StringType according +// to declarative validation rules in the API schema. +func Validate_StringType( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StringType) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type StringType"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.StringPtrField + }) + errs = append(errs, fn(fldPath.Child("stringPtrField"), obj.StringPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_StringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return &oldObj.StringTypedefField + }) + errs = append(errs, fn(fldPath.Child("stringTypedefField"), &obj.StringTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.StringTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *StringType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringTypedefPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_StringType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *StringType { + return oldObj.StringTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("stringTypedefPtrField"), obj.StringTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.IntField + }) + errs = append(errs, fn(fldPath.Child("intField"), &obj.IntField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IntPtrField + }) + errs = append(errs, fn(fldPath.Child("intPtrField"), obj.IntPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return &oldObj.IntTypedefField + }) + errs = append(errs, fn(fldPath.Child("intTypedefField"), &obj.IntTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.IntTypedefPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *IntType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.IntTypedefPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_IntType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *IntType { + return oldObj.IntTypedefPtrField + }) + errs = append(errs, fn(fldPath.Child("intTypedefPtrField"), obj.IntTypedefPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.BoolField + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.BoolField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return &oldObj.BoolField + }) + errs = append(errs, fn(fldPath.Child("boolField"), &obj.BoolField, oldVal, oldObj != nil)...) + } + + { // field Struct.FloatField + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.FloatField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *float64 { + return &oldObj.FloatField + }) + errs = append(errs, fn(fldPath.Child("floatField"), &obj.FloatField, oldVal, oldObj != nil)...) + } + + { // field Struct.ByteField + fn := func( + fldPath *field.Path, + obj, oldObj *byte, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ByteField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *byte { + return &oldObj.ByteField + }) + errs = append(errs, fn(fldPath.Child("byteField"), &obj.ByteField, oldVal, oldObj != nil)...) + } + + { // field Struct.OtherStructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.OtherStructPtrField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_OtherStruct(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return oldObj.OtherStructPtrField + }) + errs = append(errs, fn(fldPath.Child("otherStructPtrField"), obj.OtherStructPtrField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceField + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.SliceField + }) + errs = append(errs, fn(fldPath.Child("sliceField"), obj.SliceField, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj SliceType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.SliceTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_SliceType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) SliceType { + return oldObj.SliceTypedefField + }) + errs = append(errs, fn(fldPath.Child("sliceTypedefField"), obj.SliceTypedefField, oldVal, oldObj != nil)...) + } + + { // field Struct.ByteArrayField + fn := func( + fldPath *field.Path, + obj, oldObj []byte, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.ByteArrayField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []byte { + return oldObj.ByteArrayField + }) + errs = append(errs, fn(fldPath.Child("byteArrayField"), obj.ByteArrayField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapField + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.MapField + }) + errs = append(errs, fn(fldPath.Child("mapField"), obj.MapField, oldVal, oldObj != nil)...) + } + + { // field Struct.MapTypedefField + fn := func( + fldPath *field.Path, + obj, oldObj MapType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.MapTypedefField"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_MapType(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) MapType { + return oldObj.MapTypedefField + }) + errs = append(errs, fn(fldPath.Child("mapTypedefField"), obj.MapTypedefField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/doc.go new file mode 100644 index 0000000000..f901f06897 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/doc.go @@ -0,0 +1,89 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package subfield contains test types for testing subfield field validation tags. +// +k8s:validation-gen-nolint +package deep + +// TODO: Uncomment the following code once the generated validation code can handle NPE by +// configuring optionality or requiredness for the intermediate fields. + +/** +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Struct demonstrates validations for subfield fields of structs. +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:subfield(structField)=+k8s:subfield(stringField)=+k8s:validateFalse="Struct.StructField.StructField 1" + // +k8s:subfield(structField)=+k8s:subfield(stringField)=+k8s:validateFalse="Struct.StructField.StructField 2" + // +k8s:subfield(sliceField)=+k8s:eachVal=+k8s:subfield(stringField)=+k8s:validateFalse="Struct.StructField.SliceField" + // +k8s:subfield(mapField)=+k8s:eachVal=+k8s:subfield(stringField)=+k8s:validateFalse="Struct.StructField.MapField" + StructField OtherStruct `json:"structField"` + + // +k8s:subfield(structField)=+k8s:subfield(stringField)=+k8s:validateFalse="Struct.StructPtrField.StructField 1" + // +k8s:subfield(structField)=+k8s:subfield(stringField)=+k8s:validateFalse="Struct.StructPtrField.StructField 2" + // +k8s:subfield(sliceField)=+k8s:eachVal=+k8s:subfield(stringField)=+k8s:validateFalse="Struct.StructPtrField.SliceField" + // +k8s:subfield(mapField)=+k8s:eachVal=+k8s:subfield(stringField)=+k8s:validateFalse="Struct.StructPtrField.MapField" + StructPtrField *OtherStruct `json:"structPtrField"` +} + +type OtherStruct struct { + StructField SmallStruct `json:"structField"` + SliceField []SmallStruct `json:"sliceField"` + MapField map[string]SmallStruct `json:"mapField"` +} + +type SmallStruct struct { + StringField string `json:"stringField"` +} + +type StructWithOptionalField struct { + // +k8s:optional + OptionalField string `json:"optionalField"` +} + +type SetByServerStruct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:subfield(optionalField)=+k8s:setByServer + SubfieldSetByServer StructWithOptionalField `json:"subfieldSetByServer"` + + // +k8s:subfield(stringField)=+k8s:setByServer + // +k8s:subfield(stringField)=+k8s:optional + EmbeddedSetByServer UnvalidatedStruct `json:"embeddedSetByServer"` + + // +k8s:subfield(optionalField)=+k8s:required + // +k8s:opaqueType + OpaqueSubfieldRequired StructWithOptionalField `json:"opaqueSubfieldRequired"` + + // +k8s:subfield(optionalField)=+k8s:required + SubfieldRequired StructWithOptionalField `json:"subfieldRequired"` + + // +k8s:optional + // +k8s:subfield(optionalField)=+k8s:setByServer + SubfieldPtrSetByServer *StructWithOptionalField `json:"subfieldPtrSetByServer"` + + // +k8s:optional + // +k8s:subfield(optionalField)=+k8s:required + SubfieldPtrRequired *StructWithOptionalField `json:"subfieldPtrRequired"` +} +**/ diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/zz_generated.validations.go new file mode 100644 index 0000000000..921f279859 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/deep/zz_generated.validations.go @@ -0,0 +1,22 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package deep diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/doc.go new file mode 100644 index 0000000000..a510c303e1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/doc.go @@ -0,0 +1,40 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package contains test types for testing subfield field validation tags. +// +k8s:validation-gen-nolint +package list + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:subfield(ownerReferences)=+k8s:listType=map + // +k8s:subfield(ownerReferences)=+k8s:listMapKey=uid + // +k8s:subfield(finalizers)=+k8s:listType=set + // +k8s:subfield(labels)=+k8s:eachKey=+k8s:validateFalse="labels key error" + // +k8s:opaqueType + metav1.ObjectMeta `json:"objectMeta"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/doc_test.go new file mode 100644 index 0000000000..9044c3221e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/doc_test.go @@ -0,0 +1,65 @@ +/* +Copyright 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 list + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestStructValidation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid case: empty ownerReferences, distinct finalizers, empty labels + st.Value(&Struct{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{"finalizer1", "finalizer2"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{}) + + // Invalid case: duplicate UIDs + st.Value(&Struct{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + {UID: "1", Name: "ref1"}, + {UID: "1", Name: "ref2"}, + }, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Duplicate(field.NewPath("objectMeta", "ownerReferences").Index(1), metav1.OwnerReference{UID: "1", Name: "ref2"}), + }) + + // Invalid case: duplicate finalizers + st.Value(&Struct{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{"finalizer1", "finalizer1"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Duplicate(field.NewPath("objectMeta", "finalizers").Index(1), "finalizer1"), + }) + + // Invalid case: non-empty labels trigger fixed failure + st.Value(&Struct{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"key1": "val1"}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + "objectMeta.labels": {"labels key error"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/zz_generated.validations.go new file mode 100644 index 0000000000..de5a185c66 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/list/zz_generated.validations.go @@ -0,0 +1,126 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package list + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.ObjectMeta + fn := func( + fldPath *field.Path, + obj, oldObj *v1.ObjectMeta, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "labels" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "labels", + func(o *v1.ObjectMeta) map[string]string { return o.Labels }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "labels key error") + }) + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "ownerReferences" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "ownerReferences", + func(o *v1.ObjectMeta) []v1.OwnerReference { return o.OwnerReferences }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []v1.OwnerReference) field.ErrorList { + return validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *v1.OwnerReference, b *v1.OwnerReference) bool { return a.UID == b.UID }) + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "finalizers" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "finalizers", + func(o *v1.ObjectMeta) []string { return o.Finalizers }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + return validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual) + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *v1.ObjectMeta { + return &oldObj.ObjectMeta + }) + errs = append(errs, fn(fldPath.Child("objectMeta"), &obj.ObjectMeta, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc.go new file mode 100644 index 0000000000..d1814e8b60 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc.go @@ -0,0 +1,35 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package nonincluded contains test types for testing subfield field validation tags. +// +k8s:validation-gen-nolint +package nonincluded + +import ( + "k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other" + "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + // +k8s:subfield(stringField)=+k8s:validateFalse="subfield Struct.(other.StructType).StringField" + // +k8s:opaqueType + other.StructType +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc_test.go new file mode 100644 index 0000000000..557452e8ef --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/doc_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2024 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 nonincluded + +import ( + "testing" +) + +func TestSubfieldObjectMetaValidationWithValidateFalse(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{}).ExpectValidateFalseByPath(map[string][]string{ + "other.StructType.stringField": {"subfield Struct.(other.StructType).StringField"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/zz_generated.validations.go new file mode 100644 index 0000000000..e515fa99e4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/nonincluded/zz_generated.validations.go @@ -0,0 +1,96 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package nonincluded + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + other "k8s.io/code-generator/cmd/validation-gen/output_tests/_codegenignore/other" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + { // field Struct.StructType + fn := func( + fldPath *field.Path, + obj, oldObj *other.StructType, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "stringField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *other.StructType) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.(other.StructType).StringField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *other.StructType { + return &oldObj.StructType + }) + errs = append(errs, fn(safe.Value(fldPath, func() *field.Path { return fldPath.Child("other.StructType") }), &obj.StructType, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc.go new file mode 100644 index 0000000000..107c92fd0f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc.go @@ -0,0 +1,59 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package subfield contains test types for testing subfield field validation tags. +// +k8s:validation-gen-nolint +package shallow + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Struct demonstrates validations for subfield fields of structs. +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:subfield(stringField)=+k8s:validateFalse="subfield Struct.StructField.StringField 1" + // +k8s:subfield(stringField)=+k8s:validateFalse="subfield Struct.StructField.StringField 2" + // +k8s:subfield(pointerField)=+k8s:validateFalse="subfield Struct.StructField.PointerField" + // +k8s:subfield(structField)=+k8s:validateFalse="subfield Struct.StructField.StructField" + // +k8s:subfield(sliceField)=+k8s:validateFalse="subfield Struct.StructField.SliceField" + // +k8s:subfield(mapField)=+k8s:validateFalse="subfield Struct.StructField.MapField" + StructField OtherStruct `json:"structField"` + + // +k8s:subfield(stringField)=+k8s:validateFalse="subfield Struct.StructPtrField.StringField 1" + // +k8s:subfield(stringField)=+k8s:validateFalse="subfield Struct.StructPtrField.StringField 2" + // +k8s:subfield(pointerField)=+k8s:validateFalse="subfield Struct.StructPtrField.PointerField" + // +k8s:subfield(structField)=+k8s:validateFalse="subfield Struct.StructPtrField.StructField" + // +k8s:subfield(sliceField)=+k8s:validateFalse="subfield Struct.StructPtrField.SliceField" + // +k8s:subfield(mapField)=+k8s:validateFalse="subfield Struct.StructPtrField.MapField" + StructPtrField *OtherStruct `json:"structPtrField"` +} + +type OtherStruct struct { + StringField string `json:"stringField"` + PointerField *string `json:"pointerField"` + StructField SmallStruct `json:"structField"` + SliceField []string `json:"sliceField"` + MapField map[string]string `json:"mapField"` +} + +type SmallStruct struct { + StringField string `json:"stringField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc_test.go new file mode 100644 index 0000000000..6bd1d93ea9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/doc_test.go @@ -0,0 +1,55 @@ +/* +Copyright 2024 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 shallow + +import ( + "testing" + + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + StructField: OtherStruct{ + StringField: "", + PointerField: ptr.To(""), + StructField: SmallStruct{}, + SliceField: []string{}, + MapField: map[string]string{}, + }, + StructPtrField: &OtherStruct{ + StringField: "", + PointerField: ptr.To(""), + StructField: SmallStruct{}, + SliceField: []string{}, + MapField: map[string]string{}, + }, + }).ExpectValidateFalseByPath(map[string][]string{ + "structField.stringField": {"subfield Struct.StructField.StringField 1", "subfield Struct.StructField.StringField 2"}, + "structField.pointerField": {"subfield Struct.StructField.PointerField"}, + "structField.structField": {"subfield Struct.StructField.StructField"}, + "structField.sliceField": {"subfield Struct.StructField.SliceField"}, + "structField.mapField": {"subfield Struct.StructField.MapField"}, + "structPtrField.stringField": {"subfield Struct.StructPtrField.StringField 1", "subfield Struct.StructPtrField.StringField 2"}, + "structPtrField.pointerField": {"subfield Struct.StructPtrField.PointerField"}, + "structPtrField.structField": {"subfield Struct.StructPtrField.StructField"}, + "structPtrField.sliceField": {"subfield Struct.StructPtrField.SliceField"}, + "structPtrField.mapField": {"subfield Struct.StructPtrField.MapField"}, + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/zz_generated.validations.go new file mode 100644 index 0000000000..d9cf158b01 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shallow/zz_generated.validations.go @@ -0,0 +1,219 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package shallow + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StructField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "stringField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *OtherStruct) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.StringField 1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *OtherStruct) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.StringField 2") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "pointerField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "pointerField", + func(o *OtherStruct) *string { return o.PointerField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.PointerField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "structField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", + func(o *OtherStruct) *SmallStruct { return &o.StructField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.StructField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "sliceField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", + func(o *OtherStruct) []string { return o.SliceField }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.SliceField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "mapField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", + func(o *OtherStruct) map[string]string { return o.MapField }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructField.MapField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return &oldObj.StructField + }) + errs = append(errs, fn(fldPath.Child("structField"), &obj.StructField, oldVal, oldObj != nil)...) + } + + { // field Struct.StructPtrField + fn := func( + fldPath *field.Path, + obj, oldObj *OtherStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "stringField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *OtherStruct) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.StringField 1") + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", + func(o *OtherStruct) *string { return &o.StringField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.StringField 2") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "pointerField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "pointerField", + func(o *OtherStruct) *string { return o.PointerField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.PointerField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "structField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "structField", + func(o *OtherStruct) *SmallStruct { return &o.StructField }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SmallStruct) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.StructField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "sliceField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "sliceField", + func(o *OtherStruct) []string { return o.SliceField }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.SliceField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + func() { // cohort = "mapField" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "mapField", + func(o *OtherStruct) map[string]string { return o.MapField }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield Struct.StructPtrField.MapField") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *OtherStruct { + return oldObj.StructPtrField + }) + errs = append(errs, fn(fldPath.Child("structPtrField"), obj.StructPtrField, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/doc.go new file mode 100644 index 0000000000..c5f2002b6a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/doc.go @@ -0,0 +1,171 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package shortcircuit contains test types for testing subfield short-circuit behavior. +// +k8s:validation-gen-nolint +package shortcircuit + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type TargetWithRequired struct { + // +k8s:required + Value *string `json:"value"` +} + +type TargetWithImmutable struct { + // +k8s:immutable + Value string `json:"value"` + Other string `json:"other"` +} + +type ParentWithRequired struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithRequired.Field.Value" + Field TargetWithRequired `json:"field"` +} + +type ParentWithImmutable struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithImmutable.Field.Value" + Field TargetWithImmutable `json:"field"` +} + +type ParentWithOpaqueField struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithOpaqueField.Field.Value" + // +k8s:opaqueType + Field TargetWithRequired `json:"field"` +} + +type ParentWithOpaqueImmutableField struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithOpaqueImmutableField.Field.Value" + // +k8s:opaqueType + Field TargetWithImmutable `json:"field"` +} + +type ParentWithAlphaOpaqueField struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithAlphaOpaqueField.Field.Value" + // +k8s:alpha=+k8s:opaqueType + Field TargetWithRequired `json:"field"` +} + +type ParentWithAlphaOpaqueImmutableField struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithAlphaOpaqueImmutableField.Field.Value" + // +k8s:alpha=+k8s:opaqueType + Field TargetWithImmutable `json:"field"` +} + +// +k8s:opaqueType +type AliasOpaqueTargetWithRequired TargetWithRequired + +type ParentWithOpaqueAlias struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithOpaqueAlias.Field.Value" + Field AliasOpaqueTargetWithRequired `json:"field"` +} + +type ParentWithPointerOpaqueAlias struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithPointerOpaqueAlias.Field.Value" + Field *AliasOpaqueTargetWithRequired `json:"field"` +} + +// +k8s:opaqueType +type AliasOpaqueTargetWithImmutable TargetWithImmutable + +type ParentWithOpaqueImmutableAlias struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithOpaqueImmutableAlias.Field.Value" + Field AliasOpaqueTargetWithImmutable `json:"field"` +} + +type ParentWithMultipleShortCircuit struct { + TypeMeta int `json:"typeMeta"` + // +k8s:required + // +k8s:subfield(value)=+k8s:immutable + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithMultipleShortCircuit.Field.Value" + Field *TargetWithRequired `json:"field"` +} + +type TargetWithOptional struct { + // +k8s:optional + Value *string `json:"value"` +} + +type ParentWithOptional struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithOptional.Field.Value" + Field TargetWithOptional `json:"field"` +} + +type ParentWithSubfieldRequiredAndChildOptional struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:required + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithSubfieldRequiredAndChildOptional.Field.Value" + Field TargetWithOptional `json:"field"` +} + +type TargetWithForbidden struct { + // +k8s:forbidden + Value *string `json:"value"` +} + +type ParentWithForbidden struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithForbidden.Field.Value" + Field TargetWithForbidden `json:"field"` +} + +type TargetWithUpdate struct { + // +k8s:update=NoModify + Value string `json:"value"` +} + +type ParentWithUpdate struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithUpdate.Field.Value" + Field TargetWithUpdate `json:"field"` +} + +type TargetWithMaxItems struct { + // +k8s:maxItems=2 + Value []string `json:"value"` +} + +type ParentWithMaxItems struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithMaxItems.Field.Value" + Field TargetWithMaxItems `json:"field"` +} + +type TargetWithMaxProperties struct { + // +k8s:maxProperties=2 + Value map[string]string `json:"value"` +} + +type ParentWithMaxProperties struct { + TypeMeta int `json:"typeMeta"` + // +k8s:subfield(value)=+k8s:validateFalse="subfield ParentWithMaxProperties.Field.Value" + Field TargetWithMaxProperties `json:"field"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/doc_test.go new file mode 100644 index 0000000000..06a60dee7a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/doc_test.go @@ -0,0 +1,448 @@ +/* +Copyright 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 shortcircuit + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestRequiredShortCircuit(t *testing.T) { + tests := []struct { + name string + value any + expectErrors field.ErrorList + }{ + { + name: "required field is nil, short circuits", + value: &ParentWithRequired{ + Field: TargetWithRequired{ + Value: nil, + }, + }, + expectErrors: field.ErrorList{ + field.Required(field.NewPath("field", "value"), ""), + }.MarkShortCircuit(), + }, + { + name: "required field is provided, subfield validation runs", + value: &ParentWithRequired{ + Field: TargetWithRequired{ + Value: new(""), + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithRequired.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "opaqueType on field, required check is not inherited, subfield validation runs", + value: &ParentWithOpaqueField{ + Field: TargetWithRequired{ + Value: nil, + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithOpaqueField.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "alpha opaqueType on field, required check is not inherited, subfield validation runs", + value: &ParentWithAlphaOpaqueField{ + Field: TargetWithRequired{ + Value: nil, + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithAlphaOpaqueField.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "opaqueType on alias, required check is not inherited, subfield validation runs", + value: &ParentWithOpaqueAlias{ + Field: AliasOpaqueTargetWithRequired{ + Value: nil, + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithOpaqueAlias.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "opaqueType on pointer alias, required check is not inherited, subfield validation runs", + value: &ParentWithPointerOpaqueAlias{ + Field: &AliasOpaqueTargetWithRequired{ + Value: nil, + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithPointerOpaqueAlias.Field.Value").WithOrigin("validateFalse"), + }, + }, + } + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailExact().MatchShortCircuit() + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + st := localSchemeBuilder.Test(t) + st.Value(tc.value).ExpectMatches(matcher, tc.expectErrors) + }) + } +} + +func TestImmutableShortCircuit(t *testing.T) { + tests := []struct { + name string + value any + oldValue any + expectErrors field.ErrorList + }{ + { + name: "immutable field changed on update, short circuits", + value: &ParentWithImmutable{ + Field: TargetWithImmutable{ + Value: "new", + }, + }, + oldValue: &ParentWithImmutable{ + Field: TargetWithImmutable{ + Value: "old", + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), "new", "field is immutable").WithOrigin("immutable"), + }.MarkShortCircuit(), + }, + { + name: "immutable field not validated on create, subfield validation runs", + value: &ParentWithImmutable{ + Field: TargetWithImmutable{ + Value: "new", + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithImmutable.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "opaqueType on field, immutable check is not inherited, subfield validation runs on update", + value: &ParentWithOpaqueImmutableField{ + Field: TargetWithImmutable{ + Value: "new", + }, + }, + oldValue: &ParentWithOpaqueImmutableField{ + Field: TargetWithImmutable{ + Value: "old", + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithOpaqueImmutableField.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "alpha opaqueType on field, immutable check is not inherited, subfield validation runs on update", + value: &ParentWithAlphaOpaqueImmutableField{ + Field: TargetWithImmutable{ + Value: "new", + }, + }, + oldValue: &ParentWithAlphaOpaqueImmutableField{ + Field: TargetWithImmutable{ + Value: "old", + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithAlphaOpaqueImmutableField.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "opaqueType on alias, immutable check is not inherited, subfield validation runs on update", + value: &ParentWithOpaqueImmutableAlias{ + Field: AliasOpaqueTargetWithImmutable{ + Value: "new", + }, + }, + oldValue: &ParentWithOpaqueImmutableAlias{ + Field: AliasOpaqueTargetWithImmutable{ + Value: "old", + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithOpaqueImmutableAlias.Field.Value").WithOrigin("validateFalse"), + }, + }, + } + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailExact().MatchShortCircuit() + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + st := localSchemeBuilder.Test(t) + tester := st.Value(tc.value) + if tc.oldValue != nil { + tester.OldValue(tc.oldValue) + } + tester.ExpectMatches(matcher, tc.expectErrors) + }) + } +} + +func TestMultipleShortCircuit(t *testing.T) { + tests := []struct { + name string + value any + oldValue any + expectErrors field.ErrorList + }{ + { + name: "required field is nil, short circuits at field level", + value: &ParentWithMultipleShortCircuit{ + Field: nil, + }, + expectErrors: field.ErrorList{ + field.Required(field.NewPath("field"), ""), + }.MarkShortCircuit(), + }, + { + name: "immutable subfield changed, short circuits at subfield level on update", + value: &ParentWithMultipleShortCircuit{ + Field: &TargetWithRequired{ + Value: new("new"), + }, + }, + oldValue: &ParentWithMultipleShortCircuit{ + Field: &TargetWithRequired{ + Value: new("old"), + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), "new", "field is immutable").WithOrigin("immutable"), + }.MarkShortCircuit(), + }, + { + name: "immutable subfield changed to nil on update, subfield immutable runs before inherited required", + value: &ParentWithMultipleShortCircuit{ + Field: &TargetWithRequired{ + Value: nil, + }, + }, + oldValue: &ParentWithMultipleShortCircuit{ + Field: &TargetWithRequired{ + Value: new("old"), + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "field is immutable").WithOrigin("immutable"), + field.Required(field.NewPath("field", "value"), ""), + }.MarkShortCircuit(), + }, + { + name: "field is not nil, subfield validation runs on create", + value: &ParentWithMultipleShortCircuit{ + Field: &TargetWithRequired{ + Value: new("val"), + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithMultipleShortCircuit.Field.Value").WithOrigin("validateFalse"), + }, + }, + } + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailExact().MatchShortCircuit() + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + st := localSchemeBuilder.Test(t) + tester := st.Value(tc.value) + if tc.oldValue != nil { + tester.OldValue(tc.oldValue) + } + tester.ExpectMatches(matcher, tc.expectErrors) + }) + } +} + +func TestOtherShortCircuits(t *testing.T) { + tests := []struct { + name string + value any + oldValue any + expectErrors field.ErrorList + }{ + // +k8s:optional + { + name: "optional field is nil, short circuits", + value: &ParentWithOptional{ + Field: TargetWithOptional{ + Value: nil, + }, + }, + expectErrors: nil, + }, + { + name: "optional field is provided, subfield validation runs", + value: &ParentWithOptional{ + Field: TargetWithOptional{ + Value: new(""), + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithOptional.Field.Value").WithOrigin("validateFalse"), + }, + }, + + // +k8s:required on subfield, +k8s:optional on child + { + name: "subfield required but value is nil, fails required validation and short circuits (optional child does not prevent it)", + value: &ParentWithSubfieldRequiredAndChildOptional{ + Field: TargetWithOptional{ + Value: nil, + }, + }, + expectErrors: field.ErrorList{ + field.Required(field.NewPath("field", "value"), ""), + }.MarkShortCircuit(), + }, + { + name: "subfield required and value is provided, runs non-short-circuit validation", + value: &ParentWithSubfieldRequiredAndChildOptional{ + Field: TargetWithOptional{ + Value: new("val"), + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithSubfieldRequiredAndChildOptional.Field.Value").WithOrigin("validateFalse"), + }, + }, + + // +k8s:forbidden + { + name: "forbidden field is nil, short circuits", + value: &ParentWithForbidden{ + Field: TargetWithForbidden{ + Value: nil, + }, + }, + expectErrors: nil, + }, + { + name: "forbidden field is provided, fails forbidden validation and short circuits", + value: &ParentWithForbidden{ + Field: TargetWithForbidden{ + Value: new(""), + }, + }, + expectErrors: field.ErrorList{ + field.Forbidden(field.NewPath("field", "value"), ""), + }.MarkShortCircuit(), + }, + + // +k8s:update + { + name: "update field on create, subfield validation runs", + value: &ParentWithUpdate{ + Field: TargetWithUpdate{ + Value: "any", + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithUpdate.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "update field modified on update, fails update validation and short circuits", + value: &ParentWithUpdate{ + Field: TargetWithUpdate{ + Value: "new", + }, + }, + oldValue: &ParentWithUpdate{ + Field: TargetWithUpdate{ + Value: "old", + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), "new", "field cannot be modified once set").WithOrigin("update"), + }.MarkShortCircuit(), + }, + + // +k8s:maxItems + { + name: "maxItems field within limit, subfield validation runs", + value: &ParentWithMaxItems{ + Field: TargetWithMaxItems{ + Value: []string{"a", "b"}, + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithMaxItems.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "maxItems field exceeds limit, fails maxItems validation and short circuits", + value: &ParentWithMaxItems{ + Field: TargetWithMaxItems{ + Value: []string{"a", "b", "c"}, + }, + }, + expectErrors: field.ErrorList{ + field.TooMany(field.NewPath("field", "value"), 3, 2).WithOrigin("maxItems"), + }.MarkShortCircuit(), + }, + + // +k8s:maxProperties + { + name: "maxProperties field within limit, subfield validation runs", + value: &ParentWithMaxProperties{ + Field: TargetWithMaxProperties{ + Value: map[string]string{"a": "1", "b": "2"}, + }, + }, + expectErrors: field.ErrorList{ + field.Invalid(field.NewPath("field", "value"), nil, "forced failure: subfield ParentWithMaxProperties.Field.Value").WithOrigin("validateFalse"), + }, + }, + { + name: "maxProperties field exceeds limit, fails maxProperties validation and short circuits", + value: &ParentWithMaxProperties{ + Field: TargetWithMaxProperties{ + Value: map[string]string{"a": "1", "b": "2", "c": "3"}, + }, + }, + expectErrors: field.ErrorList{ + field.TooMany(field.NewPath("field", "value"), 3, 2).WithOrigin("maxProperties"), + }.MarkShortCircuit(), + }, + } + + matcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().ByDetailExact().MatchShortCircuit() + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + st := localSchemeBuilder.Test(t) + tester := st.Value(tc.value) + if tc.oldValue != nil { + tester.OldValue(tc.oldValue) + } + tester.ExpectMatches(matcher, tc.expectErrors) + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/zz_generated.validations.go new file mode 100644 index 0000000000..2c0282191a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/shortcircuit/zz_generated.validations.go @@ -0,0 +1,1335 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package shortcircuit + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type ParentWithAlphaOpaqueField + scheme.AddValidationFunc( + (*ParentWithAlphaOpaqueField)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithAlphaOpaqueField( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithAlphaOpaqueField), + safe.Cast[*ParentWithAlphaOpaqueField](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithAlphaOpaqueImmutableField + scheme.AddValidationFunc( + (*ParentWithAlphaOpaqueImmutableField)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithAlphaOpaqueImmutableField( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithAlphaOpaqueImmutableField), + safe.Cast[*ParentWithAlphaOpaqueImmutableField](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithForbidden + scheme.AddValidationFunc( + (*ParentWithForbidden)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithForbidden( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithForbidden), + safe.Cast[*ParentWithForbidden](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithImmutable + scheme.AddValidationFunc( + (*ParentWithImmutable)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithImmutable( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithImmutable), + safe.Cast[*ParentWithImmutable](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithMaxItems + scheme.AddValidationFunc( + (*ParentWithMaxItems)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithMaxItems( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithMaxItems), + safe.Cast[*ParentWithMaxItems](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithMaxProperties + scheme.AddValidationFunc( + (*ParentWithMaxProperties)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithMaxProperties( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithMaxProperties), + safe.Cast[*ParentWithMaxProperties](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithMultipleShortCircuit + scheme.AddValidationFunc( + (*ParentWithMultipleShortCircuit)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithMultipleShortCircuit( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithMultipleShortCircuit), + safe.Cast[*ParentWithMultipleShortCircuit](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithOpaqueAlias + scheme.AddValidationFunc( + (*ParentWithOpaqueAlias)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithOpaqueAlias( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithOpaqueAlias), + safe.Cast[*ParentWithOpaqueAlias](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithOpaqueField + scheme.AddValidationFunc( + (*ParentWithOpaqueField)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithOpaqueField( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithOpaqueField), + safe.Cast[*ParentWithOpaqueField](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithOpaqueImmutableAlias + scheme.AddValidationFunc( + (*ParentWithOpaqueImmutableAlias)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithOpaqueImmutableAlias( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithOpaqueImmutableAlias), + safe.Cast[*ParentWithOpaqueImmutableAlias](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithOpaqueImmutableField + scheme.AddValidationFunc( + (*ParentWithOpaqueImmutableField)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithOpaqueImmutableField( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithOpaqueImmutableField), + safe.Cast[*ParentWithOpaqueImmutableField](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithOptional + scheme.AddValidationFunc( + (*ParentWithOptional)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithOptional( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithOptional), + safe.Cast[*ParentWithOptional](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithPointerOpaqueAlias + scheme.AddValidationFunc( + (*ParentWithPointerOpaqueAlias)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithPointerOpaqueAlias( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithPointerOpaqueAlias), + safe.Cast[*ParentWithPointerOpaqueAlias](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithRequired + scheme.AddValidationFunc( + (*ParentWithRequired)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithRequired( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithRequired), + safe.Cast[*ParentWithRequired](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithSubfieldRequiredAndChildOptional + scheme.AddValidationFunc( + (*ParentWithSubfieldRequiredAndChildOptional)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithSubfieldRequiredAndChildOptional( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithSubfieldRequiredAndChildOptional), + safe.Cast[*ParentWithSubfieldRequiredAndChildOptional](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type ParentWithUpdate + scheme.AddValidationFunc( + (*ParentWithUpdate)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_ParentWithUpdate( + ctx, op, nil, /* fldPath */ + obj.(*ParentWithUpdate), + safe.Cast[*ParentWithUpdate](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_ParentWithAlphaOpaqueField validates an instance of ParentWithAlphaOpaqueField according +// to declarative validation rules in the API schema. +func Validate_ParentWithAlphaOpaqueField( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithAlphaOpaqueField) (errs field.ErrorList) { + + // field ParentWithAlphaOpaqueField.TypeMeta has no validation + + { // field ParentWithAlphaOpaqueField.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithRequired, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithRequired) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithAlphaOpaqueField.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithAlphaOpaqueField) *TargetWithRequired { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithAlphaOpaqueImmutableField validates an instance of ParentWithAlphaOpaqueImmutableField according +// to declarative validation rules in the API schema. +func Validate_ParentWithAlphaOpaqueImmutableField( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithAlphaOpaqueImmutableField) (errs field.ErrorList) { + + // field ParentWithAlphaOpaqueImmutableField.TypeMeta has no validation + + { // field ParentWithAlphaOpaqueImmutableField.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithImmutable, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithImmutable) *string { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithAlphaOpaqueImmutableField.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithAlphaOpaqueImmutableField) *TargetWithImmutable { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithForbidden validates an instance of ParentWithForbidden according +// to declarative validation rules in the API schema. +func Validate_ParentWithForbidden( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithForbidden) (errs field.ErrorList) { + + // field ParentWithForbidden.TypeMeta has no validation + + { // field ParentWithForbidden.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithForbidden, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithForbidden) *string { return o.Value }, validate.DirectEqual, validate.ForbiddenPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithForbidden) *string { return o.Value }, validate.DirectEqual, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithForbidden) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithForbidden.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithForbidden(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithForbidden) *TargetWithForbidden { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithImmutable validates an instance of ParentWithImmutable according +// to declarative validation rules in the API schema. +func Validate_ParentWithImmutable( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithImmutable) (errs field.ErrorList) { + + // field ParentWithImmutable.TypeMeta has no validation + + { // field ParentWithImmutable.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithImmutable, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithImmutable) *string { return &o.Value }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithImmutable) *string { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithImmutable.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithImmutable(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithImmutable) *TargetWithImmutable { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithMaxItems validates an instance of ParentWithMaxItems according +// to declarative validation rules in the API schema. +func Validate_ParentWithMaxItems( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithMaxItems) (errs field.ErrorList) { + + // field ParentWithMaxItems.TypeMeta has no validation + + { // field ParentWithMaxItems.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithMaxItems, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithMaxItems) []string { return o.Value }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + return validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2) + }).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithMaxItems) []string { return o.Value }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithMaxItems.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithMaxItems(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithMaxItems) *TargetWithMaxItems { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithMaxProperties validates an instance of ParentWithMaxProperties according +// to declarative validation rules in the API schema. +func Validate_ParentWithMaxProperties( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithMaxProperties) (errs field.ErrorList) { + + // field ParentWithMaxProperties.TypeMeta has no validation + + { // field ParentWithMaxProperties.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithMaxProperties, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithMaxProperties) map[string]string { return o.Value }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.MaxProperties(ctx, op, fldPath, obj, oldObj, 2) + }).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithMaxProperties) map[string]string { return o.Value }, deepEqualImpl_, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithMaxProperties.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithMaxProperties(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithMaxProperties) *TargetWithMaxProperties { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithMultipleShortCircuit validates an instance of ParentWithMultipleShortCircuit according +// to declarative validation rules in the API schema. +func Validate_ParentWithMultipleShortCircuit( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithMultipleShortCircuit) (errs field.ErrorList) { + + // field ParentWithMultipleShortCircuit.TypeMeta has no validation + + { // field ParentWithMultipleShortCircuit.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithRequired, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithRequired) *string { return o.Value }, validate.DirectEqual, validate.Immutable).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithRequired) *string { return o.Value }, validate.DirectEqual, validate.RequiredPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithRequired) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithMultipleShortCircuit.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithRequired(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithMultipleShortCircuit) *TargetWithRequired { + return oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithOpaqueAlias validates an instance of ParentWithOpaqueAlias according +// to declarative validation rules in the API schema. +func Validate_ParentWithOpaqueAlias( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithOpaqueAlias) (errs field.ErrorList) { + + // field ParentWithOpaqueAlias.TypeMeta has no validation + + { // field ParentWithOpaqueAlias.Field + fn := func( + fldPath *field.Path, + obj, oldObj *AliasOpaqueTargetWithRequired, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *AliasOpaqueTargetWithRequired) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithOpaqueAlias.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithOpaqueAlias) *AliasOpaqueTargetWithRequired { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithOpaqueField validates an instance of ParentWithOpaqueField according +// to declarative validation rules in the API schema. +func Validate_ParentWithOpaqueField( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithOpaqueField) (errs field.ErrorList) { + + // field ParentWithOpaqueField.TypeMeta has no validation + + { // field ParentWithOpaqueField.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithRequired, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithRequired) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithOpaqueField.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithOpaqueField) *TargetWithRequired { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithOpaqueImmutableAlias validates an instance of ParentWithOpaqueImmutableAlias according +// to declarative validation rules in the API schema. +func Validate_ParentWithOpaqueImmutableAlias( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithOpaqueImmutableAlias) (errs field.ErrorList) { + + // field ParentWithOpaqueImmutableAlias.TypeMeta has no validation + + { // field ParentWithOpaqueImmutableAlias.Field + fn := func( + fldPath *field.Path, + obj, oldObj *AliasOpaqueTargetWithImmutable, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *AliasOpaqueTargetWithImmutable) *string { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithOpaqueImmutableAlias.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithOpaqueImmutableAlias) *AliasOpaqueTargetWithImmutable { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithOpaqueImmutableField validates an instance of ParentWithOpaqueImmutableField according +// to declarative validation rules in the API schema. +func Validate_ParentWithOpaqueImmutableField( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithOpaqueImmutableField) (errs field.ErrorList) { + + // field ParentWithOpaqueImmutableField.TypeMeta has no validation + + { // field ParentWithOpaqueImmutableField.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithImmutable, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithImmutable) *string { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithOpaqueImmutableField.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithOpaqueImmutableField) *TargetWithImmutable { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithOptional validates an instance of ParentWithOptional according +// to declarative validation rules in the API schema. +func Validate_ParentWithOptional( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithOptional) (errs field.ErrorList) { + + // field ParentWithOptional.TypeMeta has no validation + + { // field ParentWithOptional.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithOptional, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithOptional) *string { return o.Value }, validate.DirectEqual, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithOptional) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithOptional.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithOptional(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithOptional) *TargetWithOptional { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithPointerOpaqueAlias validates an instance of ParentWithPointerOpaqueAlias according +// to declarative validation rules in the API schema. +func Validate_ParentWithPointerOpaqueAlias( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithPointerOpaqueAlias) (errs field.ErrorList) { + + // field ParentWithPointerOpaqueAlias.TypeMeta has no validation + + { // field ParentWithPointerOpaqueAlias.Field + fn := func( + fldPath *field.Path, + obj, oldObj *AliasOpaqueTargetWithRequired, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *AliasOpaqueTargetWithRequired) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithPointerOpaqueAlias.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithPointerOpaqueAlias) *AliasOpaqueTargetWithRequired { + return oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithRequired validates an instance of ParentWithRequired according +// to declarative validation rules in the API schema. +func Validate_ParentWithRequired( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithRequired) (errs field.ErrorList) { + + // field ParentWithRequired.TypeMeta has no validation + + { // field ParentWithRequired.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithRequired, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithRequired) *string { return o.Value }, validate.DirectEqual, validate.RequiredPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithRequired) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithRequired.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithRequired(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithRequired) *TargetWithRequired { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithSubfieldRequiredAndChildOptional validates an instance of ParentWithSubfieldRequiredAndChildOptional according +// to declarative validation rules in the API schema. +func Validate_ParentWithSubfieldRequiredAndChildOptional( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithSubfieldRequiredAndChildOptional) (errs field.ErrorList) { + + // field ParentWithSubfieldRequiredAndChildOptional.TypeMeta has no validation + + { // field ParentWithSubfieldRequiredAndChildOptional.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithOptional, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithOptional) *string { return o.Value }, validate.DirectEqual, validate.RequiredPointer).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithOptional) *string { return o.Value }, validate.DirectEqual, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithOptional) *string { return o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithSubfieldRequiredAndChildOptional.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithOptional(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithSubfieldRequiredAndChildOptional) *TargetWithOptional { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_ParentWithUpdate validates an instance of ParentWithUpdate according +// to declarative validation rules in the API schema. +func Validate_ParentWithUpdate( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ParentWithUpdate) (errs field.ErrorList) { + + // field ParentWithUpdate.TypeMeta has no validation + + { // field ParentWithUpdate.Field + fn := func( + fldPath *field.Path, + obj, oldObj *TargetWithUpdate, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + func() { // cohort = "value" + earlyReturn := false + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithUpdate) *string { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoModify) + }).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value", + func(o *TargetWithUpdate) *string { return &o.Value }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "subfield ParentWithUpdate.Field.Value") + }); len(e) != 0 { + errs = append(errs, e...) + } + }() + // call the type's validation function + errs = append(errs, Validate_TargetWithUpdate(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ParentWithUpdate) *TargetWithUpdate { + return &oldObj.Field + }) + errs = append(errs, fn(fldPath.Child("field"), &obj.Field, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TargetWithForbidden validates an instance of TargetWithForbidden according +// to declarative validation rules in the API schema. +func Validate_TargetWithForbidden( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *TargetWithForbidden) (errs field.ErrorList) { + + { // field TargetWithForbidden.Value + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TargetWithForbidden) *string { + return oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), obj.Value, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TargetWithImmutable validates an instance of TargetWithImmutable according +// to declarative validation rules in the API schema. +func Validate_TargetWithImmutable( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *TargetWithImmutable) (errs field.ErrorList) { + + { // field TargetWithImmutable.Value + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TargetWithImmutable) *string { + return &oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), &obj.Value, oldVal, oldObj != nil)...) + } + + // field TargetWithImmutable.Other has no validation + return errs +} + +// Validate_TargetWithMaxItems validates an instance of TargetWithMaxItems according +// to declarative validation rules in the API schema. +func Validate_TargetWithMaxItems( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *TargetWithMaxItems) (errs field.ErrorList) { + + { // field TargetWithMaxItems.Value + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 2).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TargetWithMaxItems) []string { + return oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), obj.Value, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TargetWithMaxProperties validates an instance of TargetWithMaxProperties according +// to declarative validation rules in the API schema. +func Validate_TargetWithMaxProperties( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *TargetWithMaxProperties) (errs field.ErrorList) { + + { // field TargetWithMaxProperties.Value + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxProperties(ctx, op, fldPath, obj, oldObj, 2).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TargetWithMaxProperties) map[string]string { + return oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), obj.Value, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TargetWithOptional validates an instance of TargetWithOptional according +// to declarative validation rules in the API schema. +func Validate_TargetWithOptional( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *TargetWithOptional) (errs field.ErrorList) { + + { // field TargetWithOptional.Value + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TargetWithOptional) *string { + return oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), obj.Value, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TargetWithRequired validates an instance of TargetWithRequired according +// to declarative validation rules in the API schema. +func Validate_TargetWithRequired( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *TargetWithRequired) (errs field.ErrorList) { + + { // field TargetWithRequired.Value + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TargetWithRequired) *string { + return oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), obj.Value, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_TargetWithUpdate validates an instance of TargetWithUpdate according +// to declarative validation rules in the API schema. +func Validate_TargetWithUpdate( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *TargetWithUpdate) (errs field.ErrorList) { + + { // field TargetWithUpdate.Value + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *TargetWithUpdate) *string { + return &oldObj.Value + }) + errs = append(errs, fn(fldPath.Child("value"), &obj.Value, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/doc.go new file mode 100644 index 0000000000..82a21b8b90 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/doc.go @@ -0,0 +1,43 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package unions contains test types for testing subfield union validation tags. +// +k8s:validation-gen-nolint +package unions + +import ( + "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int `json:"typeMeta"` + + // +k8s:subfield(d)=+k8s:unionDiscriminator + // +k8s:subfield(m1)=+k8s:unionMember + // +k8s:subfield(m2)=+k8s:unionMember + Subfield SubStruct `json:"subfield"` +} + +type SubStruct struct { + D string `json:"d"` + M1 *int `json:"m1,omitempty"` + M2 *int `json:"m2,omitempty"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/doc_test.go new file mode 100644 index 0000000000..6a2a9eb3b6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/doc_test.go @@ -0,0 +1,66 @@ +/* +Copyright 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 unions + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestStructValidation(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Valid case: D is M1, M1 is set + st.Value(&Struct{ + Subfield: SubStruct{ + D: "M1", + M1: ptr.To(1), + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{}) + + // Valid case: D is M2, M2 is set + st.Value(&Struct{ + Subfield: SubStruct{ + D: "M2", + M2: ptr.To(1), + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{}) + + // Invalid case: D is M1, but M2 is set + st.Value(&Struct{ + Subfield: SubStruct{ + D: "M1", + M2: ptr.To(1), + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("subfield", "m1"), "", "must be specified when `d` is \"M1\"").WithOrigin("union"), + field.Invalid(field.NewPath("subfield", "m2"), "", "may only be specified when `d` is \"M2\"").WithOrigin("union"), + }) + + // Invalid case: D is M1, and BOTH are set + st.Value(&Struct{ + Subfield: SubStruct{ + D: "M1", + M1: ptr.To(1), + M2: ptr.To(1), + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("subfield", "m2"), "", "may only be specified when `d` is \"M2\"").WithOrigin("union"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/zz_generated.validations.go new file mode 100644 index 0000000000..9362b007c9 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/subfield/unions/zz_generated.validations.go @@ -0,0 +1,112 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package unions + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_subfield_unions_Struct_subfield_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "M1"), validate.NewDiscriminatedUnionMember("m2", "M2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.Subfield + fn := func( + fldPath *field.Path, + obj, oldObj *SubStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_subfield_unions_Struct_subfield_, + func(obj *SubStruct) string { + if obj == nil { + return "" + } + return string(obj.D) + }, + func(obj *SubStruct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, + func(obj *SubStruct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *SubStruct { + return &oldObj.Subfield + }) + errs = append(errs, fn(fldPath.Child("subfield"), &obj.Subfield, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc.go new file mode 100644 index 0000000000..df6c6a5519 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc.go @@ -0,0 +1,37 @@ +/* +Copyright 2024 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. +*/ + +// Note: this selects all types in the package. +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package issubresource + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Root resource is supported by default + +// +k8s:isSubresource="/scale" + +// T1 is a test type +type T1 struct { + // +k8s:validateTrue="field T1.S" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc_test.go new file mode 100644 index 0000000000..0dbe831864 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/doc_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 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 issubresource + +import ( + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestRegisterValidations(t *testing.T) { + st := localSchemeBuilder.Test(t) + + t1 := &T1{} + st.Value(t1).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.InternalError(nil, fmt.Errorf("")), + }) + + st.Value(t1).Subresources([]string{"scale"}).ExpectValid() + + st.Value(t1).Subresources([]string{"unknown"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.InternalError(nil, fmt.Errorf("")), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/zz_generated.validations.go new file mode 100644 index 0000000000..ef2be6dc5f --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/issubresource/zz_generated.validations.go @@ -0,0 +1,89 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package issubresource + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/scale": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc.go new file mode 100644 index 0000000000..ee0d2c610a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc.go @@ -0,0 +1,37 @@ +/* +Copyright 2024 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. +*/ + +// Note: this selects all types in the package. +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package root + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// This tests that without any +k8s:supportsSubresource or +k8s:isSubresource tags, +// that the validation of both the root resource is allowed and validation of all +// subresources fails with an error. + +// T1 is a test type +type T1 struct { + // +k8s:validateTrue="field T1.S" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc_test.go new file mode 100644 index 0000000000..cceeda6dca --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/doc_test.go @@ -0,0 +1,40 @@ +/* +Copyright 2024 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 root + +import ( + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestRegisterValidations(t *testing.T) { + st := localSchemeBuilder.Test(t) + + t1 := &T1{} + + st.Value(t1).ExpectValid() + + st.Value(t1).Subresources([]string{"scale"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.InternalError(nil, fmt.Errorf("")), + }) + + st.Value(t1).Subresources([]string{"x", "y"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.InternalError(nil, fmt.Errorf("")), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/zz_generated.validations.go new file mode 100644 index 0000000000..b7755f0702 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/root/zz_generated.validations.go @@ -0,0 +1,89 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package root + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc.go new file mode 100644 index 0000000000..5b4289316d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 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. +*/ + +// Note: this selects all types in the package. +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package subresource + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Root resource is supported by default + +// +k8s:supportsSubresource="/status" +// +k8s:supportsSubresource="/scale" +// +k8s:supportsSubresource="/x/y" + +// T1 is a test type +type T1 struct { + // +k8s:validateTrue="field T1.S" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc_test.go new file mode 100644 index 0000000000..8bf631a70e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/doc_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2024 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 subresource + +import ( + "fmt" + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestRegisterValidations(t *testing.T) { + st := localSchemeBuilder.Test(t) + + t1 := &T1{} + + st.Value(t1).ExpectValid() + + st.Value(t1).Subresources([]string{"status"}).ExpectValid() + st.Value(t1).Subresources([]string{"scale"}).ExpectValid() + st.Value(t1).Subresources([]string{"x", "y"}).ExpectValid() + + st.Value(t1).Subresources([]string{"status", "unknown"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.InternalError(nil, fmt.Errorf("")), + }) + + st.Value(t1).Subresources([]string{"unknown"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.InternalError(nil, fmt.Errorf("")), + }) + st.Value(t1).Subresources([]string{"x", "unknown"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.InternalError(nil, fmt.Errorf("")), + }) + st.Value(t1).Subresources([]string{"x", "y", "unknown"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.InternalError(nil, fmt.Errorf("")), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/zz_generated.validations.go new file mode 100644 index 0000000000..8b847e8abc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/supported_resources/subresource/zz_generated.validations.go @@ -0,0 +1,89 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package subresource + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/", "/scale", "/status", "/x/y": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field T1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc.go new file mode 100644 index 0000000000..3de417aef6 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package custommembers + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Discriminated union with custom member names +type Struct struct { + TypeMeta int + + // +k8s:unionDiscriminator + D D `json:"d"` + + // +k8s:unionMember(memberName: "CustomM1") + // +k8s:optional + M1 *M1 `json:"m1"` + + // +k8s:unionMember(memberName: "CustomM2") + // +k8s:optional + M2 *M2 `json:"m2"` +} + +type D string + +const ( + DM1 D = "CustomM1" + DM2 D = "CustomM2" +) + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc_test.go new file mode 100644 index 0000000000..ce7fe0425c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/doc_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 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 custommembers + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{D: DM1, M1: &M1{}}).ExpectValid() + st.Value(&Struct{D: DM2, M2: &M2{}}).ExpectValid() + + st.Value(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "may only be specified when"), + }.WithOrigin("union")) + + st.Value(&Struct{D: DM1}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Test validation ratcheting + st.Value(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).OldValue(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).ExpectValid() + st.Value(&Struct{D: DM1}).OldValue(&Struct{D: DM1}).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/zz_generated.validations.go new file mode 100644 index 0000000000..761bcaf83d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/custom_members/zz_generated.validations.go @@ -0,0 +1,148 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package custommembers + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_custom_members_Struct_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "CustomM1"), validate.NewDiscriminatedUnionMember("m2", "CustomM2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_custom_members_Struct_, + func(obj *Struct) string { + if obj == nil { + return "" + } + return string(obj.D) + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.D has no validation + + { // field Struct.M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.M1 + }) + errs = append(errs, fn(fldPath.Child("m1"), obj.M1, oldVal, oldObj != nil)...) + } + + { // field Struct.M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.M2 + }) + errs = append(errs, fn(fldPath.Child("m2"), obj.M2, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc.go new file mode 100644 index 0000000000..7fdcdd06c1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package empty + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Empty discriminated union +type Struct struct { + TypeMeta int + + // +k8s:unionDiscriminator + D D `json:"d"` +} + +type D string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc_test.go new file mode 100644 index 0000000000..55d5877675 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/doc_test.go @@ -0,0 +1,31 @@ +/* +Copyright 2024 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 empty + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Unions discriminators may be optional. + st.Value(&Struct{D: D("")}).ExpectValid() + + // Unions discriminators Should be validated for valid values. + st.Value(&Struct{D: D("Unknown")}).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/zz_generated.validations.go new file mode 100644 index 0000000000..bb569cc741 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/empty/zz_generated.validations.go @@ -0,0 +1,22 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package empty diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc.go new file mode 100644 index 0000000000..0aedae327d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc.go @@ -0,0 +1,66 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package multiple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Two discriminated unions in the same struct +type Struct struct { + TypeMeta int + + // +k8s:unionDiscriminator(union: "union1") + D1 D `json:"d1"` + + // +k8s:unionMember(union: "union1") + // +k8s:optional + U1M1 *M1 `json:"u1m1"` + + // +k8s:unionMember(union: "union1") + // +k8s:optional + U1M2 *M2 `json:"u1m2"` + + // +k8s:unionDiscriminator(union: "union2") + D2 D `json:"d2"` + + // +k8s:unionMember(union: "union2") + // +k8s:optional + U2M1 *M1 `json:"u2m1"` + + // +k8s:unionMember(union: "union2") + // +k8s:optional + U2M2 *M2 `json:"u2m2"` +} + +type D string + +const ( + U1M1 D = "U1M1" + U1M2 D = "U1M2" + U2M1 D = "U2M1" + U2M2 D = "U2M2" +) + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc_test.go new file mode 100644 index 0000000000..3cb1e0c404 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/doc_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2024 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 multiple + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{}).ExpectValid() + + st.Value(&Struct{ + D1: U1M1, U1M1: &M1{}, + D2: U2M1, U2M1: &M1{}, + }).ExpectValid() + + st.Value(&Struct{ + D1: U1M2, U1M2: &M2{}, + D2: U2M2, U2M2: &M2{}, + }).ExpectValid() + + st.Value(&Struct{ + D1: U1M2, U1M1: &M1{}, U1M2: &M2{}, + D2: U2M2, // no value + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("u1m1"), nil, "may only be specified when"), + field.Invalid(field.NewPath("u2m2"), nil, "must be specified when"), + }.WithOrigin("union")) + + st.Value(&Struct{ + D1: U1M2, // no value + D2: U2M2, U2M1: &M1{}, U2M2: &M2{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("u1m2"), nil, "must be specified when"), + field.Invalid(field.NewPath("u2m1"), nil, "may only be specified when"), + }.WithOrigin("union")) + + st.Value(&Struct{ + D1: U1M2, // no value + D2: U2M2, // no value + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("u1m2"), nil, "must be specified when"), + field.Invalid(field.NewPath("u2m2"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Test validation ratcheting + st.Value(&Struct{ + D1: U1M2, U1M1: &M1{}, U1M2: &M2{}, + D2: U2M2, // no value + }).OldValue(&Struct{ + D1: U1M2, U1M1: &M1{}, U1M2: &M2{}, + D2: U2M2, // no value + }).ExpectValid() + + st.Value(&Struct{ + D1: U1M2, // no value + D2: U2M2, U2M1: &M1{}, U2M2: &M2{}, + }).OldValue(&Struct{ + D1: U1M2, // no value + D2: U2M2, U2M1: &M1{}, U2M2: &M2{}, + }).ExpectValid() + + st.Value(&Struct{ + D1: U1M2, // no value + D2: U2M2, // no value + }).OldValue(&Struct{ + D1: U1M2, // no value + D2: U2M2, // no value + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/zz_generated.validations.go new file mode 100644 index 0000000000..6833e3b387 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/multiple/zz_generated.validations.go @@ -0,0 +1,228 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiple + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_multiple_Struct_union1 = validate.NewDiscriminatedUnionMembership("d1", validate.NewDiscriminatedUnionMember("u1m1", "U1M1"), validate.NewDiscriminatedUnionMember("u1m2", "U1M2")) +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_multiple_Struct_union2 = validate.NewDiscriminatedUnionMembership("d2", validate.NewDiscriminatedUnionMember("u2m1", "U2M1"), validate.NewDiscriminatedUnionMember("u2m2", "U2M2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_multiple_Struct_union1, + func(obj *Struct) string { + if obj == nil { + return "" + } + return string(obj.D1) + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U1M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U1M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_multiple_Struct_union2, + func(obj *Struct) string { + if obj == nil { + return "" + } + return string(obj.D2) + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U2M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U2M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.D1 has no validation + + { // field Struct.U1M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.U1M1 + }) + errs = append(errs, fn(fldPath.Child("u1m1"), obj.U1M1, oldVal, oldObj != nil)...) + } + + { // field Struct.U1M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.U1M2 + }) + errs = append(errs, fn(fldPath.Child("u1m2"), obj.U1M2, oldVal, oldObj != nil)...) + } + + // field Struct.D2 has no validation + + { // field Struct.U2M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.U2M1 + }) + errs = append(errs, fn(fldPath.Child("u2m1"), obj.U2M1, oldVal, oldObj != nil)...) + } + + { // field Struct.U2M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.U2M2 + }) + errs = append(errs, fn(fldPath.Child("u2m2"), obj.U2M2, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc.go new file mode 100644 index 0000000000..2df20ea245 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc.go @@ -0,0 +1,63 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package simple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Discriminated union +type Struct struct { + TypeMeta int + + // +k8s:unionDiscriminator + D D `json:"d"` + + // +k8s:unionMember + // +k8s:optional + M1 *M1 `json:"m1"` + + // +k8s:unionMember + // +k8s:optional + M2 *M2 `json:"m2"` + + // +k8s:unionMember + // +k8s:optional + M3 []string `json:"m3"` + + // +k8s:unionMember + // +k8s:optional + M4 map[string]string `json:"m4"` +} + +type D string + +const ( + DM1 D = "M1" + DM2 D = "M2" + DM3 D = "M3" + DM4 D = "M4" +) + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc_test.go new file mode 100644 index 0000000000..5adfa7f0c8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/doc_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2024 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 simple + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ /* zero values */ }).ExpectValid() + + st.Value(&Struct{D: DM1, M1: &M1{}}).ExpectValid() + st.Value(&Struct{D: DM2, M2: &M2{}}).ExpectValid() + st.Value(&Struct{D: DM3, M3: []string{"a"}}).ExpectValid() + st.Value(&Struct{D: DM4, M4: map[string]string{"k": "v"}}).ExpectValid() + + st.Value(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "may only be specified when"), + }.WithOrigin("union")) + + st.Value(&Struct{D: DM1}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Slice member: discriminator matches but slice is nil + st.Value(&Struct{D: DM3}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m3"), nil, "must be specified when"), + }.WithOrigin("union")) + // Slice member: discriminator matches but slice is empty (len==0, not nil) + st.Value(&Struct{D: DM3, M3: []string{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m3"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Slice member: discriminator doesn't match but slice is set + st.Value(&Struct{D: DM1, M1: &M1{}, M3: []string{"a"}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m3"), nil, "may only be specified when"), + }.WithOrigin("union")) + + // Map member: discriminator matches but map is nil + st.Value(&Struct{D: DM4}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m4"), nil, "must be specified when"), + }.WithOrigin("union")) + // Map member: discriminator matches but map is empty (len==0, not nil) + st.Value(&Struct{D: DM4, M4: map[string]string{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m4"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Map member: discriminator doesn't match but map is set + st.Value(&Struct{D: DM1, M1: &M1{}, M4: map[string]string{"k": "v"}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m4"), nil, "may only be specified when"), + }.WithOrigin("union")) + + // Test validation ratcheting + st.Value(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).OldValue(&Struct{D: DM2, M1: &M1{}, M2: &M2{}}).ExpectValid() + st.Value(&Struct{D: DM1}).OldValue(&Struct{D: DM1}).ExpectValid() + + // Slice member ratcheting: unchanged membership + st.Value(&Struct{D: DM3, M3: []string{"a"}}).OldValue(&Struct{D: DM3, M3: []string{"b"}}).ExpectValid() + // Slice member ratcheting: changed from set to unset + st.Value(&Struct{D: DM3}).OldValue(&Struct{D: DM3, M3: []string{"a"}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m3"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Map member ratcheting: unchanged membership + st.Value(&Struct{D: DM4, M4: map[string]string{"k": "v1"}}).OldValue(&Struct{D: DM4, M4: map[string]string{"k": "v2"}}).ExpectValid() + // Map member ratcheting: changed from set to unset + st.Value(&Struct{D: DM4}).OldValue(&Struct{D: DM4, M4: map[string]string{"k": "v"}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m4"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Test update with nil old value (simulates newly-set pointer field during update). + // Discriminated union validation should still detect mismatches even though oldObj is nil. + st.Value(&Struct{D: DM1}).OldValue(nil).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "must be specified when"), + }.WithOrigin("union")) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/zz_generated.validations.go new file mode 100644 index 0000000000..d2028d6301 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/simple/zz_generated.validations.go @@ -0,0 +1,217 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package simple + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_simple_Struct_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "M1"), validate.NewDiscriminatedUnionMember("m2", "M2"), validate.NewDiscriminatedUnionMember("m3", "M3"), validate.NewDiscriminatedUnionMember("m4", "M4")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_simple_Struct_, + func(obj *Struct) string { + if obj == nil { + return "" + } + return string(obj.D) + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return len(obj.M3) != 0 + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return len(obj.M4) != 0 + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.D has no validation + + { // field Struct.M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.M1 + }) + errs = append(errs, fn(fldPath.Child("m1"), obj.M1, oldVal, oldObj != nil)...) + } + + { // field Struct.M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.M2 + }) + errs = append(errs, fn(fldPath.Child("m2"), obj.M2, oldVal, oldObj != nil)...) + } + + { // field Struct.M3 + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.M3 + }) + errs = append(errs, fn(fldPath.Child("m3"), obj.M3, oldVal, oldObj != nil)...) + } + + { // field Struct.M4 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.M4 + }) + errs = append(errs, fn(fldPath.Child("m4"), obj.M4, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc.go new file mode 100644 index 0000000000..1f60346be8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc.go @@ -0,0 +1,49 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package sparse + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Discriminated union +type Struct struct { + TypeMeta int + + // +k8s:unionDiscriminator + D D `json:"d"` + + // +k8s:unionMember + // +k8s:optional + M1 *M1 `json:"m1"` + + //Note: no M2 field to match the M2 discriminator. +} + +type D string + +const ( + DM1 D = "M1" + DM2 D = "M2" +) + +type M1 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc_test.go new file mode 100644 index 0000000000..1976ade978 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/doc_test.go @@ -0,0 +1,44 @@ +/* +Copyright 2025 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 sparse + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ /* zero values */ }).ExpectValid() + + st.Value(&Struct{D: DM1, M1: &M1{}}).ExpectValid() + st.Value(&Struct{D: DM2}).ExpectValid() + + st.Value(&Struct{D: DM2, M1: &M1{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "may only be specified when"), + }.WithOrigin("union")) + + st.Value(&Struct{D: DM1}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("m1"), nil, "must be specified when"), + }.WithOrigin("union")) + + // Test validation ratcheting + st.Value(&Struct{D: DM2, M1: &M1{}}).OldValue(&Struct{D: DM2, M1: &M1{}}).ExpectValid() + st.Value(&Struct{D: DM1}).OldValue(&Struct{D: DM1}).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/zz_generated.validations.go new file mode 100644 index 0000000000..72660eb7ae --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/discriminated/sparse/zz_generated.validations.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package sparse + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_sparse_Struct_ = validate.NewDiscriminatedUnionMembership("d", validate.NewDiscriminatedUnionMember("m1", "M1")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.DiscriminatedUnion(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_discriminated_sparse_Struct_, + func(obj *Struct) string { + if obj == nil { + return "" + } + return string(obj.D) + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.D has no validation + + { // field Struct.M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.M1 + }) + errs = append(errs, fn(fldPath.Child("m1"), obj.M1, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc.go new file mode 100644 index 0000000000..2cd7d65099 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package custommembers + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Non-discriminated union with custom member names +type Struct struct { + TypeMeta int + + NonUnionField string `json:"nonUnionField"` + + // +k8s:unionMember(memberName: "CustomM1") + // +k8s:optional + M1 *M1 `json:"m1"` + + // +k8s:unionMember(memberName: "CustomM2") + // +k8s:optional + M2 *M2 `json:"m2"` +} + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc_test.go new file mode 100644 index 0000000000..2e91d904b5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/doc_test.go @@ -0,0 +1,41 @@ +/* +Copyright 2024 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 custommembers + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{M1: &M1{}}).ExpectValid() + st.Value(&Struct{M2: &M2{}}).ExpectValid() + + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify exactly one of"), + }.WithOrigin("union")) + st.Value(&Struct{}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of"), + }.WithOrigin("union")) + + // Test validation ratcheting + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue(&Struct{M1: &M1{}, M2: &M2{}}).ExpectValid() + st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/zz_generated.validations.go new file mode 100644 index 0000000000..452b627c98 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/custom_members/zz_generated.validations.go @@ -0,0 +1,142 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package custommembers + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_custom_members_Struct_ = validate.NewUnionMembership(validate.NewUnionMember("m1"), validate.NewUnionMember("m2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_custom_members_Struct_, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.NonUnionField has no validation + + { // field Struct.M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.M1 + }) + errs = append(errs, fn(fldPath.Child("m1"), obj.M1, oldVal, oldObj != nil)...) + } + + { // field Struct.M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.M2 + }) + errs = append(errs, fn(fldPath.Child("m2"), obj.M2, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc.go new file mode 100644 index 0000000000..94069ef3c0 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package multiple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Two non-discriminated unions in the same struct +type Struct struct { + TypeMeta int + + NonUnionField string `json:"nonUnionField"` + + // +k8s:unionMember(union: "union1") + // +k8s:optional + U1M1 *M1 `json:"u1m1"` + + // +k8s:unionMember(union: "union1") + // +k8s:optional + U1M2 *M2 `json:"u1m2"` + + // +k8s:unionMember(union: "union2") + // +k8s:optional + U2M1 *M1 `json:"u2m1"` + + // +k8s:unionMember(union: "union2") + // +k8s:optional + U2M2 *M2 `json:"u2m2"` +} + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc_test.go new file mode 100644 index 0000000000..777a5be858 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/doc_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 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 multiple + +import ( + "testing" + + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of: `u1m1`, `u1m2`").WithOrigin("union"), + field.Invalid(nil, nil, "must specify one of: `u2m1`, `u2m2`").WithOrigin("union"), + }) + + st.Value(&Struct{U1M1: &M1{}, U2M1: &M1{}}).ExpectValid() + st.Value(&Struct{U1M2: &M2{}, U2M2: &M2{}}).ExpectValid() + + st.Value(&Struct{U1M1: &M1{}, U1M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify exactly one of: `u1m1`, `u1m2`").WithOrigin("union"), + field.Invalid(nil, nil, "must specify one of: `u2m1`, `u2m2`").WithOrigin("union"), + }) + + st.Value(&Struct{U2M1: &M1{}, U2M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of: `u1m1`, `u1m2`").WithOrigin("union"), + field.Invalid(nil, nil, "must specify exactly one of: `u2m1`, `u2m2`").WithOrigin("union"), + }) + + st.Value(&Struct{ + U1M1: &M1{}, U1M2: &M2{}, + U2M1: &M1{}, U2M2: &M2{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify exactly one of: `u1m1`, `u1m2`").WithOrigin("union"), + field.Invalid(nil, nil, "must specify exactly one of: `u2m1`, `u2m2`").WithOrigin("union"), + }) + + // Test validation ratcheting + st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() + st.Value(&Struct{U1M1: &M1{}, U1M2: &M2{}}).OldValue(&Struct{U1M1: &M1{}, U1M2: &M2{}}).ExpectValid() + st.Value(&Struct{U2M1: &M1{}, U2M2: &M2{}}).OldValue(&Struct{U2M1: &M1{}, U2M2: &M2{}}).ExpectValid() + st.Value(&Struct{ + U1M1: &M1{}, U1M2: &M2{}, + U2M1: &M1{}, U2M2: &M2{}, + }).OldValue(&Struct{ + U1M1: &M1{}, U1M2: &M2{}, + U2M1: &M1{}, U2M2: &M2{}, + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/zz_generated.validations.go new file mode 100644 index 0000000000..34490fedc4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/multiple/zz_generated.validations.go @@ -0,0 +1,214 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiple + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_multiple_Struct_union1 = validate.NewUnionMembership(validate.NewUnionMember("u1m1"), validate.NewUnionMember("u1m2")) +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_multiple_Struct_union2 = validate.NewUnionMembership(validate.NewUnionMember("u2m1"), validate.NewUnionMember("u2m2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_multiple_Struct_union1, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U1M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U1M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_multiple_Struct_union2, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U2M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U2M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.NonUnionField has no validation + + { // field Struct.U1M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.U1M1 + }) + errs = append(errs, fn(fldPath.Child("u1m1"), obj.U1M1, oldVal, oldObj != nil)...) + } + + { // field Struct.U1M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.U1M2 + }) + errs = append(errs, fn(fldPath.Child("u1m2"), obj.U1M2, oldVal, oldObj != nil)...) + } + + { // field Struct.U2M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.U2M1 + }) + errs = append(errs, fn(fldPath.Child("u2m1"), obj.U2M1, oldVal, oldObj != nil)...) + } + + { // field Struct.U2M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.U2M2 + }) + errs = append(errs, fn(fldPath.Child("u2m2"), obj.U2M2, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc.go new file mode 100644 index 0000000000..5cc49142e2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package simple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Non-discriminated union +type Struct struct { + TypeMeta int + + NonUnionField string `json:"nonUnionField"` + + // +k8s:unionMember + // +k8s:optional + M1 *M1 `json:"m1"` + + // +k8s:unionMember + // +k8s:optional + M2 *M2 `json:"m2"` + + // +k8s:unionMember + // +k8s:optional + M3 string `json:"m3"` + + // +k8s:unionMember + // +k8s:optional + M4 *string `json:"m4"` + + // +k8s:unionMember + // +k8s:optional + M5 []string `json:"m5"` + + // +k8s:unionMember + // +k8s:optional + M6 map[string]string `json:"m6"` +} + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc_test.go new file mode 100644 index 0000000000..a6f5c582ed --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/doc_test.go @@ -0,0 +1,75 @@ +/* +Copyright 2024 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 simple + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of"), + }.WithOrigin("union")) + + st.Value(&Struct{M1: &M1{}}).ExpectValid() + st.Value(&Struct{M2: &M2{}}).ExpectValid() + st.Value(&Struct{M3: "a string"}).ExpectValid() + st.Value(&Struct{M4: ptr.To("a string")}).ExpectValid() + st.Value(&Struct{M5: []string{"a string"}}).ExpectValid() + st.Value(&Struct{M6: map[string]string{"k": "v"}}).ExpectValid() + + // Empty slice/map are "not set" (same as nil for union membership) + st.Value(&Struct{M5: []string{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of"), + }.WithOrigin("union")) + st.Value(&Struct{M6: map[string]string{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of"), + }.WithOrigin("union")) + + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify exactly one of"), + }.WithOrigin("union")) + st.Value(&Struct{M1: &M1{}, M3: "a string"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify exactly one of"), + }.WithOrigin("union")) + st.Value(&Struct{M1: &M1{}, M4: ptr.To("a string")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify exactly one of"), + }.WithOrigin("union")) + + // Update only considers whether a field was set, not the value. + st.Value(&Struct{M3: "a string"}).OldValue(&Struct{M3: "different string"}).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue(&Struct{M1: &M1{}, M2: &M2{}}).ExpectValid() + st.Value(&Struct{M3: "a string", M2: &M2{}}).OldValue(&Struct{M3: "different string", M2: &M2{}}).ExpectValid() + + // Slice/map member ratcheting: unchanged membership, different values + st.Value(&Struct{M5: []string{"a"}}).OldValue(&Struct{M5: []string{"b"}}).ExpectValid() + st.Value(&Struct{M6: map[string]string{"k": "v1"}}).OldValue(&Struct{M6: map[string]string{"k": "v2"}}).ExpectValid() + + // Test update with nil old value (simulates new map entry or newly-set pointer field during update). + // Union validation should still detect the empty union even though oldObj is nil. + st.Value(&Struct{}).OldValue(nil).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify one of").WithOrigin("union"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/zz_generated.validations.go new file mode 100644 index 0000000000..012ee08ee8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/union/union/undiscriminated/simple/zz_generated.validations.go @@ -0,0 +1,280 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package simple + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_simple_Struct_ = validate.NewUnionMembership(validate.NewUnionMember("m1"), validate.NewUnionMember("m2"), validate.NewUnionMember("m3"), validate.NewUnionMember("m4"), validate.NewUnionMember("m5"), validate.NewUnionMember("m6")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_union_union_undiscriminated_simple_Struct_, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + var z string + return obj.M3 != z + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M4 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return len(obj.M5) != 0 + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return len(obj.M6) != 0 + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.NonUnionField has no validation + + { // field Struct.M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.M1 + }) + errs = append(errs, fn(fldPath.Child("m1"), obj.M1, oldVal, oldObj != nil)...) + } + + { // field Struct.M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.M2 + }) + errs = append(errs, fn(fldPath.Child("m2"), obj.M2, oldVal, oldObj != nil)...) + } + + { // field Struct.M3 + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.M3 + }) + errs = append(errs, fn(fldPath.Child("m3"), &obj.M3, oldVal, oldObj != nil)...) + } + + { // field Struct.M4 + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.M4 + }) + errs = append(errs, fn(fldPath.Child("m4"), obj.M4, oldVal, oldObj != nil)...) + } + + { // field Struct.M5 + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.M5 + }) + errs = append(errs, fn(fldPath.Child("m5"), obj.M5, oldVal, oldObj != nil)...) + } + + { // field Struct.M6 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) map[string]string { + return oldObj.M6 + }) + errs = append(errs, fn(fldPath.Child("m6"), obj.M6, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc.go new file mode 100644 index 0000000000..d8ad99f057 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc.go @@ -0,0 +1,123 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package unique + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // Basic unique=set on primitive slice + // +k8s:listType=atomic + // +k8s:unique=set + PrimitiveListUniqueSet []string `json:"primitiveListUniqueSet"` + + // unique=map with multiple keys + // +k8s:listType=atomic + // +k8s:unique=map + // +k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + SliceMapFieldWithMultipleKeys []ItemWithMultipleKeys `json:"sliceMapFieldWithMultipleKeys"` + + // atomic + unique=set combination + // +k8s:listType=atomic + // +k8s:unique=set + AtomicListUniqueSet []Item `json:"atomicListUniqueSet"` + + // atomic + unique=map combination + // +k8s:listType=atomic + // +k8s:unique=map + // +k8s:listMapKey=key + AtomicListUniqueMap []Item `json:"atomicListUniqueMap"` + + // customUnique with listType=set + // +k8s:listType=set + // +k8s:customUnique + CustomUniqueListWithTypeSet []string `json:"customUniqueListWithTypeSet"` + + // customUnique with listType=map + // +k8s:listType=map + // +k8s:listMapKey=key + // +k8s:customUnique + CustomUniqueListWithTypeMap []Item `json:"customUniqueListWithTypeMap"` + + // unique=map with pointer key + // +k8s:listType=atomic + // +k8s:unique=map + // +k8s:listMapKey=key + SliceMapFieldWithPtrKey []PtrKeyStruct `json:"sliceMapFieldWithPtrKey"` + + // unique=map with mixed (pointer and primitive) keys + // +k8s:listType=atomic + // +k8s:unique=map + // +k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + SliceMapFieldWithMixedKeys []ItemWithMixedKeys `json:"sliceMapFieldWithMixedKeys"` + + // unique=map with multiple pointer keys + // +k8s:listType=atomic + // +k8s:unique=map + // +k8s:listMapKey=key1 + // +k8s:listMapKey=key2 + SliceMapFieldWithMultiplePtrKeys []ItemWithMultiplePtrKeys `json:"sliceMapFieldWithMultiplePtrKeys"` + + // unique=set on slice of pointers to primitive + // +k8s:listType=atomic + // +k8s:unique=set + PrimitivePointerListUniqueSet []*string `json:"primitivePointerListUniqueSet"` + + // unique=map on slice of pointers to struct + // +k8s:listType=atomic + // +k8s:unique=map + // +k8s:listMapKey=key + PointerListUniqueMap []*Item `json:"pointerListUniqueMap"` +} + +type Item struct { + Key string `json:"key"` + Data string `json:"data"` +} + +type ItemWithMultipleKeys struct { + Key1 string `json:"key1"` + Key2 string `json:"key2"` + Data string `json:"data"` +} + +type PtrKeyStruct struct { + Key *string `json:"key"` + Data string `json:"data"` +} + +type ItemWithMixedKeys struct { + Key1 *string `json:"key1"` + Key2 string `json:"key2"` + Data string `json:"data"` +} + +type ItemWithMultiplePtrKeys struct { + Key1 *string `json:"key1"` + Key2 *string `json:"key2"` + Data string `json:"data"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc_test.go new file mode 100644 index 0000000000..999a7301d5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/doc_test.go @@ -0,0 +1,256 @@ +/* +Copyright 2025 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 unique + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestUnique(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Test empty struct (should be valid) + st.Value(&Struct{}).ExpectValid() + + // Test valid cases with no duplicates + st.Value(&Struct{ + PrimitiveListUniqueSet: []string{"aaa", "bbb"}, + SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{ + {Key1: "a", Key2: "x", Data: "first"}, + {Key1: "a", Key2: "y", Data: "second"}, + }, + AtomicListUniqueSet: []Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + }, + AtomicListUniqueMap: []Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + }, + CustomUniqueListWithTypeSet: []string{"a", "b", "a"}, + CustomUniqueListWithTypeMap: []Item{{Key: "a"}, {Key: "b"}, {Key: "a"}}, + SliceMapFieldWithPtrKey: []PtrKeyStruct{ + {Key: new("a"), Data: "first"}, + {Key: new("b"), Data: "second"}, + }, + SliceMapFieldWithMixedKeys: []ItemWithMixedKeys{ + {Key1: new("a"), Key2: "x", Data: "first"}, + {Key1: new("a"), Key2: "y", Data: "second"}, + }, + SliceMapFieldWithMultiplePtrKeys: []ItemWithMultiplePtrKeys{ + {Key1: new("a"), Key2: new("x"), Data: "first"}, + {Key1: new("a"), Key2: new("y"), Data: "second"}, + }, + PrimitivePointerListUniqueSet: []*string{new("aaa"), new("bbb")}, + PointerListUniqueMap: []*Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + }, + }).ExpectValid() + + // Test empty lists + st.Value(&Struct{ + PrimitiveListUniqueSet: []string{}, + SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{}, + AtomicListUniqueSet: []Item{}, + AtomicListUniqueMap: []Item{}, + CustomUniqueListWithTypeSet: []string{}, + CustomUniqueListWithTypeMap: []Item{}, + SliceMapFieldWithMixedKeys: []ItemWithMixedKeys{}, + SliceMapFieldWithMultiplePtrKeys: []ItemWithMultiplePtrKeys{}, + PrimitivePointerListUniqueSet: []*string{}, + PointerListUniqueMap: []*Item{}, + }).ExpectValid() + + // Test single element lists + st.Value(&Struct{ + PrimitiveListUniqueSet: []string{"single"}, + SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{{Key1: "a", Key2: "b", Data: "one"}}, + AtomicListUniqueSet: []Item{{Key: "single", Data: "one"}}, + AtomicListUniqueMap: []Item{{Key: "single", Data: "one"}}, + CustomUniqueListWithTypeSet: []string{"single"}, + CustomUniqueListWithTypeMap: []Item{{Key: "single"}}, + SliceMapFieldWithMixedKeys: []ItemWithMixedKeys{{Key1: new("a"), Key2: "b", Data: "one"}}, + SliceMapFieldWithMultiplePtrKeys: []ItemWithMultiplePtrKeys{{Key1: new("a"), Key2: new("b"), Data: "one"}}, + PrimitivePointerListUniqueSet: []*string{new("single")}, + PointerListUniqueMap: []*Item{{Key: "single", Data: "one"}}, + }).ExpectValid() + + // Test duplicate values (should fail validation) + st.Value(&Struct{ + PrimitiveListUniqueSet: []string{"aaa", "bbb", "ccc", "ccc", "bbb", "aaa"}, + SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{ + {Key1: "a", Key2: "x", Data: "first"}, + {Key1: "a", Key2: "y", Data: "second"}, + {Key1: "a", Key2: "x", Data: "third"}, + }, + AtomicListUniqueSet: []Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + {Key: "key1", Data: "one"}, + }, + AtomicListUniqueMap: []Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + {Key: "key1", Data: "three"}, + }, + CustomUniqueListWithTypeSet: []string{"a", "b", "a"}, + CustomUniqueListWithTypeMap: []Item{{Key: "a"}, {Key: "b"}, {Key: "a"}}, + SliceMapFieldWithPtrKey: []PtrKeyStruct{ + {Key: new("a"), Data: "first"}, + {Key: new("b"), Data: "second"}, + {Key: new("a"), Data: "third"}, + }, + SliceMapFieldWithMixedKeys: []ItemWithMixedKeys{ + {Key1: new("a"), Key2: "x", Data: "first"}, + {Key1: new("a"), Key2: "y", Data: "second"}, + {Key1: new("a"), Key2: "x", Data: "third"}, + }, + SliceMapFieldWithMultiplePtrKeys: []ItemWithMultiplePtrKeys{ + {Key1: new("a"), Key2: new("x"), Data: "first"}, + {Key1: new("a"), Key2: new("y"), Data: "second"}, + {Key1: new("a"), Key2: new("x"), Data: "third"}, + }, + PrimitivePointerListUniqueSet: []*string{new("aaa"), new("bbb"), new("aaa")}, + PointerListUniqueMap: []*Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + {Key: "key1", Data: "three"}, + }, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(3), nil), + field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(4), nil), + field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(5), nil), + field.Duplicate(field.NewPath("sliceMapFieldWithMultipleKeys").Index(2), nil), + field.Duplicate(field.NewPath("atomicListUniqueSet").Index(2), nil), + field.Duplicate(field.NewPath("atomicListUniqueMap").Index(2), nil), + field.Duplicate(field.NewPath("sliceMapFieldWithPtrKey").Index(2), nil), + field.Duplicate(field.NewPath("sliceMapFieldWithMixedKeys").Index(2), nil), + field.Duplicate(field.NewPath("sliceMapFieldWithMultiplePtrKeys").Index(2), nil), + field.Duplicate(field.NewPath("primitivePointerListUniqueSet").Index(2), nil), + field.Duplicate(field.NewPath("pointerListUniqueMap").Index(2), nil), + }) + + // Test with zero values and empty strings + st.Value(&Struct{ + PrimitiveListUniqueSet: []string{"", "a", ""}, + AtomicListUniqueMap: []Item{ + {Key: "", Data: "one"}, + {Key: "a", Data: "two"}, + {Key: "", Data: "three"}, + }, + CustomUniqueListWithTypeSet: []string{"", "a", ""}, + CustomUniqueListWithTypeMap: []Item{{Key: ""}, {Key: "a"}, {Key: ""}}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Duplicate(field.NewPath("primitiveListUniqueSet").Index(2), nil), + field.Duplicate(field.NewPath("atomicListUniqueMap").Index(2), nil), + }) + + // Test nil elements in pointer lists (should trigger Required errors) + st.Value(&Struct{ + PrimitivePointerListUniqueSet: []*string{new("a"), nil, new("b")}, + PointerListUniqueMap: []*Item{{Key: "key1"}, nil}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField(), field.ErrorList{ + field.Required(field.NewPath("primitivePointerListUniqueSet").Index(1), ""), + field.Required(field.NewPath("pointerListUniqueMap").Index(1), ""), + }) +} + +func TestRatcheting(t *testing.T) { + st := localSchemeBuilder.Test(t) + + struct1 := Struct{ + PrimitiveListUniqueSet: []string{"aaa", "bbb"}, + SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{ + {Key1: "a", Key2: "x", Data: "first"}, + {Key1: "a", Key2: "y", Data: "second"}, + }, + AtomicListUniqueSet: []Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + }, + AtomicListUniqueMap: []Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + }, + CustomUniqueListWithTypeSet: []string{"a", "b", "a"}, + CustomUniqueListWithTypeMap: []Item{{Key: "a"}, {Key: "b"}, {Key: "a"}}, + SliceMapFieldWithPtrKey: []PtrKeyStruct{ + {Key: new("a"), Data: "first"}, + {Key: new("b"), Data: "second"}, + }, + SliceMapFieldWithMixedKeys: []ItemWithMixedKeys{ + {Key1: new("a"), Key2: "x", Data: "first"}, + {Key1: new("a"), Key2: "y", Data: "second"}, + }, + SliceMapFieldWithMultiplePtrKeys: []ItemWithMultiplePtrKeys{ + {Key1: new("a"), Key2: new("x"), Data: "first"}, + {Key1: new("a"), Key2: new("y"), Data: "second"}, + }, + PrimitivePointerListUniqueSet: []*string{new("aaa"), new("bbb")}, + PointerListUniqueMap: []*Item{ + {Key: "key1", Data: "one"}, + {Key: "key2", Data: "two"}, + }, + } + + // Same data, different order. + struct2 := Struct{ + PrimitiveListUniqueSet: []string{"bbb", "aaa"}, + SliceMapFieldWithMultipleKeys: []ItemWithMultipleKeys{ + {Key1: "a", Key2: "y", Data: "second"}, + {Key1: "a", Key2: "x", Data: "first"}, + }, + AtomicListUniqueSet: []Item{ + {Key: "key2", Data: "two"}, + {Key: "key1", Data: "one"}, + }, + AtomicListUniqueMap: []Item{ + {Key: "key2", Data: "two"}, + {Key: "key1", Data: "one"}, + }, + CustomUniqueListWithTypeSet: []string{"a", "a", "b"}, + CustomUniqueListWithTypeMap: []Item{{Key: "a"}, {Key: "a"}, {Key: "b"}}, + SliceMapFieldWithPtrKey: []PtrKeyStruct{ + {Key: new("b"), Data: "second"}, + {Key: new("a"), Data: "first"}, + }, + SliceMapFieldWithMixedKeys: []ItemWithMixedKeys{ + {Key1: new("a"), Key2: "y", Data: "second"}, + {Key1: new("a"), Key2: "x", Data: "first"}, + }, + SliceMapFieldWithMultiplePtrKeys: []ItemWithMultiplePtrKeys{ + {Key1: new("a"), Key2: new("y"), Data: "second"}, + {Key1: new("a"), Key2: new("x"), Data: "first"}, + }, + PrimitivePointerListUniqueSet: []*string{new("bbb"), new("aaa")}, + PointerListUniqueMap: []*Item{ + {Key: "key2", Data: "two"}, + {Key: "key1", Data: "one"}, + }, + } + + // Test that reordering doesn't trigger validation errors + st.Value(&struct1).OldValue(&struct2).ExpectValid() + st.Value(&struct2).OldValue(&struct1).ExpectValid() + + // Test that the same data is considered valid regardless of order + st.Value(&struct1).ExpectValid() + st.Value(&struct2).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/zz_generated.validations.go new file mode 100644 index 0000000000..792ebe13bb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/unique/zz_generated.validations.go @@ -0,0 +1,353 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package unique + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.PrimitiveListUniqueSet + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.PrimitiveListUniqueSet + }) + errs = append(errs, fn(fldPath.Child("primitiveListUniqueSet"), obj.PrimitiveListUniqueSet, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceMapFieldWithMultipleKeys + fn := func( + fldPath *field.Path, + obj, oldObj []ItemWithMultipleKeys, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *ItemWithMultipleKeys, b *ItemWithMultipleKeys) bool { + return a.Key1 == b.Key1 && a.Key2 == b.Key2 + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []ItemWithMultipleKeys { + return oldObj.SliceMapFieldWithMultipleKeys + }) + errs = append(errs, fn(fldPath.Child("sliceMapFieldWithMultipleKeys"), obj.SliceMapFieldWithMultipleKeys, oldVal, oldObj != nil)...) + } + + { // field Struct.AtomicListUniqueSet + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.AtomicListUniqueSet + }) + errs = append(errs, fn(fldPath.Child("atomicListUniqueSet"), obj.AtomicListUniqueSet, oldVal, oldObj != nil)...) + } + + { // field Struct.AtomicListUniqueMap + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.AtomicListUniqueMap + }) + errs = append(errs, fn(fldPath.Child("atomicListUniqueMap"), obj.AtomicListUniqueMap, oldVal, oldObj != nil)...) + } + + { // field Struct.CustomUniqueListWithTypeSet + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // Uniqueness validation is implemented via custom, handwritten validation + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []string { + return oldObj.CustomUniqueListWithTypeSet + }) + errs = append(errs, fn(fldPath.Child("customUniqueListWithTypeSet"), obj.CustomUniqueListWithTypeSet, oldVal, oldObj != nil)...) + } + + { // field Struct.CustomUniqueListWithTypeMap + fn := func( + fldPath *field.Path, + obj, oldObj []Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // Uniqueness validation is implemented via custom, handwritten validation + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []Item { + return oldObj.CustomUniqueListWithTypeMap + }) + errs = append(errs, fn(fldPath.Child("customUniqueListWithTypeMap"), obj.CustomUniqueListWithTypeMap, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceMapFieldWithPtrKey + fn := func( + fldPath *field.Path, + obj, oldObj []PtrKeyStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *PtrKeyStruct, b *PtrKeyStruct) bool { + return ((a.Key == nil && b.Key == nil) || (a.Key != nil && b.Key != nil && *a.Key == *b.Key)) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []PtrKeyStruct { + return oldObj.SliceMapFieldWithPtrKey + }) + errs = append(errs, fn(fldPath.Child("sliceMapFieldWithPtrKey"), obj.SliceMapFieldWithPtrKey, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceMapFieldWithMixedKeys + fn := func( + fldPath *field.Path, + obj, oldObj []ItemWithMixedKeys, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *ItemWithMixedKeys, b *ItemWithMixedKeys) bool { + return ((a.Key1 == nil && b.Key1 == nil) || (a.Key1 != nil && b.Key1 != nil && *a.Key1 == *b.Key1)) && a.Key2 == b.Key2 + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []ItemWithMixedKeys { + return oldObj.SliceMapFieldWithMixedKeys + }) + errs = append(errs, fn(fldPath.Child("sliceMapFieldWithMixedKeys"), obj.SliceMapFieldWithMixedKeys, oldVal, oldObj != nil)...) + } + + { // field Struct.SliceMapFieldWithMultiplePtrKeys + fn := func( + fldPath *field.Path, + obj, oldObj []ItemWithMultiplePtrKeys, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *ItemWithMultiplePtrKeys, b *ItemWithMultiplePtrKeys) bool { + return ((a.Key1 == nil && b.Key1 == nil) || (a.Key1 != nil && b.Key1 != nil && *a.Key1 == *b.Key1)) && ((a.Key2 == nil && b.Key2 == nil) || (a.Key2 != nil && b.Key2 != nil && *a.Key2 == *b.Key2)) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []ItemWithMultiplePtrKeys { + return oldObj.SliceMapFieldWithMultiplePtrKeys + }) + errs = append(errs, fn(fldPath.Child("sliceMapFieldWithMultiplePtrKeys"), obj.SliceMapFieldWithMultiplePtrKeys, oldVal, oldObj != nil)...) + } + + { // field Struct.PrimitivePointerListUniqueSet + fn := func( + fldPath *field.Path, + obj, oldObj []*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.PtrSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*string { + return oldObj.PrimitivePointerListUniqueSet + }) + errs = append(errs, fn(fldPath.Child("primitivePointerListUniqueSet"), obj.PrimitivePointerListUniqueSet, oldVal, oldObj != nil)...) + } + + { // field Struct.PointerListUniqueMap + fn := func( + fldPath *field.Path, + obj, oldObj []*Item, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[Item](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.PtrSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *Item, b *Item) bool { return a.Key == b.Key }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) []*Item { + return oldObj.PointerListUniqueMap + }) + errs = append(errs, fn(fldPath.Child("pointerListUniqueMap"), obj.PointerListUniqueMap, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/doc.go new file mode 100644 index 0000000000..b74680779a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/doc.go @@ -0,0 +1,147 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package lists + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type UpdateListStruct struct { + TypeMeta int + + // Slice NoSet/NoUnset (len == 0 semantics, no match function needed). + + // +k8s:listType=atomic + // +k8s:update=NoSet + StringSliceNoSet []string `json:"stringSliceNoSet"` + + // +k8s:listType=atomic + // +k8s:update=NoUnset + StringSliceNoUnset []string `json:"stringSliceNoUnset"` + + // listType=set -> DirectEqual match for directly-comparable elements. + + // +k8s:listType=set + // +k8s:update=NoAddItem + StringSetNoAdd []string `json:"stringSetNoAdd"` + + // +k8s:listType=set + // +k8s:update=NoRemoveItem + StringSetNoRemove []string `json:"stringSetNoRemove"` + + // +k8s:listType=set + // +k8s:update=NoAddItem + // +k8s:update=NoRemoveItem + StringSetFrozenShape []string `json:"stringSetFrozenShape"` + + // listType=map -> inline listMapKey comparison closure. + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:update=NoAddItem + MapListNoAdd []UpdateItem `json:"mapListNoAdd"` + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:update=NoAddItem + // +k8s:update=NoRemoveItem + MapListFrozenShape []UpdateItem `json:"mapListFrozenShape"` + + // listType=map with a composite key (every key field is ANDed). + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:listMapKey=priority + // +k8s:update=NoRemoveItem + CompositeKeyList []CompositeKeyItem `json:"compositeKeyList"` + + // listType=atomic + unique={set,map} -> same as the non-atomic variant. + + // +k8s:listType=atomic + // +k8s:unique=set + // +k8s:update=NoAddItem + AtomicUniqueSetNoAdd []string `json:"atomicUniqueSetNoAdd"` + + // +k8s:listType=atomic + // +k8s:unique=map + // +k8s:listMapKey=name + // +k8s:update=NoAddItem + // +k8s:update=NoRemoveItem + AtomicUniqueMapFrozenShape []UpdateItem `json:"atomicUniqueMapFrozenShape"` + + // listType=set over a non-directly-comparable element -> SemanticDeepEqual. + + // +k8s:listType=set + // +k8s:update=NoAddItem + // +k8s:update=NoRemoveItem + NonComparableSetFrozenShape []NonComparableItem `json:"nonComparableSetFrozenShape"` + + // Typedef list: list metadata lives on the type, the field inherits it. + + // +k8s:update=NoAddItem + // +k8s:update=NoRemoveItem + TypedefFrozenList FrozenUserList `json:"typedefFrozenList"` + + // eachVal composition: per-item NoModify, list shape can still change. + + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:eachVal=+k8s:update=NoModify + EachValNoModifyList []UpdateItem `json:"eachValNoModifyList"` + + // Pointer Slice NoSet + // +k8s:listType=atomic + // +k8s:update=NoSet + PointerSliceNoSet []*string `json:"pointerSliceNoSet"` + + // Pointer listType=set + NoAddItem + // +k8s:listType=set + // +k8s:update=NoAddItem + PointerSetNoAdd []*string `json:"pointerSetNoAdd"` + + // Pointer listType=map + NoRemoveItem + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:update=NoRemoveItem + PointerMapListNoRemove []*UpdateItem `json:"pointerMapListNoRemove"` +} + +type UpdateItem struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type CompositeKeyItem struct { + Name string `json:"name"` + Priority int `json:"priority"` + Value string `json:"value"` +} + +// NonComparableItem holds a slice, so Go's == is unavailable. +type NonComparableItem struct { + Name string `json:"name"` + Tags []string `json:"tags"` +} + +// +k8s:listType=map +// +k8s:listMapKey=name +type FrozenUserList []UpdateItem diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/doc_test.go new file mode 100644 index 0000000000..df5d5b3998 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/doc_test.go @@ -0,0 +1,356 @@ +/* +Copyright 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 lists + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestUpdateListTags(t *testing.T) { + st := localSchemeBuilder.Test(t) + + matcher := field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin() + base := UpdateListStruct{} + + // Slice NoSet (len == 0 semantics) + { + old := base + old.StringSliceNoSet = nil + cur := base + cur.StringSliceNoSet = []string{"a"} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("stringSliceNoSet"), nil, "field cannot be set once created").WithOrigin("update"), + }) + + // nil -> nil is allowed + st.Value(&old).OldValue(&old).ExpectValid() + } + + // Slice NoUnset + { + old := base + old.StringSliceNoUnset = []string{"a"} + cur := base + cur.StringSliceNoUnset = nil + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("stringSliceNoUnset"), nil, "field cannot be cleared once set").WithOrigin("update"), + }) + } + + // listType=set + NoAddItem + { + old := base + old.StringSetNoAdd = []string{"a", "b"} + cur := base + cur.StringSetNoAdd = []string{"a", "b", "c"} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("stringSetNoAdd").Index(2), "item may not be added").WithOrigin("update"), + }) + + // Reorder of existing items is allowed. + reordered := base + reordered.StringSetNoAdd = []string{"b", "a"} + st.Value(&reordered).OldValue(&old).ExpectValid() + } + + // listType=set + NoRemoveItem + { + old := base + old.StringSetNoRemove = []string{"a", "b", "c"} + cur := base + cur.StringSetNoRemove = []string{"a", "c"} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("stringSetNoRemove"), "may not be removed").WithOrigin("update"), + }) + } + + // Frozen-shape set: NoAddItem + NoRemoveItem + { + old := base + old.StringSetFrozenShape = []string{"a", "b"} + + // Permutation is valid. + perm := base + perm.StringSetFrozenShape = []string{"b", "a"} + st.Value(&perm).OldValue(&old).ExpectValid() + + // Swap one item: one add + one remove, both reported. + swap := base + swap.StringSetFrozenShape = []string{"a", "c"} + st.Value(&swap).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("stringSetFrozenShape").Index(1), "item may not be added").WithOrigin("update"), + field.Forbidden(field.NewPath("stringSetFrozenShape"), "may not be removed").WithOrigin("update"), + }) + } + + // listType=map + single key + NoAddItem + { + old := base + old.MapListNoAdd = []UpdateItem{{Name: "alpha", Value: "1"}} + cur := base + cur.MapListNoAdd = []UpdateItem{ + {Name: "alpha", Value: "1"}, + {Name: "beta", Value: "2"}, + } + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("mapListNoAdd").Index(1), "item may not be added").WithOrigin("update"), + }) + + // Modifying the value of an existing keyed item is not an "add". + modified := base + modified.MapListNoAdd = []UpdateItem{{Name: "alpha", Value: "999"}} + st.Value(&modified).OldValue(&old).ExpectValid() + } + + // listType=map + single key + frozen shape + { + old := base + old.MapListFrozenShape = []UpdateItem{ + {Name: "alpha", Value: "1"}, + {Name: "beta", Value: "2"}, + } + + // Value-only change to an existing keyed item is allowed. + modify := base + modify.MapListFrozenShape = []UpdateItem{ + {Name: "alpha", Value: "99"}, + {Name: "beta", Value: "2"}, + } + st.Value(&modify).OldValue(&old).ExpectValid() + + // Removing a key produces a NoRemoveItem error. + removed := base + removed.MapListFrozenShape = []UpdateItem{{Name: "alpha", Value: "1"}} + st.Value(&removed).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("mapListFrozenShape"), "may not be removed").WithOrigin("update"), + }) + } + + // listType=map + composite key + NoRemoveItem + { + old := base + old.CompositeKeyList = []CompositeKeyItem{ + {Name: "alpha", Priority: 1, Value: "x"}, + {Name: "alpha", Priority: 2, Value: "y"}, + } + + // Same key pair, different value: allowed. + modify := base + modify.CompositeKeyList = []CompositeKeyItem{ + {Name: "alpha", Priority: 1, Value: "x"}, + {Name: "alpha", Priority: 2, Value: "y2"}, + } + st.Value(&modify).OldValue(&old).ExpectValid() + + // Remove one pair entirely: rejected. + removed := base + removed.CompositeKeyList = []CompositeKeyItem{ + {Name: "alpha", Priority: 1, Value: "x"}, + } + st.Value(&removed).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("compositeKeyList"), "may not be removed").WithOrigin("update"), + }) + } + + // listType=atomic + unique=set + NoAddItem + { + old := base + old.AtomicUniqueSetNoAdd = []string{"a"} + cur := base + cur.AtomicUniqueSetNoAdd = []string{"a", "b"} + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("atomicUniqueSetNoAdd").Index(1), "item may not be added").WithOrigin("update"), + }) + } + + // listType=atomic + unique=map: semanticMap path via unique= + { + old := base + old.AtomicUniqueMapFrozenShape = []UpdateItem{ + {Name: "alpha", Value: "1"}, + {Name: "beta", Value: "2"}, + } + + // Value-only change on an existing keyed item is allowed. This + // matches listType=map behavior. + modify := base + modify.AtomicUniqueMapFrozenShape = []UpdateItem{ + {Name: "alpha", Value: "99"}, + {Name: "beta", Value: "2"}, + } + st.Value(&modify).OldValue(&old).ExpectValid() + + // Adding a new key is rejected. + add := base + add.AtomicUniqueMapFrozenShape = []UpdateItem{ + {Name: "alpha", Value: "1"}, + {Name: "beta", Value: "2"}, + {Name: "gamma", Value: "3"}, + } + st.Value(&add).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("atomicUniqueMapFrozenShape").Index(2), "item may not be added").WithOrigin("update"), + }) + } + + // listType=set over non-directly-comparable elements + { + old := base + old.NonComparableSetFrozenShape = []NonComparableItem{ + {Name: "alpha", Tags: []string{"x", "y"}}, + {Name: "beta", Tags: []string{"z"}}, + } + + // Reordering full-value items is allowed (SemanticDeepEqual matches + // each one in the old list). + reordered := base + reordered.NonComparableSetFrozenShape = []NonComparableItem{ + {Name: "beta", Tags: []string{"z"}}, + {Name: "alpha", Tags: []string{"x", "y"}}, + } + st.Value(&reordered).OldValue(&old).ExpectValid() + + // Mutating a tag inside one element changes its identity under + // set semantics (the whole element is the key), so this is + // reported as both an add and a remove. + mutated := base + mutated.NonComparableSetFrozenShape = []NonComparableItem{ + {Name: "alpha", Tags: []string{"x", "y", "NEW"}}, + {Name: "beta", Tags: []string{"z"}}, + } + st.Value(&mutated).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("nonComparableSetFrozenShape").Index(0), "item may not be added").WithOrigin("update"), + field.Forbidden(field.NewPath("nonComparableSetFrozenShape"), "may not be removed").WithOrigin("update"), + }) + } + + // Typedef list: metadata inherited from type + { + old := base + old.TypedefFrozenList = FrozenUserList{{Name: "alpha", Value: "1"}} + + // Value-only mutation of the existing keyed item: allowed. + modify := base + modify.TypedefFrozenList = FrozenUserList{{Name: "alpha", Value: "2"}} + st.Value(&modify).OldValue(&old).ExpectValid() + + // Add a new keyed item: rejected. + add := base + add.TypedefFrozenList = FrozenUserList{ + {Name: "alpha", Value: "1"}, + {Name: "beta", Value: "9"}, + } + st.Value(&add).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("typedefFrozenList").Index(1), "item may not be added").WithOrigin("update"), + }) + } + + // eachVal composition on a list: per-item NoModify + { + old := base + old.EachValNoModifyList = []UpdateItem{ + {Name: "alpha", Value: "1"}, + {Name: "beta", Value: "2"}, + } + + // Adding a new keyed item is allowed. NoModify is per-item. + add := base + add.EachValNoModifyList = []UpdateItem{ + {Name: "alpha", Value: "1"}, + {Name: "beta", Value: "2"}, + {Name: "gamma", Value: "3"}, + } + st.Value(&add).OldValue(&old).ExpectValid() + + // Removing a keyed item is allowed. NoModify is per-item. + remove := base + remove.EachValNoModifyList = []UpdateItem{{Name: "alpha", Value: "1"}} + st.Value(&remove).OldValue(&old).ExpectValid() + + // Mutating the non-key Value field of an existing keyed item fires. + mutate := base + mutate.EachValNoModifyList = []UpdateItem{ + {Name: "alpha", Value: "99"}, + {Name: "beta", Value: "2"}, + } + st.Value(&mutate).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("eachValNoModifyList").Index(0), nil, "cannot be modified").WithOrigin("update"), + }) + } + + // Pointer Slice NoSet + { + old := base + old.PointerSliceNoSet = nil + cur := base + cur.PointerSliceNoSet = []*string{new("a")} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("pointerSliceNoSet"), nil, "field cannot be set once created").WithOrigin("update"), + }) + + // nil -> nil is allowed + st.Value(&old).OldValue(&old).ExpectValid() + } + + // Pointer listType=set + NoAddItem + { + old := base + old.PointerSetNoAdd = []*string{new("a"), new("b")} + cur := base + cur.PointerSetNoAdd = []*string{new("a"), new("b"), new("c")} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("pointerSetNoAdd").Index(2), "item may not be added").WithOrigin("update"), + }) + } + + // Pointer listType=map + NoRemoveItem + { + old := base + old.PointerMapListNoRemove = []*UpdateItem{ + {Name: "alpha", Value: "1"}, + {Name: "beta", Value: "2"}, + } + cur := base + cur.PointerMapListNoRemove = []*UpdateItem{ + {Name: "alpha", Value: "1"}, + } + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("pointerMapListNoRemove"), "item may not be removed").WithOrigin("update"), + }) + } + + // Nil element in pointer slice on update should trigger Required error from PtrSliceUpdate + { + old := base + old.PointerSetNoAdd = []*string{new("a")} + cur := base + cur.PointerSetNoAdd = []*string{nil} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Required(field.NewPath("pointerSetNoAdd").Index(0), ""), + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/zz_generated.validations.go new file mode 100644 index 0000000000..b0e1a4e3fb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/lists/zz_generated.validations.go @@ -0,0 +1,634 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package lists + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type UpdateListStruct + scheme.AddValidationFunc( + (*UpdateListStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_UpdateListStruct( + ctx, op, nil, /* fldPath */ + obj.(*UpdateListStruct), + safe.Cast[*UpdateListStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_FrozenUserList validates an instance of FrozenUserList according +// to declarative validation rules in the API schema. +func Validate_FrozenUserList( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj FrozenUserList) (errs field.ErrorList) { + + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_UpdateListStruct validates an instance of UpdateListStruct according +// to declarative validation rules in the API schema. +func Validate_UpdateListStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *UpdateListStruct) (errs field.ErrorList) { + + // field UpdateListStruct.TypeMeta has no validation + + { // field UpdateListStruct.StringSliceNoSet + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, nil, validate.NoSet).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []string { + return oldObj.StringSliceNoSet + }) + errs = append(errs, fn(fldPath.Child("stringSliceNoSet"), obj.StringSliceNoSet, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.StringSliceNoUnset + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, nil, validate.NoUnset).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []string { + return oldObj.StringSliceNoUnset + }) + errs = append(errs, fn(fldPath.Child("stringSliceNoUnset"), obj.StringSliceNoUnset, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.StringSetNoAdd + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, validate.NoAddItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []string { + return oldObj.StringSetNoAdd + }) + errs = append(errs, fn(fldPath.Child("stringSetNoAdd"), obj.StringSetNoAdd, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.StringSetNoRemove + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []string { + return oldObj.StringSetNoRemove + }) + errs = append(errs, fn(fldPath.Child("stringSetNoRemove"), obj.StringSetNoRemove, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.StringSetFrozenShape + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, validate.NoAddItem, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []string { + return oldObj.StringSetFrozenShape + }) + errs = append(errs, fn(fldPath.Child("stringSetFrozenShape"), obj.StringSetFrozenShape, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.MapListNoAdd + fn := func( + fldPath *field.Path, + obj, oldObj []UpdateItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }, validate.NoAddItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []UpdateItem { + return oldObj.MapListNoAdd + }) + errs = append(errs, fn(fldPath.Child("mapListNoAdd"), obj.MapListNoAdd, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.MapListFrozenShape + fn := func( + fldPath *field.Path, + obj, oldObj []UpdateItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }, validate.NoAddItem, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []UpdateItem { + return oldObj.MapListFrozenShape + }) + errs = append(errs, fn(fldPath.Child("mapListFrozenShape"), obj.MapListFrozenShape, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.CompositeKeyList + fn := func( + fldPath *field.Path, + obj, oldObj []CompositeKeyItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, + func(a *CompositeKeyItem, b *CompositeKeyItem) bool { + return a.Name == b.Name && a.Priority == b.Priority + }, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *CompositeKeyItem, b *CompositeKeyItem) bool { + return a.Name == b.Name && a.Priority == b.Priority + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []CompositeKeyItem { + return oldObj.CompositeKeyList + }) + errs = append(errs, fn(fldPath.Child("compositeKeyList"), obj.CompositeKeyList, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.AtomicUniqueSetNoAdd + fn := func( + fldPath *field.Path, + obj, oldObj []string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, validate.NoAddItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []string { + return oldObj.AtomicUniqueSetNoAdd + }) + errs = append(errs, fn(fldPath.Child("atomicUniqueSetNoAdd"), obj.AtomicUniqueSetNoAdd, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.AtomicUniqueMapFrozenShape + fn := func( + fldPath *field.Path, + obj, oldObj []UpdateItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }, validate.NoAddItem, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []UpdateItem { + return oldObj.AtomicUniqueMapFrozenShape + }) + errs = append(errs, fn(fldPath.Child("atomicUniqueMapFrozenShape"), obj.AtomicUniqueMapFrozenShape, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.NonComparableSetFrozenShape + fn := func( + fldPath *field.Path, + obj, oldObj []NonComparableItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, deepEqualImpl_, validate.NoAddItem, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, deepEqualImpl_); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []NonComparableItem { + return oldObj.NonComparableSetFrozenShape + }) + errs = append(errs, fn(fldPath.Child("nonComparableSetFrozenShape"), obj.NonComparableSetFrozenShape, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.TypedefFrozenList + fn := func( + fldPath *field.Path, + obj, oldObj FrozenUserList, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.ValSliceUpdate(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }, validate.NoAddItem, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // call the type's validation function + errs = append(errs, Validate_FrozenUserList(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) FrozenUserList { + return oldObj.TypedefFrozenList + }) + errs = append(errs, fn(fldPath.Child("typedefFrozenList"), obj.TypedefFrozenList, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.EachValNoModifyList + fn := func( + fldPath *field.Path, + obj, oldObj []UpdateItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *UpdateItem) field.ErrorList { + return validate.UpdateStruct(ctx, op, fldPath, obj, oldObj, validate.NoModify) + }).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.ValSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []UpdateItem { + return oldObj.EachValNoModifyList + }) + errs = append(errs, fn(fldPath.Child("eachValNoModifyList"), obj.EachValNoModifyList, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.PointerSliceNoSet + fn := func( + fldPath *field.Path, + obj, oldObj []*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.PtrSliceUpdate(ctx, op, fldPath, obj, oldObj, nil, validate.NoSet).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []*string { + return oldObj.PointerSliceNoSet + }) + errs = append(errs, fn(fldPath.Child("pointerSliceNoSet"), obj.PointerSliceNoSet, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.PointerSetNoAdd + fn := func( + fldPath *field.Path, + obj, oldObj []*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.PtrSliceUpdate(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, validate.NoAddItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with set semantics require unique values + if e := validate.PtrSliceUnique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []*string { + return oldObj.PointerSetNoAdd + }) + errs = append(errs, fn(fldPath.Child("pointerSetNoAdd"), obj.PointerSetNoAdd, oldVal, oldObj != nil)...) + } + + { // field UpdateListStruct.PointerMapListNoRemove + fn := func( + fldPath *field.Path, + obj, oldObj []*UpdateItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrSliceNoNils[UpdateItem](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.PtrSliceUpdate(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.PtrSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *UpdateItem, b *UpdateItem) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateListStruct) []*UpdateItem { + return oldObj.PointerMapListNoRemove + }) + errs = append(errs, fn(fldPath.Child("pointerMapListNoRemove"), obj.PointerMapListNoRemove, oldVal, oldObj != nil)...) + } + + return errs +} + +// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time. +func deepEqualImpl_[T any](a, b T) bool { + return equality.Semantic.DeepEqual(a, b) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc.go new file mode 100644 index 0000000000..465888aea1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc.go @@ -0,0 +1,72 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package maps + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type UpdateMapStruct struct { + TypeMeta int + + // +k8s:update=NoSet + MapNoSet map[string]string `json:"mapNoSet"` + + // +k8s:update=NoUnset + MapNoUnset map[string]string `json:"mapNoUnset"` + + // +k8s:update=NoAddItem + MapNoAdd map[string]string `json:"mapNoAdd"` + + // +k8s:update=NoRemoveItem + MapNoRemove map[string]string `json:"mapNoRemove"` + + // +k8s:update=NoAddItem + // +k8s:update=NoRemoveItem + MapFrozenShape map[string]string `json:"mapFrozenShape"` + + // +k8s:update=NoSet + // +k8s:update=NoAddItem + MapSetThenFreeze map[string]MapItem `json:"mapSetThenFreeze"` + + // eachVal composition: per-value NoModify, key set can still change. + + // +k8s:eachVal=+k8s:update=NoModify + EachValNoModifyMap map[string]MapItem `json:"eachValNoModifyMap"` + + // Pointer Map NoSet + // +k8s:update=NoSet + PointerMapNoSet map[string]*string `json:"pointerMapNoSet"` + + // Pointer Map NoAdd + // +k8s:update=NoAddItem + PointerMapNoAdd map[string]*string `json:"pointerMapNoAdd"` + + // Pointer Map NoRemove + // +k8s:update=NoRemoveItem + PointerMapNoRemove map[string]*MapItem `json:"pointerMapNoRemove"` +} + +type MapItem struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc_test.go new file mode 100644 index 0000000000..3917898a1c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/doc_test.go @@ -0,0 +1,188 @@ +/* +Copyright 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 maps + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestUpdateMapTags(t *testing.T) { + st := localSchemeBuilder.Test(t) + + matcher := field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin() + base := UpdateMapStruct{} + + // Map NoSet/NoUnset + { + old := base + cur := base + cur.MapNoSet = map[string]string{"a": "1"} + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("mapNoSet"), nil, "field cannot be set once created").WithOrigin("update"), + }) + } + { + old := base + old.MapNoUnset = map[string]string{"a": "1"} + cur := base + cur.MapNoUnset = nil + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("mapNoUnset"), nil, "field cannot be cleared once set").WithOrigin("update"), + }) + } + + // Map NoAddItem/NoRemoveItem + { + old := base + old.MapNoAdd = map[string]string{"a": "1"} + cur := base + cur.MapNoAdd = map[string]string{"a": "1", "b": "2"} + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("mapNoAdd").Key("b"), "item may not be added").WithOrigin("update"), + }) + + // Value-only change to an existing key is allowed. + modify := base + modify.MapNoAdd = map[string]string{"a": "99"} + st.Value(&modify).OldValue(&old).ExpectValid() + } + { + old := base + old.MapNoRemove = map[string]string{"a": "1", "b": "2"} + cur := base + cur.MapNoRemove = map[string]string{"a": "1"} + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("mapNoRemove").Key("b"), "item may not be removed").WithOrigin("update"), + }) + } + + // Map frozen shape + { + old := base + old.MapFrozenShape = map[string]string{"a": "1", "b": "2"} + + // Value-only change: allowed. + modify := base + modify.MapFrozenShape = map[string]string{"a": "11", "b": "22"} + st.Value(&modify).OldValue(&old).ExpectValid() + + // Swap one key: one add + one remove. + swap := base + swap.MapFrozenShape = map[string]string{"a": "1", "c": "3"} + st.Value(&swap).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("mapFrozenShape").Key("c"), "item may not be added").WithOrigin("update"), + field.Forbidden(field.NewPath("mapFrozenShape").Key("b"), "item may not be removed").WithOrigin("update"), + }) + } + + // Map combined NoSet + NoAddItem + { + old := base + cur := base + cur.MapSetThenFreeze = map[string]MapItem{"k": {Name: "k", Value: "v"}} + + // Both constraints fire: NoSet on the empty->non-empty transition + // and NoAddItem on the new key. + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("mapSetThenFreeze"), nil, "field cannot be set once created").WithOrigin("update"), + field.Forbidden(field.NewPath("mapSetThenFreeze").Key("k"), "item may not be added").WithOrigin("update"), + }) + } + + // eachVal composition on a map: per-value NoModify + { + old := base + old.EachValNoModifyMap = map[string]MapItem{ + "a": {Name: "a", Value: "1"}, + } + + // Adding/removing keys is fine. + add := base + add.EachValNoModifyMap = map[string]MapItem{ + "a": {Name: "a", Value: "1"}, + "b": {Name: "b", Value: "2"}, + } + st.Value(&add).OldValue(&old).ExpectValid() + + // Mutating an existing value fires. + mutate := base + mutate.EachValNoModifyMap = map[string]MapItem{ + "a": {Name: "a", Value: "99"}, + } + st.Value(&mutate).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("eachValNoModifyMap").Key("a"), nil, "cannot be modified").WithOrigin("update"), + }) + } + + // Pointer Map NoSet + { + old := base + old.PointerMapNoSet = nil + cur := base + cur.PointerMapNoSet = map[string]*string{"a": new("1")} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Invalid(field.NewPath("pointerMapNoSet"), nil, "field cannot be set once created").WithOrigin("update"), + }) + + // nil -> nil is allowed + st.Value(&old).OldValue(&old).ExpectValid() + } + + // Pointer Map NoAddItem + { + old := base + old.PointerMapNoAdd = map[string]*string{"a": new("1")} + cur := base + cur.PointerMapNoAdd = map[string]*string{"a": new("1"), "b": new("2")} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("pointerMapNoAdd").Key("b"), "item may not be added").WithOrigin("update"), + }) + } + + // Pointer Map NoRemoveItem + { + old := base + old.PointerMapNoRemove = map[string]*MapItem{ + "a": {Name: "a", Value: "1"}, + "b": {Name: "b", Value: "2"}, + } + cur := base + cur.PointerMapNoRemove = map[string]*MapItem{ + "a": {Name: "a", Value: "1"}, + } + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Forbidden(field.NewPath("pointerMapNoRemove").Key("b"), "item may not be removed").WithOrigin("update"), + }) + } + + // Nil element in pointer map on update should trigger Required error from PtrMapNoNils + { + old := base + old.PointerMapNoAdd = map[string]*string{"a": new("1")} + cur := base + cur.PointerMapNoAdd = map[string]*string{"a": nil} + + st.Value(&cur).OldValue(&old).ExpectMatches(matcher, field.ErrorList{ + field.Required(field.NewPath("pointerMapNoAdd").Key("a"), ""), + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/zz_generated.validations.go new file mode 100644 index 0000000000..e471ab1470 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/maps/zz_generated.validations.go @@ -0,0 +1,373 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maps + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type UpdateMapStruct + scheme.AddValidationFunc( + (*UpdateMapStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_UpdateMapStruct( + ctx, op, nil, /* fldPath */ + obj.(*UpdateMapStruct), + safe.Cast[*UpdateMapStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_UpdateMapStruct validates an instance of UpdateMapStruct according +// to declarative validation rules in the API schema. +func Validate_UpdateMapStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *UpdateMapStruct) (errs field.ErrorList) { + + // field UpdateMapStruct.TypeMeta has no validation + + { // field UpdateMapStruct.MapNoSet + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoSet).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]string { + return oldObj.MapNoSet + }) + errs = append(errs, fn(fldPath.Child("mapNoSet"), obj.MapNoSet, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.MapNoUnset + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoUnset).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]string { + return oldObj.MapNoUnset + }) + errs = append(errs, fn(fldPath.Child("mapNoUnset"), obj.MapNoUnset, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.MapNoAdd + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoAddItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]string { + return oldObj.MapNoAdd + }) + errs = append(errs, fn(fldPath.Child("mapNoAdd"), obj.MapNoAdd, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.MapNoRemove + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]string { + return oldObj.MapNoRemove + }) + errs = append(errs, fn(fldPath.Child("mapNoRemove"), obj.MapNoRemove, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.MapFrozenShape + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoAddItem, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]string { + return oldObj.MapFrozenShape + }) + errs = append(errs, fn(fldPath.Child("mapFrozenShape"), obj.MapFrozenShape, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.MapSetThenFreeze + fn := func( + fldPath *field.Path, + obj, oldObj map[string]MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoSet, validate.NoAddItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]MapItem { + return oldObj.MapSetThenFreeze + }) + errs = append(errs, fn(fldPath.Child("mapSetThenFreeze"), obj.MapSetThenFreeze, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.EachValNoModifyMap + fn := func( + fldPath *field.Path, + obj, oldObj map[string]MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MapItem) field.ErrorList { + return validate.UpdateStruct(ctx, op, fldPath, obj, oldObj, validate.NoModify) + }).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]MapItem { + return oldObj.EachValNoModifyMap + }) + errs = append(errs, fn(fldPath.Child("eachValNoModifyMap"), obj.EachValNoModifyMap, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.PointerMapNoSet + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoSet).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]*string { + return oldObj.PointerMapNoSet + }) + errs = append(errs, fn(fldPath.Child("pointerMapNoSet"), obj.PointerMapNoSet, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.PointerMapNoAdd + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, string](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoAddItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]*string { + return oldObj.PointerMapNoAdd + }) + errs = append(errs, fn(fldPath.Child("pointerMapNoAdd"), obj.PointerMapNoAdd, oldVal, oldObj != nil)...) + } + + { // field UpdateMapStruct.PointerMapNoRemove + fn := func( + fldPath *field.Path, + obj, oldObj map[string]*MapItem, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.PtrMapNoNils[string, MapItem](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.UpdateMap(ctx, op, fldPath, obj, oldObj, validate.NoRemoveItem).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateMapStruct) map[string]*MapItem { + return oldObj.PointerMapNoRemove + }) + errs = append(errs, fn(fldPath.Child("pointerMapNoRemove"), obj.PointerMapNoRemove, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/doc.go new file mode 100644 index 0000000000..46af4eadcb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/doc.go @@ -0,0 +1,126 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package for the +k8s:update tag. +// +k8s:validation-gen-nolint +package primitives + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type UpdateTestStruct struct { + TypeMeta int + + // +k8s:update=NoSet + StringNoSet string `json:"stringNoSet"` + + // +k8s:update=NoUnset + StringNoUnset string `json:"stringNoUnset"` + + // +k8s:update=NoModify + StringNoModify string `json:"stringNoModify"` + + // +k8s:update=NoSet + // +k8s:update=NoModify + // +k8s:update=NoUnset + StringFullyRestricted string `json:"stringFullyRestricted"` + + // +k8s:update=NoModify + // +k8s:update=NoUnset + StringSetOnce string `json:"stringSetOnce"` + + // +k8s:update=NoModify + IntNoModify int `json:"intNoModify"` + + // +k8s:update=NoModify + Int32NoModify int32 `json:"int32NoModify"` + + // +k8s:update=NoModify + Int64NoModify int64 `json:"int64NoModify"` + + // +k8s:update=NoModify + UintNoModify uint `json:"uintNoModify"` + + // +k8s:update=NoModify + BoolNoModify bool `json:"boolNoModify"` + + // +k8s:update=NoModify + Float32NoModify float32 `json:"float32NoModify"` + + // +k8s:update=NoModify + Float64NoModify float64 `json:"float64NoModify"` + + // +k8s:update=NoModify + ByteNoModify byte `json:"byteNoModify"` + + // +k8s:update=NoModify + StructNoModify TestStruct `json:"structNoModify"` + + // +k8s:update=NoModify + NonComparableStructNoModify NonComparableStruct `json:"nonComparableStructNoModify"` + + // Pointer field tests + + // +k8s:update=NoSet + PointerNoSet *string `json:"pointerNoSet"` + + // +k8s:update=NoUnset + PointerNoUnset *string `json:"pointerNoUnset"` + + // +k8s:update=NoModify + PointerNoModify *string `json:"pointerNoModify"` + + // +k8s:update=NoSet + // +k8s:update=NoModify + // +k8s:update=NoUnset + PointerFullyRestricted *string `json:"pointerFullyRestricted"` + + // +k8s:update=NoModify + IntPointerNoModify *int `json:"intPointerNoModify"` + + // +k8s:update=NoModify + BoolPointerNoModify *bool `json:"boolPointerNoModify"` + + // +k8s:update=NoModify + StructPointerNoModify *TestStruct `json:"structPointerNoModify"` + + // Type alias tests + + // +k8s:update=NoModify + CustomTypeNoModify CustomString `json:"customTypeNoModify"` + + // +k8s:update=NoSet + CustomTypeNoSet CustomInt `json:"customTypeNoSet"` +} + +type TestStruct struct { + StringField string `json:"stringField"` + IntField int `json:"intField"` +} + +// NonComparableStruct contains a slice which makes it non-comparable +type NonComparableStruct struct { + SliceField []string `json:"sliceField"` + IntField int `json:"intField"` +} + +// Custom types to test type aliases +type CustomString string +type CustomInt int diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/doc_test.go new file mode 100644 index 0000000000..bbefa4c5c5 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/doc_test.go @@ -0,0 +1,401 @@ +/* +Copyright 2025 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 primitives + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func TestUpdateTags(t *testing.T) { + st := localSchemeBuilder.Test(t) + + baseStruct := UpdateTestStruct{} + + // String NoSet + old := baseStruct + old.StringNoSet = "" // unset + + new := baseStruct + new.StringNoSet = "value" + + st.Value(&new).OldValue(&old).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringNoSet"), nil, "field cannot be set once created").WithOrigin("update"), + }) + + st.Value(&old).OldValue(&old).ExpectValid() + + // String NoUnset + oldWithValue := baseStruct + oldWithValue.StringNoUnset = "value" + + newUnset := baseStruct + newUnset.StringNoUnset = "" + + // Can set initially (empty to non-empty) + st.Value(&oldWithValue).OldValue(&baseStruct).ExpectValid() + + // Cannot unset (non-empty to empty) + st.Value(&newUnset).OldValue(&oldWithValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringNoUnset"), nil, "field cannot be cleared once set").WithOrigin("update"), + }) + + // String NoModify + oldEmpty := baseStruct + withValue := baseStruct + withValue.StringNoModify = "value" + modified := baseStruct + modified.StringNoModify = "different" + + // Can set initially (empty to non-empty) + st.Value(&withValue).OldValue(&oldEmpty).ExpectValid() + + // Can unset (non-empty to empty) + st.Value(&oldEmpty).OldValue(&withValue).ExpectValid() + + // Cannot modify (non-empty to different non-empty) + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // String Fully Restricted + oldEmpty = baseStruct + withValue = baseStruct + withValue.StringFullyRestricted = "value" + modified = baseStruct + modified.StringFullyRestricted = "different" + + // Cannot set (NoSet) + st.Value(&withValue).OldValue(&oldEmpty).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringFullyRestricted"), nil, "field cannot be set once created").WithOrigin("update"), + }) + + // Cannot unset (NoUnset) + st.Value(&oldEmpty).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringFullyRestricted"), nil, "field cannot be cleared once set").WithOrigin("update"), + }) + + // Cannot modify (NoModify) + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringFullyRestricted"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // String Set-Once Pattern + oldEmpty = baseStruct + withValue = baseStruct + withValue.StringSetOnce = "value" + modified = baseStruct + modified.StringSetOnce = "different" + + // Can set once (empty to non-empty) + st.Value(&withValue).OldValue(&oldEmpty).ExpectValid() + + // Cannot unset (NoUnset) + st.Value(&oldEmpty).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringSetOnce"), nil, "field cannot be cleared once set").WithOrigin("update"), + }) + + // Cannot modify (NoModify) + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("stringSetOnce"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Int NoModify + oldZero := baseStruct + withValue = baseStruct + withValue.IntNoModify = 10 + + // Can transition from 0 to 10 (unset to set is allowed) + st.Value(&withValue).OldValue(&oldZero).ExpectValid() + + // Cannot modify from one non-zero to another + modified = baseStruct + modified.IntNoModify = 20 + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("intNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Int32 NoModify + old = baseStruct + withValue = baseStruct + withValue.Int32NoModify = 42 + + // Can set initially + st.Value(&withValue).OldValue(&old).ExpectValid() + + // Cannot modify + modified = baseStruct + modified.Int32NoModify = 100 + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("int32NoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Uint NoModify + old = baseStruct + withValue = baseStruct + withValue.UintNoModify = 42 + + // Can set initially + st.Value(&withValue).OldValue(&old).ExpectValid() + + // Cannot modify + modified = baseStruct + modified.UintNoModify = 100 + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("uintNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Bool NoModify + oldFalse := baseStruct + withTrue := baseStruct + withTrue.BoolNoModify = true + + // Can transition from false to true (unset to set) + st.Value(&withTrue).OldValue(&oldFalse).ExpectValid() + + // Cannot modify back to false + st.Value(&oldFalse).OldValue(&withTrue).ExpectValid() // This is allowed as it's set->unset + + // Float32 NoModify + old = baseStruct + withValue = baseStruct + withValue.Float32NoModify = 3.14 + + // Can set initially + st.Value(&withValue).OldValue(&old).ExpectValid() + + // Cannot modify + modified = baseStruct + modified.Float32NoModify = 2.71 + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("float32NoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Float64 NoModify + oldZero = baseStruct + withValue = baseStruct + withValue.Float64NoModify = 3.14 + + // Can transition from 0.0 to 3.14 + st.Value(&withValue).OldValue(&oldZero).ExpectValid() + + // Cannot modify to different value + modified = baseStruct + modified.Float64NoModify = 2.71 + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("float64NoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Byte NoModify + old = baseStruct + withValue = baseStruct + withValue.ByteNoModify = 255 + + // Can set initially + st.Value(&withValue).OldValue(&old).ExpectValid() + + // Cannot modify + modified = baseStruct + modified.ByteNoModify = 128 + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("byteNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Struct NoModify + withStruct := baseStruct + withStruct.StructNoModify = TestStruct{StringField: "value", IntField: 42} + + modifiedStruct := baseStruct + modifiedStruct.StructNoModify = TestStruct{StringField: "different", IntField: 100} + + // Cannot modify (struct fields are always set, never unset) + st.Value(&modifiedStruct).OldValue(&withStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("structNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // NonComparable Struct NoModify - uses reflection + // For non-pointer structs, even zero value is considered "set" + // So any change is a modification + old = baseStruct + old.NonComparableStructNoModify = NonComparableStruct{} // zero value + + withValue = baseStruct + withValue.NonComparableStructNoModify = NonComparableStruct{ + SliceField: []string{"a", "b"}, + IntField: 42, + } + + // Cannot change from zero value to non-zero (both are "set") + st.Value(&withValue).OldValue(&old).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("nonComparableStructNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Also cannot modify between two non-zero values + modified = baseStruct + modified.NonComparableStructNoModify = NonComparableStruct{ + SliceField: []string{"c", "d"}, + IntField: 100, + } + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("nonComparableStructNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Can keep the same value + st.Value(&withValue).OldValue(&withValue).ExpectValid() + + // Pointer NoSet + withSet := baseStruct + withSet.PointerNoSet = ptr.To("value") + + // Cannot set after creation (nil to non-nil) + st.Value(&withSet).OldValue(&baseStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("pointerNoSet"), nil, "field cannot be set once created").WithOrigin("update"), + }) + + // Pointer NoUnset + withPointer := baseStruct + withPointer.PointerNoUnset = ptr.To("value") + + // Can set initially + st.Value(&withPointer).OldValue(&baseStruct).ExpectValid() + + // Cannot unset (non-nil to nil) + st.Value(&baseStruct).OldValue(&withPointer).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("pointerNoUnset"), nil, "field cannot be cleared once set").WithOrigin("update"), + }) + + // Pointer NoModify + withPointer = baseStruct + withPointer.PointerNoModify = ptr.To("value") + + modifiedPointer := baseStruct + modifiedPointer.PointerNoModify = ptr.To("different") + + // Can set initially + st.Value(&withPointer).OldValue(&baseStruct).ExpectValid() + + // Can unset (NoModify allows set/unset transitions) + st.Value(&baseStruct).OldValue(&withPointer).ExpectValid() + + // Cannot modify content + st.Value(&modifiedPointer).OldValue(&withPointer).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("pointerNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Pointer Fully Restricted + withPointer = baseStruct + withPointer.PointerFullyRestricted = ptr.To("value") + + modifiedPointer = baseStruct + modifiedPointer.PointerFullyRestricted = ptr.To("different") + + // Cannot set (NoSet) + st.Value(&withPointer).OldValue(&baseStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("pointerFullyRestricted"), nil, "field cannot be set once created").WithOrigin("update"), + }) + + // Cannot unset (NoUnset) + st.Value(&baseStruct).OldValue(&withPointer).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("pointerFullyRestricted"), nil, "field cannot be cleared once set").WithOrigin("update"), + }) + + // Cannot modify (NoModify) + st.Value(&modifiedPointer).OldValue(&withPointer).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("pointerFullyRestricted"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Int Pointer NoModify + withPointer = baseStruct + withPointer.IntPointerNoModify = ptr.To(42) + + modifiedPointer = baseStruct + modifiedPointer.IntPointerNoModify = ptr.To(100) + + // Can set initially + st.Value(&withPointer).OldValue(&baseStruct).ExpectValid() + + // Can unset + st.Value(&baseStruct).OldValue(&withPointer).ExpectValid() + + // Cannot modify content + st.Value(&modifiedPointer).OldValue(&withPointer).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("intPointerNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Bool Pointer NoModify + falseVal := false + trueVal := true + + withFalse := baseStruct + withFalse.BoolPointerNoModify = &falseVal + + withTrue = baseStruct + withTrue.BoolPointerNoModify = &trueVal + + // Can set initially + st.Value(&withFalse).OldValue(&baseStruct).ExpectValid() + + // Cannot modify content + st.Value(&withTrue).OldValue(&withFalse).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("boolPointerNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Struct Pointer NoModify + withPointer = baseStruct + withPointer.StructPointerNoModify = &TestStruct{StringField: "value", IntField: 42} + + modifiedPointer = baseStruct + modifiedPointer.StructPointerNoModify = &TestStruct{StringField: "different", IntField: 100} + + // Can set initially + st.Value(&withPointer).OldValue(&baseStruct).ExpectValid() + + // Can unset + st.Value(&baseStruct).OldValue(&withPointer).ExpectValid() + + // Cannot modify content + st.Value(&modifiedPointer).OldValue(&withPointer).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("structPointerNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Custom Type NoModify + old = baseStruct + withValue = baseStruct + withValue.CustomTypeNoModify = "custom-value" + + // Can set initially + st.Value(&withValue).OldValue(&old).ExpectValid() + + // Cannot modify + modified = baseStruct + modified.CustomTypeNoModify = "different-value" + st.Value(&modified).OldValue(&withValue).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("customTypeNoModify"), nil, "field cannot be modified once set").WithOrigin("update"), + }) + + // Custom Type NoSet + old = baseStruct + withValue = baseStruct + withValue.CustomTypeNoSet = 42 + + // Cannot set + st.Value(&withValue).OldValue(&old).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("customTypeNoSet"), nil, "field cannot be set once created").WithOrigin("update"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/zz_generated.validations.go new file mode 100644 index 0000000000..61e82e9fde --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/update/primitives/zz_generated.validations.go @@ -0,0 +1,779 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package primitives + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type UpdateTestStruct + scheme.AddValidationFunc( + (*UpdateTestStruct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_UpdateTestStruct( + ctx, op, nil, /* fldPath */ + obj.(*UpdateTestStruct), + safe.Cast[*UpdateTestStruct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_UpdateTestStruct validates an instance of UpdateTestStruct according +// to declarative validation rules in the API schema. +func Validate_UpdateTestStruct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *UpdateTestStruct) (errs field.ErrorList) { + + // field UpdateTestStruct.TypeMeta has no validation + + { // field UpdateTestStruct.StringNoSet + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoSet).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return &oldObj.StringNoSet + }) + errs = append(errs, fn(fldPath.Child("stringNoSet"), &obj.StringNoSet, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.StringNoUnset + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoUnset).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return &oldObj.StringNoUnset + }) + errs = append(errs, fn(fldPath.Child("stringNoUnset"), &obj.StringNoUnset, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.StringNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return &oldObj.StringNoModify + }) + errs = append(errs, fn(fldPath.Child("stringNoModify"), &obj.StringNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.StringFullyRestricted + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoSet, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return &oldObj.StringFullyRestricted + }) + errs = append(errs, fn(fldPath.Child("stringFullyRestricted"), &obj.StringFullyRestricted, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.StringSetOnce + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a string, b string) bool { return a == b }, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return &oldObj.StringSetOnce + }) + errs = append(errs, fn(fldPath.Child("stringSetOnce"), &obj.StringSetOnce, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.IntNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a int, b int) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *int { + return &oldObj.IntNoModify + }) + errs = append(errs, fn(fldPath.Child("intNoModify"), &obj.IntNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.Int32NoModify + fn := func( + fldPath *field.Path, + obj, oldObj *int32, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a int32, b int32) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *int32 { + return &oldObj.Int32NoModify + }) + errs = append(errs, fn(fldPath.Child("int32NoModify"), &obj.Int32NoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.Int64NoModify + fn := func( + fldPath *field.Path, + obj, oldObj *int64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a int64, b int64) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *int64 { + return &oldObj.Int64NoModify + }) + errs = append(errs, fn(fldPath.Child("int64NoModify"), &obj.Int64NoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.UintNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *uint, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a uint, b uint) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *uint { + return &oldObj.UintNoModify + }) + errs = append(errs, fn(fldPath.Child("uintNoModify"), &obj.UintNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.BoolNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a bool, b bool) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *bool { + return &oldObj.BoolNoModify + }) + errs = append(errs, fn(fldPath.Child("boolNoModify"), &obj.BoolNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.Float32NoModify + fn := func( + fldPath *field.Path, + obj, oldObj *float32, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a float32, b float32) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *float32 { + return &oldObj.Float32NoModify + }) + errs = append(errs, fn(fldPath.Child("float32NoModify"), &obj.Float32NoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.Float64NoModify + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a float64, b float64) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *float64 { + return &oldObj.Float64NoModify + }) + errs = append(errs, fn(fldPath.Child("float64NoModify"), &obj.Float64NoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.ByteNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *byte, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a byte, b byte) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *byte { + return &oldObj.ByteNoModify + }) + errs = append(errs, fn(fldPath.Child("byteNoModify"), &obj.ByteNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.StructNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *TestStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateStruct(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *TestStruct { + return &oldObj.StructNoModify + }) + errs = append(errs, fn(fldPath.Child("structNoModify"), &obj.StructNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.NonComparableStructNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *NonComparableStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateStruct(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *NonComparableStruct { + return &oldObj.NonComparableStructNoModify + }) + errs = append(errs, fn(fldPath.Child("nonComparableStructNoModify"), &obj.NonComparableStructNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.PointerNoSet + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoSet).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return oldObj.PointerNoSet + }) + errs = append(errs, fn(fldPath.Child("pointerNoSet"), obj.PointerNoSet, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.PointerNoUnset + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoUnset).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return oldObj.PointerNoUnset + }) + errs = append(errs, fn(fldPath.Child("pointerNoUnset"), obj.PointerNoUnset, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.PointerNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return oldObj.PointerNoModify + }) + errs = append(errs, fn(fldPath.Child("pointerNoModify"), obj.PointerNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.PointerFullyRestricted + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoSet, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *string { + return oldObj.PointerFullyRestricted + }) + errs = append(errs, fn(fldPath.Child("pointerFullyRestricted"), obj.PointerFullyRestricted, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.IntPointerNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *int { + return oldObj.IntPointerNoModify + }) + errs = append(errs, fn(fldPath.Child("intPointerNoModify"), obj.IntPointerNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.BoolPointerNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *bool { + return oldObj.BoolPointerNoModify + }) + errs = append(errs, fn(fldPath.Child("boolPointerNoModify"), obj.BoolPointerNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.StructPointerNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *TestStruct, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdatePointer(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *TestStruct { + return oldObj.StructPointerNoModify + }) + errs = append(errs, fn(fldPath.Child("structPointerNoModify"), obj.StructPointerNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.CustomTypeNoModify + fn := func( + fldPath *field.Path, + obj, oldObj *CustomString, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a CustomString, b CustomString) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *CustomString { + return &oldObj.CustomTypeNoModify + }) + errs = append(errs, fn(fldPath.Child("customTypeNoModify"), &obj.CustomTypeNoModify, oldVal, oldObj != nil)...) + } + + { // field UpdateTestStruct.CustomTypeNoSet + fn := func( + fldPath *field.Path, + obj, oldObj *CustomInt, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj, + func(a CustomInt, b CustomInt) bool { return a == b }, validate.NoSet).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *UpdateTestStruct) *CustomInt { + return &oldObj.CustomTypeNoSet + }) + errs = append(errs, fn(fldPath.Child("customTypeNoSet"), &obj.CustomTypeNoSet, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc.go new file mode 100644 index 0000000000..9ad0c06a03 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc.go @@ -0,0 +1,33 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package validatefalse + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:validateFalse="field Struct.StringField" + StringField string `json:"stringField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc_test.go new file mode 100644 index 0000000000..3bbc4edecc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/doc_test.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 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 validatefalse + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectValidateFalseByPath(map[string][]string{ + "stringField": {"field Struct.StringField"}, + }) + // Test validation ratcheting + st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + }).ExpectValidateFalseByPath(map[string][]string{ + "stringField": {"field Struct.StringField"}, + }) + // Test validation ratcheting + st.Value(&Struct{ + StringField: "abc", + }).OldValue(&Struct{ + StringField: "abc", + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/zz_generated.validations.go new file mode 100644 index 0000000000..551b70fa22 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_false/zz_generated.validations.go @@ -0,0 +1,91 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package validatefalse + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field Struct.StringField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc.go new file mode 100644 index 0000000000..c58589c772 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc.go @@ -0,0 +1,33 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package validatetrue + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:validateTrue="field Struct.StringField" + StringField string `json:"stringField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc_test.go new file mode 100644 index 0000000000..7123c9588d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/doc_test.go @@ -0,0 +1,33 @@ +/* +Copyright 2024 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 validatetrue + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/zz_generated.validations.go new file mode 100644 index 0000000000..eea30b7e58 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validate_true/zz_generated.validations.go @@ -0,0 +1,91 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package validatetrue + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field Struct.StringField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc.go new file mode 100644 index 0000000000..1019067ece --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc.go @@ -0,0 +1,33 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package validatetruealpha + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:validateTrueAlpha="field Struct.StringField" + StringField string `json:"stringField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc_test.go new file mode 100644 index 0000000000..e741b3ab25 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/doc_test.go @@ -0,0 +1,33 @@ +/* +Copyright 2025 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 validatetruealpha + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/zz_generated.validations.go new file mode 100644 index 0000000000..670aa29bdb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruealpha/zz_generated.validations.go @@ -0,0 +1,91 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package validatetruealpha + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field Struct.StringField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc.go new file mode 100644 index 0000000000..37e863bfd3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc.go @@ -0,0 +1,33 @@ +/* +Copyright 2025 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package validatetruebeta + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + TypeMeta int + + // +k8s:validateTrueBeta="field Struct.StringField" + StringField string `json:"stringField"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc_test.go new file mode 100644 index 0000000000..534712bd5c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/doc_test.go @@ -0,0 +1,33 @@ +/* +Copyright 2025 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 validatetruebeta + +import ( + "testing" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + st.Value(&Struct{ + // All zero-values. + }).ExpectValid() + + st.Value(&Struct{ + StringField: "abc", + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/zz_generated.validations.go new file mode 100644 index 0000000000..73d5b60fa8 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/validatetruebeta/zz_generated.validations.go @@ -0,0 +1,91 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package validatetruebeta + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + // field Struct.TypeMeta has no validation + + { // field Struct.StringField + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, true, "field Struct.StringField"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.StringField + }) + errs = append(errs, fn(fldPath.Child("stringField"), &obj.StringField, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc.go new file mode 100644 index 0000000000..98cd500812 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package custommembers + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Non-discriminated zero-or-one-of union with custom member names +type Struct struct { + TypeMeta int + + NonUnionField string `json:"nonUnionField"` + + // +k8s:zeroOrOneOfMember(memberName: "CustomM1") + // +k8s:optional + M1 *M1 `json:"m1"` + + // +k8s:zeroOrOneOfMember(memberName: "CustomM2") + // +k8s:optional + M2 *M2 `json:"m2"` +} + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc_test.go new file mode 100644 index 0000000000..a8d559372e --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/doc_test.go @@ -0,0 +1,41 @@ +/* +Copyright 2024 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 custommembers + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Empty union is valid + st.Value(&Struct{}).ExpectValid() + + st.Value(&Struct{M1: &M1{}}).ExpectValid() + st.Value(&Struct{M2: &M2{}}).ExpectValid() + + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of"), + }) + + // Test validation ratcheting + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue(&Struct{M1: &M1{}, M2: &M2{}}).ExpectValid() + st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/zz_generated.validations.go new file mode 100644 index 0000000000..557da66d7d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/custom_members/zz_generated.validations.go @@ -0,0 +1,142 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package custommembers + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_zerooroneof_zerooroneof_custom_members_Struct_ = validate.NewUnionMembership(validate.NewUnionMember("m1"), validate.NewUnionMember("m2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_zerooroneof_zerooroneof_custom_members_Struct_, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.NonUnionField has no validation + + { // field Struct.M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.M1 + }) + errs = append(errs, fn(fldPath.Child("m1"), obj.M1, oldVal, oldObj != nil)...) + } + + { // field Struct.M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.M2 + }) + errs = append(errs, fn(fldPath.Child("m2"), obj.M2, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc.go new file mode 100644 index 0000000000..e04ccc0071 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package multiple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Two non-discriminated zero-or-one-of unions in the same struct +type Struct struct { + TypeMeta int + + NonUnionField string `json:"nonUnionField"` + + // +k8s:zeroOrOneOfMember(union: "union1") + // +k8s:optional + U1M1 *M1 `json:"u1m1"` + + // +k8s:zeroOrOneOfMember(union: "union1") + // +k8s:optional + U1M2 *M2 `json:"u1m2"` + + // +k8s:zeroOrOneOfMember(union: "union2") + // +k8s:optional + U2M1 *M1 `json:"u2m1"` + + // +k8s:zeroOrOneOfMember(union: "union2") + // +k8s:optional + U2M2 *M2 `json:"u2m2"` +} + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc_test.go new file mode 100644 index 0000000000..2be31c9563 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/doc_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2024 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 multiple + +import ( + "testing" + + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Both unions can be empty + st.Value(&Struct{}).ExpectValid() + + // One member from each union + st.Value(&Struct{U1M1: &M1{}, U2M1: &M1{}}).ExpectValid() + st.Value(&Struct{U1M2: &M2{}, U2M2: &M2{}}).ExpectValid() + + // One union with member, other empty + st.Value(&Struct{U1M1: &M1{}}).ExpectValid() + st.Value(&Struct{U2M2: &M2{}}).ExpectValid() + + // Multiple members in one union + st.Value(&Struct{U1M1: &M1{}, U1M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of").WithOrigin("zeroOrOneOf"), + }) + + st.Value(&Struct{U2M1: &M1{}, U2M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of").WithOrigin("zeroOrOneOf"), + }) + + st.Value(&Struct{ + U1M1: &M1{}, U1M2: &M2{}, + U2M1: &M1{}, U2M2: &M2{}, + }).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of").WithOrigin("zeroOrOneOf"), + }) + + // Test validation ratcheting + st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() + st.Value(&Struct{U1M1: &M1{}, U1M2: &M2{}}).OldValue(&Struct{U1M1: &M1{}, U1M2: &M2{}}).ExpectValid() + st.Value(&Struct{U2M1: &M1{}, U2M2: &M2{}}).OldValue(&Struct{U2M1: &M1{}, U2M2: &M2{}}).ExpectValid() + st.Value(&Struct{ + U1M1: &M1{}, U1M2: &M2{}, + U2M1: &M1{}, U2M2: &M2{}, + }).OldValue(&Struct{ + U1M1: &M1{}, U1M2: &M2{}, + U2M1: &M1{}, U2M2: &M2{}, + }).ExpectValid() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/zz_generated.validations.go new file mode 100644 index 0000000000..5eb2bfbf20 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/multiple/zz_generated.validations.go @@ -0,0 +1,214 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package multiple + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_zerooroneof_zerooroneof_multiple_Struct_union1 = validate.NewUnionMembership(validate.NewUnionMember("u1m1"), validate.NewUnionMember("u1m2")) +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_zerooroneof_zerooroneof_multiple_Struct_union2 = validate.NewUnionMembership(validate.NewUnionMember("u2m1"), validate.NewUnionMember("u2m2")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_zerooroneof_zerooroneof_multiple_Struct_union1, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U1M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U1M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_zerooroneof_zerooroneof_multiple_Struct_union2, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U2M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.U2M2 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.NonUnionField has no validation + + { // field Struct.U1M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.U1M1 + }) + errs = append(errs, fn(fldPath.Child("u1m1"), obj.U1M1, oldVal, oldObj != nil)...) + } + + { // field Struct.U1M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.U1M2 + }) + errs = append(errs, fn(fldPath.Child("u1m2"), obj.U1M2, oldVal, oldObj != nil)...) + } + + { // field Struct.U2M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.U2M1 + }) + errs = append(errs, fn(fldPath.Child("u2m1"), obj.U2M1, oldVal, oldObj != nil)...) + } + + { // field Struct.U2M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.U2M2 + }) + errs = append(errs, fn(fldPath.Child("u2m2"), obj.U2M2, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc.go new file mode 100644 index 0000000000..d0cb62252c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package simple + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Non-discriminated zero-or-one-of union +type Struct struct { + TypeMeta int + + NonUnionField string `json:"nonUnionField"` + + // +k8s:zeroOrOneOfMember + // +k8s:optional + M1 *M1 `json:"m1"` + + // +k8s:zeroOrOneOfMember + // +k8s:optional + M2 *M2 `json:"m2"` + + // +k8s:zeroOrOneOfMember + // +k8s:optional + M3 string `json:"m3"` + + // +k8s:zeroOrOneOfMember + // +k8s:optional + M4 *string `json:"m4"` +} + +type M1 struct{} + +type M2 struct{} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc_test.go new file mode 100644 index 0000000000..6764fa0654 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/doc_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2024 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 simple + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + // Empty union is valid for zeroOrOneOf + st.Value(&Struct{}).ExpectValid() + + st.Value(&Struct{M1: &M1{}}).ExpectValid() + st.Value(&Struct{M2: &M2{}}).ExpectValid() + st.Value(&Struct{M3: "a string"}).ExpectValid() + st.Value(&Struct{M4: ptr.To("a string")}).ExpectValid() + + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of").WithOrigin("zeroOrOneOf"), + }) + st.Value(&Struct{M1: &M1{}, M3: "a string"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of").WithOrigin("zeroOrOneOf"), + }) + st.Value(&Struct{M1: &M1{}, M4: ptr.To("a string")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of").WithOrigin("zeroOrOneOf"), + }) + + // Update only considers whether a field was set, not the value. + st.Value(&Struct{M3: "a string"}).OldValue(&Struct{M3: "different string"}).ExpectValid() + + // Test validation ratcheting + st.Value(&Struct{}).OldValue(&Struct{}).ExpectValid() + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue(&Struct{M1: &M1{}, M2: &M2{}}).ExpectValid() + st.Value(&Struct{M3: "a string", M2: &M2{}}).OldValue(&Struct{M3: "different string", M2: &M2{}}).ExpectValid() + + // Test update with nil old value (simulates new map entry added during update). + // Empty union is still valid for zeroOrOneOf, even with nil old value. + st.Value(&Struct{}).OldValue((*Struct)(nil)).ExpectValid() + // Multiple members set should still be caught even with nil old value. + st.Value(&Struct{M1: &M1{}, M2: &M2{}}).OldValue((*Struct)(nil)).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(nil, nil, "must specify at most one of").WithOrigin("zeroOrOneOf"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/zz_generated.validations.go new file mode 100644 index 0000000000..87be5caeef --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/zerooroneof/zerooroneof/simple/zz_generated.validations.go @@ -0,0 +1,211 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package simple + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +var zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_zerooroneof_zerooroneof_simple_Struct_ = validate.NewUnionMembership(validate.NewUnionMember("m1"), validate.NewUnionMember("m2"), validate.NewUnionMember("m3"), validate.NewUnionMember("m4")) + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + if e := validate.ZeroOrOneOfUnion(ctx, op, fldPath, obj, oldObj, zeroOrOneOfMembershipFor_k8s_io_code_generator_cmd_validation_gen_output_tests_tags_zerooroneof_zerooroneof_simple_Struct_, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M1 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M2 != nil + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + var z string + return obj.M3 != z + }, + func(obj *Struct) bool { + if obj == nil { + return false + } + return obj.M4 != nil + }); len(e) != 0 { + errs = append(errs, e...) + } + + // field Struct.TypeMeta has no validation + // field Struct.NonUnionField has no validation + + { // field Struct.M1 + fn := func( + fldPath *field.Path, + obj, oldObj *M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M1 { + return oldObj.M1 + }) + errs = append(errs, fn(fldPath.Child("m1"), obj.M1, oldVal, oldObj != nil)...) + } + + { // field Struct.M2 + fn := func( + fldPath *field.Path, + obj, oldObj *M2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *M2 { + return oldObj.M2 + }) + errs = append(errs, fn(fldPath.Child("m2"), obj.M2, oldVal, oldObj != nil)...) + } + + { // field Struct.M3 + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.M3 + }) + errs = append(errs, fn(fldPath.Child("m3"), &obj.M3, oldVal, oldObj != nil)...) + } + + { // field Struct.M4 + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.M4 + }) + errs = append(errs, fn(fldPath.Child("m4"), obj.M4, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/doc.go new file mode 100644 index 0000000000..69102603fa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/doc.go @@ -0,0 +1,55 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package typeargs + +import ( + "k8s.io/code-generator/cmd/validation-gen/output_tests/primitives" + "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +var localSchemeBuilder = testscheme.New() + +// Explicitly set the type-arg to prove it renders properly. +// NOTE: because of how validation code is generated, these must always be +// pointers, because that is what gets passed around. +type T1 struct { + TypeMeta int + + // +k8s:validateFalse(typeArg: "k8s.io/code-generator/cmd/validation-gen/output_tests/primitives.T1")="T1.S1" + S1 *primitives.T1 `json:"s1"` + // +k8s:validateFalse(typeArg: "k8s.io/code-generator/cmd/validation-gen/output_tests/primitives.T1")="PT1.PS1" + PS1 *primitives.T1 `json:"ps1"` + + // +k8s:validateFalse(typeArg: "k8s.io/code-generator/cmd/validation-gen/output_tests/type_args.E1")="T1.E1" + E1 E1 `json:"e1"` + // +k8s:validateTrue(typeArg: "k8s.io/code-generator/cmd/validation-gen/output_tests/type_args.E1")="T1.PE1" + PE1 *E1 `json:"pe1"` + + // +k8s:validateFalse(typeArg: "int")="T1.I1" + I1 int `json:"i1"` + // +k8s:validateTrue(typeArg: "int")="T1.PI1" + PI1 *int `json:"pi1"` +} + +// +k8s:validateFalse="type E1" +type E1 string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/testdata/validate-false.json new file mode 100644 index 0000000000..53d0f98df2 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/testdata/validate-false.json @@ -0,0 +1,98 @@ +{ + "*typeargs.T1": { + "e1": [ + "T1.E1", + "type E1" + ], + "i1": [ + "T1.I1" + ], + "pe1": [ + "type E1" + ], + "ps1": [ + "PT1.PS1" + ], + "ps1.anothert2.b": [ + "field T2.B" + ], + "ps1.anothert2.f": [ + "field T2.F" + ], + "ps1.anothert2.i": [ + "field T2.I" + ], + "ps1.anothert2.s": [ + "field T2.S" + ], + "ps1.b": [ + "field T1.B" + ], + "ps1.f": [ + "field T1.F" + ], + "ps1.i": [ + "field T1.I" + ], + "ps1.s": [ + "field T1.S" + ], + "ps1.t2": [ + "field T1.T2" + ], + "ps1.t2.b": [ + "field T2.B" + ], + "ps1.t2.f": [ + "field T2.F" + ], + "ps1.t2.i": [ + "field T2.I" + ], + "ps1.t2.s": [ + "field T2.S" + ], + "s1": [ + "T1.S1" + ], + "s1.anothert2.b": [ + "field T2.B" + ], + "s1.anothert2.f": [ + "field T2.F" + ], + "s1.anothert2.i": [ + "field T2.I" + ], + "s1.anothert2.s": [ + "field T2.S" + ], + "s1.b": [ + "field T1.B" + ], + "s1.f": [ + "field T1.F" + ], + "s1.i": [ + "field T1.I" + ], + "s1.s": [ + "field T1.S" + ], + "s1.t2": [ + "field T1.T2" + ], + "s1.t2.b": [ + "field T2.B" + ], + "s1.t2.f": [ + "field T2.F" + ], + "s1.t2.i": [ + "field T2.I" + ], + "s1.t2.s": [ + "field T2.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/zz_generated.validations.go new file mode 100644 index 0000000000..ddad383707 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/zz_generated.validations.go @@ -0,0 +1,233 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typeargs + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + primitives "k8s.io/code-generator/cmd/validation-gen/output_tests/primitives" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_E1 validates an instance of E1 according +// to declarative validation rules in the API schema. +func Validate_E1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E1) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type E1"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + // field T1.TypeMeta has no validation + + { // field T1.S1 + fn := func( + fldPath *field.Path, + obj, oldObj *primitives.T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult[*primitives.T1](ctx, op, fldPath, obj, oldObj, false, "T1.S1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, primitives.Validate_T1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *primitives.T1 { + return oldObj.S1 + }) + errs = append(errs, fn(fldPath.Child("s1"), obj.S1, oldVal, oldObj != nil)...) + } + + { // field T1.PS1 + fn := func( + fldPath *field.Path, + obj, oldObj *primitives.T1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult[*primitives.T1](ctx, op, fldPath, obj, oldObj, false, "PT1.PS1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, primitives.Validate_T1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *primitives.T1 { + return oldObj.PS1 + }) + errs = append(errs, fn(fldPath.Child("ps1"), obj.PS1, oldVal, oldObj != nil)...) + } + + { // field T1.E1 + fn := func( + fldPath *field.Path, + obj, oldObj *E1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult[*E1](ctx, op, fldPath, obj, oldObj, false, "T1.E1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E1 { + return &oldObj.E1 + }) + errs = append(errs, fn(fldPath.Child("e1"), &obj.E1, oldVal, oldObj != nil)...) + } + + { // field T1.PE1 + fn := func( + fldPath *field.Path, + obj, oldObj *E1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult[*E1](ctx, op, fldPath, obj, oldObj, true, "T1.PE1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E1 { + return oldObj.PE1 + }) + errs = append(errs, fn(fldPath.Child("pe1"), obj.PE1, oldVal, oldObj != nil)...) + } + + { // field T1.I1 + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult[*int](ctx, op, fldPath, obj, oldObj, false, "T1.I1"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *int { + return &oldObj.I1 + }) + errs = append(errs, fn(fldPath.Child("i1"), &obj.I1, oldVal, oldObj != nil)...) + } + + { // field T1.PI1 + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult[*int](ctx, op, fldPath, obj, oldObj, true, "T1.PI1"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *int { + return oldObj.PI1 + }) + errs = append(errs, fn(fldPath.Child("pi1"), obj.PI1, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/zz_generated.validations_test.go new file mode 100644 index 0000000000..15940447e7 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/type_args/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typeargs + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/doc.go new file mode 100644 index 0000000000..f97a544f27 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/doc.go @@ -0,0 +1,75 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=TypesWithField=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package typedefs + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// +k8s:validateFalse="type E1" +type E1 string + +// +k8s:validateFalse="type E2" +type E2 int + +// +k8s:validateFalse="type E3" +type E3 E1 + +// +k8s:validateFalse="type E4" +type E4 T2 + +// +k8s:validateFalse="type T1" +type T1 struct { + TypeMeta int + + // +k8s:validateFalse="field T1.E1" + E1 E1 `json:"e1"` + // +k8s:validateFalse="field T1.PE1" + PE1 *E1 `json:"pe1"` + + // +k8s:validateFalse="field T1.E2" + E2 E2 `json:"e2"` + // +k8s:validateFalse="field T1.PE2" + PE2 *E2 `json:"pe2"` + + // +k8s:validateFalse="field T1.E3" + E3 E3 `json:"e3"` + // +k8s:validateFalse="field T1.PE3" + PE3 *E3 `json:"pe3"` + + // +k8s:validateFalse="field T1.E4" + E4 E4 `json:"e4"` + // +k8s:validateFalse="field T1.PE4" + PE4 *E4 `json:"pe4"` + + // +k8s:validateFalse="field T1.T2" + T2 T2 `json:"t2"` + // +k8s:validateFalse="field T1.PT2" + PT2 *T2 `json:"pt2"` +} + +// +k8s:validateFalse="type T2" +type T2 struct { + // +k8s:validateFalse="field T2.S" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/testdata/validate-false.json new file mode 100644 index 0000000000..45d5766bdd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/testdata/validate-false.json @@ -0,0 +1,59 @@ +{ + "*typedefs.T1": { + "": [ + "type T1" + ], + "e1": [ + "field T1.E1", + "type E1" + ], + "e2": [ + "field T1.E2", + "type E2" + ], + "e3": [ + "field T1.E3", + "type E3" + ], + "e4": [ + "field T1.E4", + "type E4" + ], + "e4.s": [ + "field T2.S" + ], + "pe1": [ + "field T1.PE1", + "type E1" + ], + "pe2": [ + "field T1.PE2", + "type E2" + ], + "pe3": [ + "field T1.PE3", + "type E3" + ], + "pe4": [ + "field T1.PE4", + "type E4" + ], + "pe4.s": [ + "field T2.S" + ], + "pt2": [ + "field T1.PT2", + "type T2" + ], + "pt2.s": [ + "field T2.S" + ], + "t2": [ + "field T1.T2", + "type T2" + ], + "t2.s": [ + "field T2.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/zz_generated.validations.go new file mode 100644 index 0000000000..ca5c5b891a --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/zz_generated.validations.go @@ -0,0 +1,444 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedefs + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_E1 validates an instance of E1 according +// to declarative validation rules in the API schema. +func Validate_E1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E1) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type E1"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_E2 validates an instance of E2 according +// to declarative validation rules in the API schema. +func Validate_E2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E2) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type E2"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_E3 validates an instance of E3 according +// to declarative validation rules in the API schema. +func Validate_E3( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E3) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type E3"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_E4 validates an instance of E4 according +// to declarative validation rules in the API schema. +func Validate_E4( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *E4) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type E4"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field E4.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *E4) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T1"); len(e) != 0 { + errs = append(errs, e...) + } + + // field T1.TypeMeta has no validation + + { // field T1.E1 + fn := func( + fldPath *field.Path, + obj, oldObj *E1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.E1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E1 { + return &oldObj.E1 + }) + errs = append(errs, fn(fldPath.Child("e1"), &obj.E1, oldVal, oldObj != nil)...) + } + + { // field T1.PE1 + fn := func( + fldPath *field.Path, + obj, oldObj *E1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PE1"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E1(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E1 { + return oldObj.PE1 + }) + errs = append(errs, fn(fldPath.Child("pe1"), obj.PE1, oldVal, oldObj != nil)...) + } + + { // field T1.E2 + fn := func( + fldPath *field.Path, + obj, oldObj *E2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.E2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E2 { + return &oldObj.E2 + }) + errs = append(errs, fn(fldPath.Child("e2"), &obj.E2, oldVal, oldObj != nil)...) + } + + { // field T1.PE2 + fn := func( + fldPath *field.Path, + obj, oldObj *E2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PE2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E2 { + return oldObj.PE2 + }) + errs = append(errs, fn(fldPath.Child("pe2"), obj.PE2, oldVal, oldObj != nil)...) + } + + { // field T1.E3 + fn := func( + fldPath *field.Path, + obj, oldObj *E3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.E3"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E3(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E3 { + return &oldObj.E3 + }) + errs = append(errs, fn(fldPath.Child("e3"), &obj.E3, oldVal, oldObj != nil)...) + } + + { // field T1.PE3 + fn := func( + fldPath *field.Path, + obj, oldObj *E3, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PE3"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E3(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E3 { + return oldObj.PE3 + }) + errs = append(errs, fn(fldPath.Child("pe3"), obj.PE3, oldVal, oldObj != nil)...) + } + + { // field T1.E4 + fn := func( + fldPath *field.Path, + obj, oldObj *E4, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.E4"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E4(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E4 { + return &oldObj.E4 + }) + errs = append(errs, fn(fldPath.Child("e4"), &obj.E4, oldVal, oldObj != nil)...) + } + + { // field T1.PE4 + fn := func( + fldPath *field.Path, + obj, oldObj *E4, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PE4"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_E4(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *E4 { + return oldObj.PE4 + }) + errs = append(errs, fn(fldPath.Child("pe4"), obj.PE4, oldVal, oldObj != nil)...) + } + + { // field T1.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.T2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + { // field T1.PT2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T1.PT2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) *T2 { + return oldObj.PT2 + }) + errs = append(errs, fn(fldPath.Child("pt2"), obj.PT2, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T2"); len(e) != 0 { + errs = append(errs, e...) + } + + { // field T2.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/zz_generated.validations_test.go new file mode 100644 index 0000000000..13aa49d8a1 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/typedefs/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package typedefs + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/doc.go new file mode 100644 index 0000000000..4b2ddba4ee --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/doc.go @@ -0,0 +1,66 @@ +/* +Copyright 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. +*/ + +// +k8s:validation-gen=TypesWithSuffix=Request +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package primitives + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type StructRequest struct { + // +k8s:validateFalse="field StructRequest.S" + S string `json:"s"` + + // +k8s:validateFalse="field StructRequest.T2" + T2 T2 `json:"t2"` + + // No internal validations. + T3 T3 `json:"t3"` +} + +// Note: This has validations and is linked into the type-graph of StructRequest. +type T2 struct { + // +k8s:validateFalse="field T2.I" + I int `json:"i"` +} + +// Note: This has no validations and is linked into the type-graph of StructRequest. +type T3 struct { + B bool `json:"b"` + F float64 `json:"f"` +} + +// Note: This has validations and is not linked into the type-graph of +// StructRequest. +type T4 struct { + // +k8s:validateFalse="field T4.S" + S string `json:"s"` + // +k8s:validateFalse="field T4.I" + I int `json:"i"` + // +k8s:validateFalse="field T4.B" + B bool `json:"b"` + // +k8s:validateFalse="field T4.F" + F float64 `json:"f"` +} + +// +k8s:validateFalse="type EnumRequest" +type EnumRequest string diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/testdata/validate-false.json new file mode 100644 index 0000000000..a692993ae4 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/testdata/validate-false.json @@ -0,0 +1,18 @@ +{ + "*primitives.EnumRequest": { + "": [ + "type EnumRequest" + ] + }, + "*primitives.StructRequest": { + "s": [ + "field StructRequest.S" + ], + "t2": [ + "field StructRequest.T2" + ], + "t2.i": [ + "field T2.I" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/zz_generated.validations.go new file mode 100644 index 0000000000..e5ea729e15 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/zz_generated.validations.go @@ -0,0 +1,177 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package primitives + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type EnumRequest + scheme.AddValidationFunc( + (*EnumRequest)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_EnumRequest( + ctx, op, nil, /* fldPath */ + obj.(*EnumRequest), + safe.Cast[*EnumRequest](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type StructRequest + scheme.AddValidationFunc( + (*StructRequest)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_StructRequest( + ctx, op, nil, /* fldPath */ + obj.(*StructRequest), + safe.Cast[*StructRequest](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_EnumRequest validates an instance of EnumRequest according +// to declarative validation rules in the API schema. +func Validate_EnumRequest( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *EnumRequest) (errs field.ErrorList) { + + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type EnumRequest"); len(e) != 0 { + errs = append(errs, e...) + } + + return errs +} + +// Validate_StructRequest validates an instance of StructRequest according +// to declarative validation rules in the API schema. +func Validate_StructRequest( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *StructRequest) (errs field.ErrorList) { + + { // field StructRequest.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field StructRequest.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructRequest) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field StructRequest.T2 + fn := func( + fldPath *field.Path, + obj, oldObj *T2, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field StructRequest.T2"); len(e) != 0 { + errs = append(errs, e...) + } + // call the type's validation function + errs = append(errs, Validate_T2(ctx, op, fldPath, obj, oldObj)...) + return + } + oldVal := safe.Field(oldObj, + func(oldObj *StructRequest) *T2 { + return &oldObj.T2 + }) + errs = append(errs, fn(fldPath.Child("t2"), &obj.T2, oldVal, oldObj != nil)...) + } + + // field StructRequest.T3 has no validation + return errs +} + +// Validate_T2 validates an instance of T2 according +// to declarative validation rules in the API schema. +func Validate_T2( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T2) (errs field.ErrorList) { + + { // field T2.I + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T2.I"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T2) *int { + return &oldObj.I + }) + errs = append(errs, fn(fldPath.Child("i"), &obj.I, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/zz_generated.validations_test.go new file mode 100644 index 0000000000..550334b185 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/types_with_suffix/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package primitives + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/doc.go new file mode 100644 index 0000000000..2ea277d170 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/doc.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package lists + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + LM1 []M1 `json:"lm1"` +} + +type M1 struct { + // +k8s:validateFalse="M1.S" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/testdata/validate-false.json new file mode 100644 index 0000000000..1657294497 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/testdata/validate-false.json @@ -0,0 +1,15 @@ +{ + "*lists.M1": { + "s": [ + "M1.S" + ] + }, + "*lists.T1": { + "lm1[0].s": [ + "M1.S" + ], + "lm1[1].s": [ + "M1.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/zz_generated.validations.go new file mode 100644 index 0000000000..7374822e41 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/zz_generated.validations.go @@ -0,0 +1,138 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package lists + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type M1 + scheme.AddValidationFunc( + (*M1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_M1( + ctx, op, nil, /* fldPath */ + obj.(*M1), + safe.Cast[*M1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_M1 validates an instance of M1 according +// to declarative validation rules in the API schema. +func Validate_M1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *M1) (errs field.ErrorList) { + + { // field M1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "M1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *M1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.LM1 + fn := func( + fldPath *field.Path, + obj, oldObj []M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the list and call the type's validation function + if e := validate.EachValSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_M1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) []M1 { + return oldObj.LM1 + }) + errs = append(errs, fn(fldPath.Child("lm1"), obj.LM1, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/zz_generated.validations_test.go new file mode 100644 index 0000000000..a9e21dc747 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/lists/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package lists + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/doc.go new file mode 100644 index 0000000000..ff88bd638d --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/doc.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +// +k8s:validation-gen-nolint +package maps + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type T1 struct { + MSM1 map[string]M1 `json:"msm1"` +} + +type M1 struct { + // +k8s:validateFalse="M1.S" + S string `json:"s"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/testdata/validate-false.json b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/testdata/validate-false.json new file mode 100644 index 0000000000..1b140e6f39 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/testdata/validate-false.json @@ -0,0 +1,15 @@ +{ + "*maps.M1": { + "s": [ + "M1.S" + ] + }, + "*maps.T1": { + "msm1[ƒ岯Ȉ\u0026\u003c沲3镟Ō仲牚輠ɟɛ].s": [ + "M1.S" + ], + "msm1[Ȱ轷N].s": [ + "M1.S" + ] + } +} \ No newline at end of file diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/zz_generated.validations.go new file mode 100644 index 0000000000..02a6379422 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/zz_generated.validations.go @@ -0,0 +1,138 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maps + +import ( + context "context" + fmt "fmt" + + equality "k8s.io/apimachinery/pkg/api/equality" + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type M1 + scheme.AddValidationFunc( + (*M1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_M1( + ctx, op, nil, /* fldPath */ + obj.(*M1), + safe.Cast[*M1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + // type T1 + scheme.AddValidationFunc( + (*T1)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T1( + ctx, op, nil, /* fldPath */ + obj.(*T1), + safe.Cast[*T1](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_M1 validates an instance of M1 according +// to declarative validation rules in the API schema. +func Validate_M1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *M1) (errs field.ErrorList) { + + { // field M1.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "M1.S"); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *M1) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + return errs +} + +// Validate_T1 validates an instance of T1 according +// to declarative validation rules in the API schema. +func Validate_T1( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *T1) (errs field.ErrorList) { + + { // field T1.MSM1 + fn := func( + fldPath *field.Path, + obj, oldObj map[string]M1, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + } + // iterate the map and call the value type's validation function + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, Validate_M1); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *T1) map[string]M1 { + return oldObj.MSM1 + }) + errs = append(errs, fn(fldPath.Child("msm1"), obj.MSM1, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/zz_generated.validations_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/zz_generated.validations_test.go new file mode 100644 index 0000000000..12393a0ccd --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/maps/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package maps + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc.go new file mode 100644 index 0000000000..56bb38bd29 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc.go @@ -0,0 +1,40 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// Package primitivepointers is a test package. +// +// +k8s:validation-gen-nolint +// +//nolint:unused +package primitivepointers + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + // +k8s:immutable + SP *string `json:"sp"` + // +k8s:immutable + IP *int `json:"ip"` + // +k8s:immutable + BP *bool `json:"bp"` + // +k8s:immutable + FP *float64 `json:"fp"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc_test.go new file mode 100644 index 0000000000..fb0e735218 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/doc_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 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 primitivepointers + +import ( + "testing" + + field "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/utils/ptr" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + structA1 := Struct{ + SP: ptr.To("zero"), + IP: ptr.To(0), + BP: ptr.To(false), + FP: ptr.To(0.0), + } + + // Same data, different pointers + structA2 := Struct{ + SP: ptr.To("zero"), + IP: ptr.To(0), + BP: ptr.To(false), + FP: ptr.To(0.0), + } + // Different data. + structB := Struct{ + SP: ptr.To("one"), + IP: ptr.To(1), + BP: ptr.To(true), + FP: ptr.To(1.1), + } + + st.Value(&structA1).OldValue(&structA1).ExpectValid() + + st.Value(&structA1).OldValue(&structA2).ExpectValid() + + st.Value(&structA1).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("sp"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("ip"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("bp"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("fp"), nil, "").WithOrigin("immutable"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/zz_generated.validations.go new file mode 100644 index 0000000000..6d5021e475 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitive_pointers/zz_generated.validations.go @@ -0,0 +1,181 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package primitivepointers + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + { // field Struct.SP + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return oldObj.SP + }) + errs = append(errs, fn(fldPath.Child("sp"), obj.SP, oldVal, oldObj != nil)...) + } + + { // field Struct.IP + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return oldObj.IP + }) + errs = append(errs, fn(fldPath.Child("ip"), obj.IP, oldVal, oldObj != nil)...) + } + + { // field Struct.BP + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return oldObj.BP + }) + errs = append(errs, fn(fldPath.Child("bp"), obj.BP, oldVal, oldObj != nil)...) + } + + { // field Struct.FP + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *float64 { + return oldObj.FP + }) + errs = append(errs, fn(fldPath.Child("fp"), obj.FP, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc.go new file mode 100644 index 0000000000..a0d9ec2fae --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc.go @@ -0,0 +1,37 @@ +/* +Copyright 2024 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. +*/ + +// +k8s:validation-gen=* +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme + +// This is a test package. +// +k8s:validation-gen-nolint +package primitives + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +type Struct struct { + // +k8s:immutable + S string `json:"s"` + // +k8s:immutable + I int `json:"i"` + // +k8s:immutable + B bool `json:"b"` + // +k8s:immutable + F float64 `json:"f"` +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc_test.go new file mode 100644 index 0000000000..1a2ac506d3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/doc_test.go @@ -0,0 +1,51 @@ +/* +Copyright 2024 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 primitives + +import ( + "testing" + + field "k8s.io/apimachinery/pkg/util/validation/field" +) + +func Test(t *testing.T) { + st := localSchemeBuilder.Test(t) + + structA := Struct{ + S: "zero", + I: 0, + B: false, + F: 0.0, + } + + // Different data. + structB := Struct{ + S: "one", + I: 1, + B: true, + F: 1.1, + } + + st.Value(&structA).OldValue(&structA).ExpectValid() + + st.Value(&structA).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{ + field.Invalid(field.NewPath("s"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("i"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("b"), nil, "").WithOrigin("immutable"), + field.Invalid(field.NewPath("f"), nil, "").WithOrigin("immutable"), + }) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/zz_generated.validations.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/zz_generated.validations.go new file mode 100644 index 0000000000..c4f213b209 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/output_tests/update_validations/primitives/zz_generated.validations.go @@ -0,0 +1,181 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by validation-gen. DO NOT EDIT. + +package primitives + +import ( + context "context" + fmt "fmt" + + operation "k8s.io/apimachinery/pkg/api/operation" + safe "k8s.io/apimachinery/pkg/api/safe" + validate "k8s.io/apimachinery/pkg/api/validate" + field "k8s.io/apimachinery/pkg/util/validation/field" + testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme" +) + +func init() { localSchemeBuilder.Register(RegisterValidations) } + +// RegisterValidations adds validation functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterValidations(scheme *testscheme.Scheme) error { + // type Struct + scheme.AddValidationFunc( + (*Struct)(nil), + func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_Struct( + ctx, op, nil, /* fldPath */ + obj.(*Struct), + safe.Cast[*Struct](oldObj)) + } + return field.ErrorList{ + field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath())), + } + }) + return nil +} + +// Validate_Struct validates an instance of Struct according +// to declarative validation rules in the API schema. +func Validate_Struct( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *Struct) (errs field.ErrorList) { + + { // field Struct.S + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *string { + return &oldObj.S + }) + errs = append(errs, fn(fldPath.Child("s"), &obj.S, oldVal, oldObj != nil)...) + } + + { // field Struct.I + fn := func( + fldPath *field.Path, + obj, oldObj *int, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *int { + return &oldObj.I + }) + errs = append(errs, fn(fldPath.Child("i"), &obj.I, oldVal, oldObj != nil)...) + } + + { // field Struct.B + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *bool { + return &oldObj.B + }) + errs = append(errs, fn(fldPath.Child("b"), &obj.B, oldVal, oldObj != nil)...) + } + + { // field Struct.F + fn := func( + fldPath *field.Path, + obj, oldObj *float64, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *Struct) *float64 { + return &oldObj.F + }) + errs = append(errs, fn(fldPath.Child("f"), &obj.F, oldVal, oldObj != nil)...) + } + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/targets.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/targets.go new file mode 100644 index 0000000000..90692c7f40 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/targets.go @@ -0,0 +1,616 @@ +/* +Copyright 2024 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 main + +import ( + "cmp" + "fmt" + "reflect" + "slices" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/code-generator/cmd/validation-gen/validators" + "k8s.io/code-generator/pkg/apidefinitions" + "k8s.io/gengo/v2" + "k8s.io/gengo/v2/codetags" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +// These are the comment tags that carry parameters for validation generation. +const ( + // Defines which types to generate validation for. There are two places + // this can be used: + // Per-package: + // * "*": generate validation for all types in this package + // * "TypesWithField=FooBar": generate validation for all types with a + // field named "FooBar" + // * "TypesWithSuffix=FooBar": generate validation for all types whose + // name ends with "FooBar" + // Per-type: + // * "true": generate validation for this type + // * "false": do not generate validation for this type + mainTagName = "k8s:validation-gen" + // Defines the type of the scheme used to register validations. Defaults to + // "k8s.io/apimachinery/pkg.runtime.Scheme", but can be set to another type + // (e.g. in tests), or set to "nil" to disable scheme registration for this + // package. + schemeRegistryTagName = "k8s:validation-gen-scheme-registry" + // Defines the deep-equal function used for equivalence and ratcheting checks. + // Defaults to "k8s.io/apimachinery/pkg/api/equality.Semantic.DeepEqual". + deepEqualFuncTagName = "k8s:validation-gen-deep-equal-func" + // If set, generate go test files for test fixtures. Supported values: "validateFalse". + testFixtureTagName = "k8s:validation-gen-test-fixture" + + // name of the subresource that this type represents and can validate declaratively. + isSubresourceTagName = "k8s:isSubresource" + + // name of a subresource that this type can validate declaratively, tag may be + // repeated to support multiple subresources. + supportsSubresourceTagName = "k8s:supportsSubresource" + + // if set on a package, generates declarative coverage test targets even if it's not a versioned API package. + generateTestTargetsTagName = "k8s:validation-gen-test-targets" +) + +var ( + runtimePkg = "k8s.io/apimachinery/pkg/runtime" + schemeType = types.Name{Package: runtimePkg, Name: "Scheme"} + defaultDeepEqualFunc = types.Name{Package: "k8s.io/apimachinery/pkg/api/equality", Name: "Semantic.DeepEqual"} + metav1Pkg = "k8s.io/apimachinery/pkg/apis/meta/v1" + listMetaType = types.Name{Package: metav1Pkg, Name: "ListMeta"} +) + +// extractAndParseTag extracts all the values for a given tag, according to the +// tag grammar. +func extractAndParseTag(tagName string, comments []string) ([]codetags.Tag, error) { + extracted := codetags.Extract("+", comments) + var tags []codetags.Tag + for key, lines := range extracted { + if key != tagName { + continue + } + t, err := codetags.ParseAll(lines) + if err != nil { + return nil, fmt.Errorf("failed to parse tags: %w: %s", err, lines) + } + tags = append(tags, t...) + } + return tags, nil +} + +// validationTypeMatch returns the +k8s:validation-gen tag values for pkg, +// or false if validation-gen should not run. +func validationTypeMatch(pkg *types.Package, idOpts []apidefinitions.Option) ([]string, bool) { + info, err := apidefinitions.Identify(pkg, apidefinitions.Validation, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + return nil, false + } + return info.TypeFilters(), true +} + +// TODO: this can just accept a single bool +func checkMainTag(comments []string, require ...string) bool { + // TODO: convert to extractAndParseTag() and update all callers to use quoted values + tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{mainTagName}, comments) + if err != nil { + klog.Fatalf("Failed to extract tags: %v", err) + } + values, found := tags[mainTagName] + if !found { + return false + } + + if len(require) == 0 { + return len(values) == 1 && values[0].Value == "" + } + + valueStrings := make([]string, len(values)) + for i, tag := range values { + valueStrings[i] = tag.Value + } + + return reflect.DeepEqual(valueStrings, require) +} + +func schemeRegistryTag(pkg *types.Package) (types.Name, bool) { + // TODO: convert to extractAndParseTag() and update all callers to use quoted values + tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{schemeRegistryTagName}, pkg.Comments) + if err != nil { + klog.Fatalf("Failed to extract scheme registry tags: %v", err) + } + values, found := tags[schemeRegistryTagName] + if !found || len(values) == 0 { + return schemeType, true // default + } + if len(values) > 1 { + panic(fmt.Sprintf("Package %q contains more than one usage of %q", pkg.Path, schemeRegistryTagName)) + } + val := values[0].Value + if val == "nil" { + // no registration wanted for this package + return types.Name{}, false + } + return types.ParseFullyQualifiedName(val), true +} + +// registerScheme reports whether pkg registers its validations with a scheme. +func registerScheme(pkg *types.Package) bool { + _, ok := schemeRegistryTag(pkg) + return ok +} + +func deepEqualFuncTag(pkg *types.Package) types.Name { + // TODO: convert to extractAndParseTag() and update all callers to use quoted values + tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{deepEqualFuncTagName}, pkg.Comments) + if err != nil { + klog.Fatalf("Failed to extract deep equal func tags: %v", err) + } + values, found := tags[deepEqualFuncTagName] + if !found || len(values) == 0 { + return defaultDeepEqualFunc + } + if len(values) > 1 { + panic(fmt.Sprintf("Package %q contains more than one usage of %q", pkg.Path, deepEqualFuncTagName)) + } + val := values[0].Value + if val == "" { + return defaultDeepEqualFunc + } + return parseDeepEqualFunc(val) +} + +func parseDeepEqualFunc(val string) types.Name { + lastSlash := strings.LastIndex(val, "/") + if lastSlash == -1 { + dot := strings.Index(val, ".") + if dot == -1 { + return types.Name{Name: val} + } + return types.Name{ + Package: val[:dot], + Name: val[dot+1:], + } + } + dot := lastSlash + strings.Index(val[lastSlash:], ".") + if dot == lastSlash { + return types.Name{Package: val} + } + return types.Name{ + Package: val[:dot], + Name: val[dot+1:], + } +} + +func isSubresourceTag(t *types.Type) (string, bool) { + var comments []string + comments = append(comments, t.SecondClosestCommentLines...) + comments = append(comments, t.CommentLines...) + tags, err := extractAndParseTag(isSubresourceTagName, comments) + if err != nil { + klog.Fatalf("Failed to extract isSubresource tags: %v", err) + } + if len(tags) == 0 { + return "", false + } + if len(tags) > 1 { + panic(fmt.Sprintf("Type %q contains more than one usage of %q", t.Name.String(), isSubresourceTagName)) + } + return tags[0].Value, true +} + +func supportedSubresourceTags(t *types.Type) sets.Set[string] { + var comments []string + comments = append(comments, t.SecondClosestCommentLines...) + comments = append(comments, t.CommentLines...) + tags, err := extractAndParseTag(supportsSubresourceTagName, comments) + if err != nil { + klog.Fatalf("Failed to extract supportedSubresource tags: %v", err) + } + if len(tags) == 0 { + return sets.New[string]() + } + subresources := sets.New[string]() + for _, tag := range tags { + subresources.Insert(tag.Value) + } + return subresources +} + +var testFixtureTagValues = sets.New("validateFalse") + +func testFixtureTag(pkg *types.Package) sets.Set[string] { + result := sets.New[string]() + // TODO: convert to extractAndParseTag() and update all callers to use quoted values + tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{testFixtureTagName}, pkg.Comments) + if err != nil { + klog.Fatalf("Failed to extract test fixture tags: %v", err) + } + values, found := tags[testFixtureTagName] + if !found { + return result + } + + for _, tag := range values { + if !testFixtureTagValues.Has(tag.Value) { + panic(fmt.Sprintf("Package %q: %s must be one of '%s', but got: %s", pkg.Path, testFixtureTagName, testFixtureTagValues.UnsortedList(), tag.Value)) + } + result.Insert(tag.Value) + } + return result +} + +func generateTestTargetsTag(pkg *types.Package) bool { + tags, err := gengo.ExtractFunctionStyleCommentTags("+", []string{generateTestTargetsTagName}, pkg.Comments) + if err != nil { + klog.Fatalf("Failed to extract %s tags: %v", generateTestTargetsTagName, err) + } + _, found := tags[generateTestTargetsTagName] + return found +} + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{ + "public": namer.NewPublicNamer(1), + "raw": namer.NewRawNamer("", nil), + "objectvalidationfn": validationFnNamer(), + "private": namer.NewPrivateNamer(0), + "name": namer.NewPublicNamer(0), + } +} + +func validationFnNamer() *namer.NameStrategy { + return &namer.NameStrategy{ + Prefix: "Validate_", + Join: func(pre string, in []string, post string) string { + return pre + strings.Join(in, "_") + post + }, + } +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +func GetTargets(context *generator.Context, args *Args) []generator.Target { + boilerplate, err := gengo.GoBoilerplate(args.GoHeaderFile, gengo.StdBuildTag, gengo.StdGeneratedBy) + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + var idOpts []apidefinitions.Option + if len(args.LintRules) > 0 { + idOpts = append(idOpts, apidefinitions.WithLintRules(args.LintRules...)) + } + + var targetList []generator.Target + + // First load other "input" packages. We do this as a single call because + // it is MUCH faster. + inputPkgs := make([]string, 0, len(context.Inputs)) + pkgToInput := map[string]string{} + inputToCanonicalPkg := map[string]string{} // types package -> the output package cross-package references resolve to + for _, input := range context.Inputs { + klog.V(4).Infof("considering pkg %q", input) + pkg := context.Universe[input] + + info, err := apidefinitions.Identify(pkg, apidefinitions.Validation, idOpts...) + if err != nil { + klog.Fatal(err) + } + if !info.ShouldGenerate() { + continue + } + + // +k8s:validation-gen-input may direct the generator at types in + // a different package than the one where validators will be emitted. + inputPath := info.ExternalTypes() + pkgToInput[input] = inputPath + if inputPath != pkg.Path { + klog.V(4).Infof(" input pkg %v", inputPath) + inputPkgs = append(inputPkgs, inputPath) + } + // An input's validation may be generated into more than one package. One + // is canonical -- the package cross-package references resolve to. The + // registering package is canonical; if none registers, the first one seen + // wins. At most one package may register. + if prev, ok := inputToCanonicalPkg[inputPath]; !ok { + inputToCanonicalPkg[inputPath] = input + } else if registerScheme(pkg) { + if registerScheme(context.Universe[prev]) { + klog.Fatalf("input %q is generated into two registering packages (%q, %q); mark one +k8s:validation-gen-scheme-registry=nil", inputPath, prev, input) + } + inputToCanonicalPkg[inputPath] = input // a registering package displaces a non-registering one + } + } + + // Make sure explicit extra-packages are added. + var readOnlyPkgs []string + for _, pkg := range args.ReadOnlyPkgs { + // In case someone specifies an extra as a path into vendor, convert + // it to its "real" package path. + if i := strings.Index(pkg, "/vendor/"); i != -1 { + pkg = pkg[i+len("/vendor/"):] + } + readOnlyPkgs = append(readOnlyPkgs, pkg) + } + if expanded, err := context.FindPackages(readOnlyPkgs...); err != nil { + klog.Fatalf("cannot find extra packages: %v", err) + } else { + readOnlyPkgs = expanded // now in fully canonical form + } + for _, extra := range readOnlyPkgs { + inputPkgs = append(inputPkgs, extra) + pkgToInput[extra] = extra + // Don't let a read-only package override a generation mapping. + if _, ok := inputToCanonicalPkg[extra]; !ok { + inputToCanonicalPkg[extra] = extra + } + } + + if len(inputPkgs) > 0 { + if _, err := context.LoadPackages(inputPkgs...); err != nil { + klog.Fatalf("cannot load packages: %v", err) + } + } + // update context.Order to the latest context.Universe + orderer := namer.Orderer{Namer: namer.NewPublicNamer(1)} + context.Order = orderer.OrderUniverse(context.Universe) + + // Initialize all validator plugins exactly once. + validator := validators.InitGlobalValidator(context, inputToCanonicalPkg) + + // Create a type discoverer for all types of all inputs. + td := NewTypeDiscoverer(validator, inputToCanonicalPkg) + if err := td.Init(context); err != nil { + klog.Fatalf("Error discovering constants: %v", err) + } + + // Create a linter to collect errors as we go. + linter := newLinter(lintRules(validator)...) + + // groupKindReports accumulates Reports across every input, keyed by + // GroupKind so testTargets emits exactly one SimpleTarget per Kind. + groupKindReports := map[schema.GroupKind][]*report{} + + // Build a cache of type->callNode for every type we need. + for _, input := range context.Inputs { + klog.V(2).InfoS("processing", "pkg", input) + + pkg := context.Universe[input] + + schemeRegistry, registerThisPkg := schemeRegistryTag(pkg) + deepEqualFunc := deepEqualFuncTag(pkg) + + criteria, found := validationTypeMatch(pkg, idOpts) + if !found { + klog.V(2).InfoS(" did not find required tag", "tag", mainTagName) + continue + } + if len(criteria) == 1 && criteria[0] == "" { + klog.Fatalf("%s: found package tag %q with no value", input, mainTagName) + } + for _, crit := range criteria { + if crit == "*" { + continue + } + if val, found := strings.CutPrefix(crit, "TypesWithField="); found { + if val == "" { + klog.Fatalf("%s: found package tag \"%s=%s\" with empty value", input, mainTagName, crit) + } + continue + } + if val, found := strings.CutPrefix(crit, "TypesWithSuffix="); found { + if val == "" { + klog.Fatalf("%s: found package tag \"%s=%s\" with empty value", input, mainTagName, crit) + } + continue + } + klog.Fatalf("%s: unknown value for package tag %q: %q", input, mainTagName, crit) + } + shouldCreateObjectValidationFn := func(t *types.Type) bool { + // Never generate validation for unexported types. + if namer.IsPrivateGoName(t.Name.Name) { + return false + } + // opt-out + if checkMainTag(t.CommentLines, "false") { + return false + } + if checkMainTag(t.SecondClosestCommentLines, "false") { + return false + } + // opt-in + if checkMainTag(t.CommentLines, "true") { + return true + } + if checkMainTag(t.SecondClosestCommentLines, "true") { + return true + } + + // skip types that embed metav1.ListMeta + if t.Kind == types.Struct { + for _, member := range t.Members { + if member.Embedded && member.Type.Name == listMetaType { + return false + } + } + } + + // all types + for _, v := range criteria { + if v == "*" { + return true + } + if field, found := strings.CutPrefix(v, "TypesWithField="); found { + if isTypeWithField(t, field) { + return true + } + } + if field, found := strings.CutPrefix(v, "TypesWithSuffix="); found { + if isTypeWithSuffix(t, field) { + return true + } + } + } + return false + } + + // Find the right input pkg, which might not be this one. + inputPath := pkgToInput[input] + // typesPkg is where the types that need validation are defined. + // Sometimes it is different from pkg. For example, kubernetes core/v1 + // types are defined in k8s.io/api/core/v1, while the pkg which holds + // defaulter code is at k/k/pkg/api/v1. + typesPkg := context.Universe[inputPath] + + // Figure out which types we should be considering further. + var rootTypes []*types.Type + for _, t := range typesPkg.Types { + if shouldCreateObjectValidationFn(t) { + rootTypes = append(rootTypes, t) + } else { + klog.V(6).InfoS("skipping type", "type", t) + } + } + // Deterministic ordering helps in logs and debugging. + slices.SortFunc(rootTypes, func(a, b *types.Type) int { + return cmp.Compare(a.Name.String(), b.Name.String()) + }) + + for _, t := range rootTypes { + klog.V(3).InfoS("pre-processing", "type", t) + if err := td.DiscoverType(t); err != nil { + klog.Fatalf("failed to generate validations: %v", err) + } + } + + extracted := codetags.Extract("+", pkg.Comments) + if _, ok := extracted["k8s:validation-gen-nolint"]; !ok { + for _, t := range rootTypes { + klog.V(3).InfoS("linting root-type", "type", t) + if err := linter.lintType(t); err != nil { + klog.Fatalf("failed to lint type %q: %v", t.Name, err) + } + } + } + + targetList = append(targetList, + &generator.SimpleTarget{ + PkgName: pkg.Name, + PkgPath: pkg.Path, + PkgDir: pkg.Dir, // output pkg is the same as the input + HeaderComment: boilerplate, + + FilterFunc: func(c *generator.Context, t *types.Type) bool { + return t.Name.Package == typesPkg.Path + }, + + GeneratorsFunc: func(c *generator.Context) (generators []generator.Generator) { + generators = []generator.Generator{ + NewGenValidations(args.OutputFile, pkg.Path, typesPkg.Path, rootTypes, td, inputToCanonicalPkg, schemeRegistry, registerThisPkg, deepEqualFunc), + } + testFixtureTags := testFixtureTag(pkg) + if testFixtureTags.Len() > 0 { + if !strings.HasSuffix(args.OutputFile, ".go") { + panic(fmt.Sprintf("%s requires that output file have .go suffix", testFixtureTagName)) + } + filename := args.OutputFile[0:len(args.OutputFile)-3] + "_test.go" + generators = append(generators, FixtureTests(filename, testFixtureTags)) + } + if generateTestTargetsTag(pkg) { + var reports []*report + for _, t := range rootTypes { + rules := collectRules(td.typeNodes[t]) + if len(rules) == 0 { + continue + } + reports = append(reports, &report{ + Group: pkg.Path, + Version: pkg.Name, + Kind: t.Name.Name, + Rules: rules, + }) + } + if len(reports) > 0 { + filename := args.OutputFile[0:len(args.OutputFile)-3] + "_coverage_test.go" + generators = append(generators, newCoverageTestGen(pkg.Path, filename, reports, true, nil)) + } + } + return generators + }, + }) + + // Accumulate per-Kind rules; testTargets emits after the loop. + // Only the registering package contributes coverage; a non-registering + // package generated from the same input has identical rules, so counting + // it too would double-list the version for the Kind. + if args.TestOutputRoot != "" && registerThisPkg { + collectReports(typesPkg, rootTypes, td, groupKindReports) + } + } + + // All inputs processed: fail if a ValidateCustom_* function lacks a tag. + if err := validators.VerifyCustomValidationsHaveTags(); err != nil { + klog.Fatalf("%v", err) + } + + // Emit per-Kind coverage test targets. No-op when --test-output-root is empty. + allowlist, err := loadAllowlist(args.TestAllowlist) + if err != nil { + klog.Fatalf("loading allowlist: %v", err) + } + targetList = append(targetList, testTargets(args.TestOutputRoot, args.TestOutputFilePrefix, groupKindReports, allowlist, boilerplate)...) + + if len(linter.lintErrors) > 0 { + buf := strings.Builder{} + + for t, errs := range linter.lintErrors { + buf.WriteString(fmt.Sprintf(" type %v:\n", t)) + for _, err := range errs { + buf.WriteString(fmt.Sprintf(" %s\n", err.Error())) + } + } + klog.Fatalf("lint failed:\n%s", buf.String()) + } + return targetList +} + +func isTypeWithField(t *types.Type, fieldName string) bool { + if t.Kind == types.Struct { + for _, field := range t.Members { + if field.Name == fieldName { + return true + } + } + } + return false +} + +func isTypeWithSuffix(t *types.Type, suffix string) bool { + return strings.HasSuffix(t.Name.Name, suffix) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/targets_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/targets_test.go new file mode 100644 index 0000000000..ff67234ed3 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/targets_test.go @@ -0,0 +1,64 @@ +/* +Copyright 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 main + +import ( + "testing" + + "k8s.io/gengo/v2/types" +) + +func TestParseDeepEqualFunc(t *testing.T) { + testCases := []struct { + name string + input string + expected types.Name + }{{ + name: "fully qualified with struct field", + input: "k8s.io/apimachinery/pkg/api/equality.Semantic.DeepEqual", + expected: types.Name{Package: "k8s.io/apimachinery/pkg/api/equality", Name: "Semantic.DeepEqual"}, + }, { + name: "fully qualified package function", + input: "k8s.io/code-generator/cmd/validation-gen/testscheme.CustomEqual", + expected: types.Name{Package: "k8s.io/code-generator/cmd/validation-gen/testscheme", Name: "CustomEqual"}, + }, { + name: "single level package function", + input: "testscheme.CustomEqual", + expected: types.Name{Package: "testscheme", Name: "CustomEqual"}, + }, { + name: "single level package struct field", + input: "testscheme.Semantic.DeepEqual", + expected: types.Name{Package: "testscheme", Name: "Semantic.DeepEqual"}, + }, { + name: "package-local function", + input: "CustomDeepEqual", + expected: types.Name{Package: "", Name: "CustomDeepEqual"}, + }, { + name: "empty input", + input: "", + expected: types.Name{Package: "", Name: ""}, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := parseDeepEqualFunc(tc.input) + if want, got := tc.expected, result; got != want { + t.Errorf("expected %v, got %v", want, got) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/test_targets.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/test_targets.go new file mode 100644 index 0000000000..8d9c1d34bc --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/test_targets.go @@ -0,0 +1,480 @@ +/* +Copyright 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 main + +import ( + "cmp" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "slices" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/code-generator/cmd/validation-gen/validators" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/types" + "sigs.k8s.io/yaml" +) + +// Package symbols referenced in emitted test fixtures. fmtPkgSymbols (shared +// with validation.go) covers Fprintln. +var ( + schemaPkg = "k8s.io/apimachinery/pkg/runtime/schema" + schemaPkgSymbols = mkPkgNames(schemaPkg, "GroupVersionKind") + osPkgSymbols = mkPkgNames("os", "Stderr", "Exit") + testingSymbols = mkPkgNames("testing", "M") + runtimeTestPkg = "k8s.io/apimachinery/pkg/test/coverage" + runtimeRegisterSymbols = mkPkgNames(runtimeTestPkg, "RegisterDeclaredRules", "FieldRules") + runtimeAssertCovSymbols = mkPkgNames(runtimeTestPkg, "AssertDeclarativeCoverage") +) + +// apiVersionRe matches Kubernetes-style API versions: v1, v1alpha1, v2beta3. +// Used to detect whether an input package is a versioned API package (vs. a +// helper or main package, which we skip). +var apiVersionRe = regexp.MustCompile(`^v\d+(alpha\d+|beta\d+)?$`) + +// rule is one declared field-validation error. +type rule struct { + ErrorType string + Origin string +} + +// fieldRules maps field path → declared rules for a single Kind. +type fieldRules map[string][]rule + +// report is one GVK's declared rules. Used as in-memory scaffolding while +// emitting per-Kind test fixtures; not serialized. +type report struct { + Group string + Version string + Kind string + Rules fieldRules +} + +// allowlistEntry filters a declared rule out of coverage fixture generation. +// Every field is required: apiVersion matches by literal equality (in the +// "/" form, or just "" for core); kind/path/ +// errorType/origin match literally or wildcard with "*". Empty is rejected +// at load time so an entry can never silently overexclude. +type allowlistEntry struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Path string `json:"path"` + ErrorType string `json:"errorType"` + Origin string `json:"origin"` + Reason string `json:"reason"` +} + +// groupVersion splits e.APIVersion into (group, version). "v1" → ("", "v1"); +// "apps/v1" → ("apps", "v1"). +func (e *allowlistEntry) groupVersion() (string, string) { + if i := strings.IndexByte(e.APIVersion, '/'); i >= 0 { + return e.APIVersion[:i], e.APIVersion[i+1:] + } + return "", e.APIVersion +} + +// matches reports whether the entry filters out the given rule. apiVersion +// must match exactly; the remaining fields match by literal equality or +// wildcard with "*". +func (e *allowlistEntry) matches(group, version, kind, path string, rule rule) bool { + g, v := e.groupVersion() + return g == group && + v == version && + (e.Kind == "*" || e.Kind == kind) && + (e.Path == "*" || e.Path == path) && + (e.ErrorType == "*" || e.ErrorType == rule.ErrorType) && + (e.Origin == "*" || e.Origin == rule.Origin) +} + +// filterReports returns reports with allowlisted rules removed. Reports +// left with no rules are dropped. Returns reports unchanged if allowlist is empty. +func filterReports(reports []*report, allowlist []allowlistEntry) []*report { + if len(allowlist) == 0 { + return reports + } + out := make([]*report, 0, len(reports)) + for _, r := range reports { + filtered := fieldRules{} + for path, rs := range r.Rules { + for _, rule := range rs { + if slices.ContainsFunc(allowlist, func(e allowlistEntry) bool { + return e.matches(r.Group, r.Version, r.Kind, path, rule) + }) { + continue + } + filtered[path] = append(filtered[path], rule) + } + } + if len(filtered) == 0 { + continue + } + out = append(out, &report{ + Group: r.Group, Version: r.Version, Kind: r.Kind, Rules: filtered, + }) + } + return out +} + +// loadAllowlist reads the YAML file at path. Returns nil when path is empty. +// Errors on read/parse failure or any entry missing the required reason. +func loadAllowlist(path string) ([]allowlistEntry, error) { + if path == "" { + return nil, nil + } + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading allowlist %q: %w", path, err) + } + var entries []allowlistEntry + if err := yaml.Unmarshal(data, &entries); err != nil { + return nil, fmt.Errorf("parsing allowlist %q: %w", path, err) + } + for i, e := range entries { + for _, f := range []struct{ name, val string }{ + {"apiVersion", e.APIVersion}, + {"kind", e.Kind}, + {"path", e.Path}, + {"errorType", e.ErrorType}, + {"origin", e.Origin}, + {"reason", e.Reason}, + } { + if strings.TrimSpace(f.val) == "" { + return nil, fmt.Errorf("allowlist %q entry %d: %s is required (use %q to wildcard)", path, i, f.name, "*") + } + } + } + return entries, nil +} + +// coverageTestGen emits test files for validation-gen coverage. +// It can emit rule registrations (init func), apiVersions variable, and TestMain. +type coverageTestGen struct { + generator.GoGenerator + outputPackage string + imports namer.ImportTracker + reports []*report + emitMain bool + versions []string +} + +func newCoverageTestGen(outputPackage, filename string, reports []*report, emitMain bool, versions []string) generator.Generator { + return &coverageTestGen{ + GoGenerator: generator.GoGenerator{ + OutputFilename: filename, + }, + outputPackage: outputPackage, + imports: generator.NewImportTrackerForPackage(outputPackage), + reports: reports, + emitMain: emitMain, + versions: versions, + } +} + +func (g *coverageTestGen) Namers(*generator.Context) namer.NameSystems { + return namer.NameSystems{"raw": namer.NewRawNamer(g.outputPackage, g.imports)} +} + +func (g *coverageTestGen) Imports(*generator.Context) []string { return g.imports.ImportLines() } + +func (g *coverageTestGen) Filter(*generator.Context, *types.Type) bool { return false } + +func emitRegisterDeclaredRules(sw *generator.SnippetWriter, c *generator.Context, reports []*report) { + for _, r := range reports { + args := generator.Args{ + "schema": mkSymbolArgs(c, schemaPkgSymbols), + "runtime": mkSymbolArgs(c, runtimeRegisterSymbols), + "group": strconv.Quote(r.Group), + "version": strconv.Quote(r.Version), + "kind": strconv.Quote(r.Kind), + } + sw.Do("func init() {\n", nil) + sw.Do(" $.runtime.RegisterDeclaredRules|raw$(\n", args) + sw.Do(" $.schema.GroupVersionKind|raw${Group: $.group$, Version: $.version$, Kind: $.kind$},\n", args) + sw.Do(" $.runtime.FieldRules|raw${\n", args) + + paths := make([]string, 0, len(r.Rules)) + for p := range r.Rules { + paths = append(paths, p) + } + slices.Sort(paths) + for _, path := range paths { + rules := slices.Clone(r.Rules[path]) + slices.SortFunc(rules, func(a, b rule) int { + if c := cmp.Compare(a.ErrorType, b.ErrorType); c != 0 { + return c + } + return cmp.Compare(a.Origin, b.Origin) + }) + sw.Do(" $.path$: {\n", generator.Args{"path": strconv.Quote(path)}) + for _, r := range rules { + ruleArgs := generator.Args{"errorType": strconv.Quote(r.ErrorType)} + if r.Origin != "" { + ruleArgs["origin"] = strconv.Quote(r.Origin) + sw.Do(" {ErrorType: $.errorType$, Origin: $.origin$},\n", ruleArgs) + } else { + sw.Do(" {ErrorType: $.errorType$},\n", ruleArgs) + } + } + sw.Do(" },\n", nil) + } + sw.Do(" },\n )\n}\n\n", nil) + } +} + +func emitTestMain(sw *generator.SnippetWriter, c *generator.Context) { + args := generator.Args{ + "testing": mkSymbolArgs(c, testingSymbols), + "fmt": mkSymbolArgs(c, fmtPkgSymbols), + "os": mkSymbolArgs(c, osPkgSymbols), + "runtime": mkSymbolArgs(c, runtimeAssertCovSymbols), + } + sw.Do("func TestMain(m *$.testing.M|raw$) {\n", args) + sw.Do(" code := m.Run()\n", nil) + sw.Do(" if err := $.runtime.AssertDeclarativeCoverage|raw$(); err != nil {\n", args) + sw.Do(" $.fmt.Fprintln|raw$($.os.Stderr|raw$, err)\n", args) + sw.Do(" if code == 0 {\n code = 1\n }\n }\n", nil) + sw.Do(" $.os.Exit|raw$(code)\n}\n", args) +} + +func (g *coverageTestGen) Init(c *generator.Context, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + if len(g.reports) > 0 { + emitRegisterDeclaredRules(sw, c, g.reports) + } + + if g.emitMain { + if len(g.versions) > 0 { + versions := slices.Clone(g.versions) + slices.Sort(versions) + sw.Do("var apiVersions = []string{", nil) + for i, v := range versions { + if i > 0 { + sw.Do(", ", nil) + } + sw.Do("$.v$", generator.Args{"v": strconv.Quote(v)}) + } + sw.Do("}\n\n", nil) + } + emitTestMain(sw, c) + } + return sw.Error() +} + +// collectReports appends a *report into groupKindReports for each +// Kind in pkg with at least one declared rule. Records all packages; testTargets +// filters out non-API packages (empty Version) at emit time. +func collectReports(pkg *types.Package, rootTypes []*types.Type, td *typeDiscoverer, groupKindReports map[schema.GroupKind][]*report) { + // Derive (group, version) from the input package: + // group: the GroupName const if defined (the established API + // convention); otherwise the package path as a fallback. + // version: the package name when it looks like an API version + // (vN[alphaM|betaM]); empty otherwise — testTargets uses the + // empty version to skip non-API packages. + var group, version string + if c, ok := pkg.Constants["GroupName"]; ok && c.ConstValue != nil { + group = *c.ConstValue + } else { + group = pkg.Path + } + if apiVersionRe.MatchString(pkg.Name) { + version = pkg.Name + } + + for _, t := range rootTypes { + rules := collectRules(td.typeNodes[t]) + if len(rules) == 0 { + continue + } + kind := t.Name.Name + gk := schema.GroupKind{Group: group, Kind: kind} + groupKindReports[gk] = append(groupKindReports[gk], &report{ + Group: group, + Version: version, + Kind: kind, + Rules: rules, + }) + } +} + +// testTargets returns one SimpleTarget per Kind in groupKindReports. Each +// target's directory is /// +// and contains one _test.go per version plus a +// shared main_test.go. Skips Kinds with empty Version +// (non-API packages) and Kinds whose every rule is allowlisted. +func testTargets(testOutputRoot, filePrefix string, groupKindReports map[schema.GroupKind][]*report, allowlist []allowlistEntry, boilerplate []byte) []generator.Target { + if testOutputRoot == "" || len(groupKindReports) == 0 { + return nil + } + out := make([]generator.Target, 0, len(groupKindReports)) + for _, reports := range groupKindReports { + // reports is non-empty by construction in collectReports. + first := reports[0] + if first.Version == "" { + continue // not a real API package + } + reports = filterReports(reports, allowlist) + if len(reports) == 0 { + continue // every rule was allowlisted away + } + lowerKind := strings.ToLower(first.Kind) + // Short group name (first DNS label) as the directory; "core" for the + // empty legacy group — matches client-gen / informer-gen convention. + group := first.Group + if group == "" { + group = "core" + } + pkgDir := filepath.Join(testOutputRoot, strings.Split(group, ".")[0], lowerKind) + + out = append(out, &generator.SimpleTarget{ + PkgName: lowerKind, + PkgPath: pkgDir, // informational; gengo writes to PkgDir + PkgDir: pkgDir, + HeaderComment: boilerplate, + GeneratorsFunc: func(*generator.Context) []generator.Generator { + gens := make([]generator.Generator, 0, len(reports)+1) // +1 for main_test.go + versions := make([]string, 0, len(reports)) + for _, r := range reports { + gens = append(gens, newCoverageTestGen(pkgDir, filePrefix+r.Version+"_test.go", []*report{r}, false, nil)) + versions = append(versions, r.Version) + } + gens = append(gens, newCoverageTestGen(pkgDir, filePrefix+"main_test.go", nil, true, versions)) + return gens + }}) + } + return out +} + +// collectRules walks node's type tree and returns its declared FieldRules. +func collectRules(node *typeNode) fieldRules { + if node == nil { + return nil + } + rules := fieldRules{} + seen := map[*typeNode]bool{} + + record := func(path string, fns []validators.FunctionGen) { + for _, fn := range fns { + recordRules(rules, path, fn, "") + } + } + + var walkNode func(*typeNode, string, bool, bool) + var walkChild func(*childNode, string, bool, bool) + walkNode = func(n *typeNode, path string, skipElem, skipKey bool) { + if n == nil || seen[n] { + return + } + seen[n] = true + defer delete(seen, n) + + record(path, n.typeValidations.Functions) + if n.valueType == nil { + return + } + + if n.typeValidations.OpaqueType { + return + } + + skipElem = skipElem || n.typeValidations.OpaqueValType + skipKey = skipKey || n.typeValidations.OpaqueKeyType + + record(joinPath(path, "[*]"), n.typeValIterations.Functions) + record(path, n.typeKeyIterations.Functions) // keys validate at parent path + + for _, fld := range n.fields { + walkChild(fld, joinPath(path, fld.jsonName), false, false) + } + if n.elem != nil && !skipElem { + walkChild(n.elem, joinPath(path, "[*]"), false, false) + } + if n.key != nil && !skipKey { + walkChild(n.key, path, false, false) + } + if n.underlying != nil { + walkChild(n.underlying, path, skipElem, skipKey) + } + } + walkChild = func(c *childNode, path string, skipElem, skipKey bool) { + if c == nil { + return + } + record(path, c.fieldValidations.Functions) + record(joinPath(path, "[*]"), c.fieldValIterations.Functions) + record(path, c.fieldKeyIterations.Functions) + + if c.fieldValidations.OpaqueType { + return + } + + skipElem = skipElem || c.fieldValidations.OpaqueValType + skipKey = skipKey || c.fieldValidations.OpaqueKeyType + + walkNode(c.node, path, skipElem, skipKey) + } + walkNode(node, "", false, false) + return rules +} + +// recordRules descends fg, accumulating Wrapper/MultiWrapperFunction +// PathFragments into suffix, and records (basePath+suffix, Rule) at each +// emitting leaf. A FunctionGen may declare multiple Emissions when the +// runtime emits errors of different types or at different path fragments +// from the same call (e.g. ValSliceUpdate with NoAddItem and NoRemoveItem). +func recordRules(rules fieldRules, basePath string, fg validators.FunctionGen, suffix string) { + if len(fg.Emits) > 0 { + for _, e := range fg.Emits { + path := basePath + suffix + e.PathFragment + rules[path] = append(rules[path], rule{ + ErrorType: string(e.Type), + Origin: e.Origin, + }) + } + return + } + for _, arg := range fg.Args { + switch a := arg.(type) { + case validators.WrapperFunction: + recordRules(rules, basePath, a.Function, suffix+a.PathFragment) + case validators.MultiWrapperFunction: + for _, child := range a.Functions { + recordRules(rules, basePath, child, suffix+a.PathFragment) + } + } + } +} + +// joinPath joins seg onto base; empty seg is a no-op so inline-embedded +// struct fields (jsonName "") stay transparent. +func joinPath(base, seg string) string { + if seg == "" { + return base + } + if base == "" { + return seg + } + if strings.HasPrefix(seg, "[") { + return base + seg + } + return base + "." + seg +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/testscheme/doc.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/testscheme/doc.go new file mode 100644 index 0000000000..618fb7f72c --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/testscheme/doc.go @@ -0,0 +1,107 @@ +/* +Copyright 2024 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 testscheme provides a scheme implementation and test utilities +// useful for writing output_tests for validation-gen. +// +// For an output test to use this scheme, it should be located in a dedicated go package. +// The go package should have validation-gen and this test scheme enabled and must declare a +// 'localSchemeBuilder' using the 'testscheme.New()' function. For example: +// +// // +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// // +k8s:validation-gen +// package example +// import "k8s.io/code-generator/cmd/validation-gen/testscheme" +// var localSchemeBuilder = testscheme.New() +// +// This is sufficient for validation-gen to generate a `zz_generated.validations.go` for the types +// in the package that compile. +// +// With the scheme enabled. An output test may be tested either by handwritten test code or +// by generated test fixtures. +// +// For example, to test by hand. The testschema provides utilities to create a value and assert +// that the expected errors are returned when the value is validated. +// Note that if `OldValue()` or `OldValueFuzzed()` is called on the `ValidationTester`, subsequent +// validation calls will implicitly use update validation. +// +// func Test(t *testing.T) { +// st := localSchemeBuilder.Test(t) +// st.Value(&T1{ +// E0: "x", +// PE0: pointer.To(E0("y")), +// }).ExpectInvalid( +// field.NotSupported(field.NewPath("e0"), "x", []string{EnumValue1, EnumValue2}), +// field.NotSupported(field.NewPath("pe0"), "y", []string{EnumValue1, EnumValue2}))} +// } +// +// Tests fixtures can also be enabled. For example: +// +// // ... +// // +k8s:validation-gen-test-fixture=validateFalse +// // package example +// +// When a test fixture is enabled, a `zz_generated.validations_test.go` file will be generated +// test for all the registered types in the package according to the behavior of the named test +// fixture(s). +// +// Test Fixtures: +// +// `validateFalse` - This test fixture executes validation of each registered type and accumulates +// all `validateFalse` validation errors. For example: +// +// type T1 struct { +// // +k8s:validateFalse="field T1.S" +// S string `json:"s"` +// // +k8s:validateFalse="field T1.T" +// T T2 `json:"t"` +// } +// +// The above `example.T1` test type has two validated fields: `s` and 't'. The fields are named +// according to the Go "json" field tag, and each has a validation error identifier +// provided by `+k8s:validateFalse=`. +// +// The `validateFalse` test fixture will validate an instance of `example.T1`, generated using fuzzed +// data, and then group the validation errors by field name. Represented in JSON like: +// +// { +// "*example.T1": { +// "s": [ +// "field T1.S" +// ], +// "t": [ +// "field T1.T" +// ] +// } +// } +// +// This validation error data contains an object for each registered type, keyed by type name. +// For each registered type, the validation errors from `+k8s:validateFalse` tags are grouped +// by field path with all validation error identifiers collected into a list. +// +// This data is compared with the expected validation results that are defined in +// a `testdata/validate-false.json` file in the same package, and any differences are +// reported as test errors. +// +// `testdata/validate-false.json` can be generated automatically by setting the +// `UPDATE_VALIDATION_GEN_FIXTURE_DATA=true` environment variable to true when running the tests. +// +// Test authors that generated `testdata/validate-false.json` are expected to ensure that file +// is correct before checking it in to source control. +// +// The fuzzed data is generated pseudo-randomly with a consistent seed, with all nilable fields se +// to a value, and with a single entry for each map and a single element for each slice. +package testscheme diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/testscheme/testscheme.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/testscheme/testscheme.go new file mode 100644 index 0000000000..add7a3daaa --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/testscheme/testscheme.go @@ -0,0 +1,394 @@ +/* +Copyright 2024 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 testscheme + +import ( + "bytes" + stdcmp "cmp" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "math/rand" + "os" + "path" + "reflect" + "sort" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" // nolint:depguard // this package provides test utilities + "github.com/google/go-cmp/cmp/cmpopts" // nolint:depguard // this package provides test utilities + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/test/coverage" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/randfill" +) + +// Scheme is similar to runtime.Scheme, but for validation testing purposes. Scheme only supports validation, +// supports registration of any type (not just runtime.Object) and implements Register directly, allowing it +// to also be used as a scheme builder. +// Must only be used with tests that perform all registration before calls to validate. +type Scheme struct { + validationFuncs map[reflect.Type]func(ctx context.Context, op operation.Operation, object, oldObject interface{}) field.ErrorList + registrationErrors field.ErrorList +} + +// New creates a new Scheme. +func New() *Scheme { + return &Scheme{validationFuncs: map[reflect.Type]func(ctx context.Context, op operation.Operation, object interface{}, oldObject interface{}) field.ErrorList{}} +} + +// AddValidationFunc registers a validation function. +// Last writer wins. +func (s *Scheme) AddValidationFunc(srcType any, fn func(ctx context.Context, op operation.Operation, object, oldObject interface{}) field.ErrorList) { + s.validationFuncs[reflect.TypeOf(srcType)] = fn +} + +// Validate validates an object using the registered validation function. +func (s *Scheme) Validate(ctx context.Context, options map[string]bool, object any, subresources ...string) field.ErrorList { + if len(s.registrationErrors) > 0 { + return s.registrationErrors // short circuit with registration errors if any are present + } + if fn, ok := s.validationFuncs[reflect.TypeOf(object)]; ok { + return fn(ctx, operation.Operation{Type: operation.Create, Request: operation.Request{Subresources: subresources}, Options: options}, object, nil) + } + return nil +} + +// ValidateUpdate validates an update to an object using the registered validation function. +func (s *Scheme) ValidateUpdate(ctx context.Context, options map[string]bool, object, oldObject any, subresources ...string) field.ErrorList { + if len(s.registrationErrors) > 0 { + return s.registrationErrors // short circuit with registration errors if any are present + } + if fn, ok := s.validationFuncs[reflect.TypeOf(object)]; ok { + return fn(ctx, operation.Operation{Type: operation.Update, Request: operation.Request{Subresources: subresources}, Options: options}, object, oldObject) + } + return nil +} + +// Register adds a scheme setup function to the list. +func (s *Scheme) Register(funcs ...func(*Scheme) error) { + for _, f := range funcs { + err := f(s) + if err != nil { + s.registrationErrors = append(s.registrationErrors, toRegistrationError(err)) + } + } +} + +func toRegistrationError(err error) *field.Error { + return field.InternalError(nil, fmt.Errorf("registration error: %w", err)) +} + +// Test returns a ValidationTestBuilder for this scheme. +func (s *Scheme) Test(t *testing.T) *ValidationTestBuilder { + return &ValidationTestBuilder{t, s} +} + +// ValidationTestBuilder provides convenience functions to build +// validation tests. +type ValidationTestBuilder struct { + *testing.T + s *Scheme +} + +const fixtureEnvVar = "UPDATE_VALIDATION_GEN_FIXTURE_DATA" + +// ValidateFixtures ensures that the validation errors of all registered types match what is expected by the test fixture files. +// For each registered type, a value is created for the type, and populated by fuzzing the value, before validating the type. +// See ValueFuzzed for details. +// +// If the UPDATE_VALIDATION_GEN_FIXTURE_DATA=true environment variable is set, test fixture files are created or overridden. +// +// Fixtures: +// - validate-false.json: defines a map of registered type to a map of field path to +validateFalse validations args +// that are expected to be returned as errors when the type is validated. +func (s *ValidationTestBuilder) ValidateFixtures() { + s.T.Helper() + + flag := os.Getenv(fixtureEnvVar) + // Run validation + got := map[string]map[string][]string{} + for t := range s.s.validationFuncs { + var v any + // TODO: this should handle maps and slices + if t.Kind() == reflect.Ptr { + v = reflect.New(t.Elem()).Interface() + } else { + v = reflect.Indirect(reflect.New(t)).Interface() + } + if reflect.TypeOf(v).Kind() != reflect.Ptr { + v = &v + } + s.ValueFuzzed(v) + vt := &ValidationTester{ValidationTestBuilder: s, value: v} + byPath := vt.validateFalseArgsByPath() + got[t.String()] = byPath + } + + testdataFilename := "testdata/validate-false.json" + if flag == "true" { + // Generate fixture file + if err := os.MkdirAll(path.Dir(testdataFilename), os.FileMode(0755)); err != nil { + s.Fatal("error making directory", err) + } + data, err := json.MarshalIndent(got, "", " ") + if err != nil { + s.Fatal(err) + } + err = os.WriteFile(testdataFilename, data, os.FileMode(0644)) + if err != nil { + s.Fatal(err) + } + } else { + // Load fixture file + testdataFile, err := os.Open(testdataFilename) + if errors.Is(err, os.ErrNotExist) { + s.Fatalf("%s test fixture data not found. Run go test with the environment variable %s=true to create test fixture data.", + testdataFilename, fixtureEnvVar) + } else if err != nil { + s.Fatal(err) + } + defer func() { + err := testdataFile.Close() + if err != nil { + s.Fatal(err) + } + }() + + byteValue, err := io.ReadAll(testdataFile) + if err != nil { + s.Fatal(err) + } + testdata := map[string]map[string][]string{} + err = json.Unmarshal(byteValue, &testdata) + if err != nil { + s.Fatal(err) + } + // Compare fixture with validation results + expectedKeys := sets.New[string]() + gotKeys := sets.New[string]() + for k := range got { + gotKeys.Insert(k) + } + hasErrors := false + for k, expectedForType := range testdata { + expectedKeys.Insert(k) + gotForType, ok := got[k] + s.T.Run(k, func(t *testing.T) { + t.Helper() + + if !ok { + t.Errorf("%q has expected validateFalse args in %s but got no validation errors.", k, testdataFilename) + hasErrors = true + } else if !cmp.Equal(gotForType, expectedForType) { + t.Errorf("validateFalse args, grouped by field path, differed from %s:\n%s\n", + testdataFilename, cmp.Diff(gotForType, expectedForType, cmpopts.SortMaps(stdcmp.Less[string]))) + hasErrors = true + } + }) + } + for unexpectedType := range gotKeys.Difference(expectedKeys) { + s.T.Run(unexpectedType, func(t *testing.T) { + t.Helper() + + t.Errorf("%q got unexpected validateFalse args, grouped by field path:\n%s\n", + unexpectedType, cmp.Diff(nil, got[unexpectedType], cmpopts.SortMaps(stdcmp.Less[string]))) + hasErrors = true + }) + } + if hasErrors { + s.T.Logf("If the test expectations have changed, run go test with the environment variable %s=true", fixtureEnvVar) + } + } +} + +func randfiller() *randfill.Filler { + // Ensure that lists and maps are not empty and use a deterministic seed. + // But also, don't recurse infinitely. + return randfill.New().NilChance(0.0).NumElements(2, 2).MaxDepth(8).RandSource(rand.NewSource(0)) +} + +// ValueFuzzed automatically populates the given value using a deterministic filler. +// The filler sets pointers to values and always includes a two map keys and slice elements. +func (s *ValidationTestBuilder) ValueFuzzed(value any) *ValidationTester { + randfiller().Fill(value) + return &ValidationTester{ValidationTestBuilder: s, value: value} +} + +// Value returns a ValidationTester for the given value. The value +// must be a registered with the scheme for validation. +func (s *ValidationTestBuilder) Value(value any) *ValidationTester { + return &ValidationTester{ValidationTestBuilder: s, value: value} +} + +// ValidationTester provides convenience functions to define validation +// tests for a validatable value. +type ValidationTester struct { + *ValidationTestBuilder + value any + oldValue any + isUpdate bool + options map[string]bool + subresources []string +} + +// OldValue sets the oldValue for this ValidationTester. When oldValue is set, +// update validation will be used to test validation. +// oldValue must be the same type as value. +// Returns ValidationTester to support call chaining. +func (v *ValidationTester) OldValue(oldValue any) *ValidationTester { + v.oldValue = oldValue + v.isUpdate = true + return v +} + +// OldValueFuzzed automatically populates the given value using a deterministic filler. +// The filler sets pointers to values and always includes a two map keys and slice elements. +func (v *ValidationTester) OldValueFuzzed(oldValue any) *ValidationTester { + randfiller().Fill(oldValue) + v.oldValue = oldValue + v.isUpdate = true + return v +} + +// Opts sets the ValidationOpts to use. +func (v *ValidationTester) Opts(options map[string]bool) *ValidationTester { + v.options = options + return v +} + +// Subresource sets the ValidationOpts to use. +func (v *ValidationTester) Subresources(subresources []string) *ValidationTester { + v.subresources = subresources + return v +} + +func multiline(errs field.ErrorList) string { + if len(errs) == 0 { + return "" + } + if len(errs) == 1 { + return errs[0].Error() + } + + var buf bytes.Buffer + for _, err := range errs { + buf.WriteString("\n") + buf.WriteString(err.Error()) + } + return buf.String() +} + +// ExpectValid validates the value and calls t.Errorf if any validation errors are returned. +// Returns ValidationTester to support call chaining. +func (v *ValidationTester) ExpectValid() *ValidationTester { + v.T.Helper() + + v.T.Run(fmt.Sprintf("%T", v.value), func(t *testing.T) { + t.Helper() + + errs := v.validate() + if len(errs) > 0 { + t.Errorf("want no errors, got: %v", multiline(errs)) + } + }) + return v +} + +// ExpectValidateFalseByPath validates the value and looks for the errors +// specifically produced by `+k8s:validateFalse` tags. Each field (the map key) +// can have multiple error strings (the map value). Test which are trying +// to prove that the validation logic itself (e.g. validation-gen) produces the +// expected errors should use this method. +func (v *ValidationTester) ExpectValidateFalseByPath(expectedByPath map[string][]string) *ValidationTester { + v.T.Helper() + + v.T.Run(fmt.Sprintf("%T", v.value), func(t *testing.T) { + t.Helper() + + actualByPath := v.validateFalseArgsByPath() + // ensure args are sorted + for _, args := range expectedByPath { + sort.Strings(args) + } + if !cmp.Equal(expectedByPath, actualByPath) { + t.Errorf("validateFalse args, grouped by field path, differed from expected:\n%s\n", cmp.Diff(expectedByPath, actualByPath, cmpopts.SortMaps(stdcmp.Less[string]))) + } + + }) + return v +} + +func (v *ValidationTester) validateFalseArgsByPath() map[string][]string { + byPath := map[string][]string{} + errs := v.validate() + for _, e := range errs { + if strings.HasPrefix(e.Detail, "forced failure: ") { + arg := strings.TrimPrefix(e.Detail, "forced failure: ") + f := e.Field + if f == "" { + f = "" + } + byPath[f] = append(byPath[f], arg) + } + } + // ensure args are sorted + for _, args := range byPath { + sort.Strings(args) + } + return byPath +} + +// ExpectMatches compares the expected errors with the actual errors returned +// by the validation, using the provided ErrorMatcher. Tests which are trying +// to prove that a use-case of validation (e.g. testing pod validation) +// produces the expected errors should use this method. +func (v *ValidationTester) ExpectMatches(matcher field.ErrorMatcher, expected field.ErrorList) *ValidationTester { + v.Helper() + + v.Run(fmt.Sprintf("%T", v.value), func(t *testing.T) { + t.Helper() + actual := v.validate() + matcher.Test(t, expected, actual) + }) + return v +} + +func (v *ValidationTester) validate() field.ErrorList { + var errs field.ErrorList + if v.isUpdate { + errs = v.s.ValidateUpdate(context.Background(), v.options, v.value, v.oldValue, v.subresources...) + } else { + errs = v.s.Validate(context.Background(), v.options, v.value, v.subresources...) + } + + rt := reflect.TypeOf(v.value) + for rt.Kind() == reflect.Ptr { + rt = rt.Elem() + } + pkgName := strings.Split(rt.String(), ".")[0] + gvk := schema.GroupVersionKind{Group: rt.PkgPath(), Version: pkgName, Kind: rt.Name()} + coverage.RecordObservedRules(gvk, errs) + + return errs +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/util/util.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/util/util.go new file mode 100644 index 0000000000..6b11730682 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/util/util.go @@ -0,0 +1,228 @@ +/* +Copyright 2025 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 util + +import ( + "fmt" + "math" + "strconv" + + "k8s.io/gengo/v2/parser/tags" + "k8s.io/gengo/v2/types" +) + +// GetMemberByJSON returns the child member of the type that has the given JSON +// name. It returns nil if no such member exists. +func GetMemberByJSON(t *types.Type, jsonName string) *types.Member { + for i := range t.Members { + if jsonTag, ok := tags.LookupJSON(t.Members[i]); ok { + if jsonTag.Name == jsonName { + return &t.Members[i] + } + } + } + return nil +} + +// IsNilableType returns true if the argument type can be compared to nil. +func IsNilableType(t *types.Type) bool { + t = NativeType(t) + + switch t.Kind { + case types.Pointer, types.Map, types.Slice, types.Interface: // Note: Arrays are not nilable + return true + } + return false +} + +// NativeType returns the Go native type of the argument type, with any +// intermediate typedefs removed. Go itself already flattens typedefs, but this +// handles it in the unlikely event that we ever fix that. +// +// Examples: +// * Trivial: +// - given `int`, returns `int` +// - given `*int`, returns `*int` +// - given `[]int`, returns `[]int` +// +// * Typedefs +// - given `type X int; X`, returns `int` +// - given `type X int; []X`, returns `[]X` +// +// * Typedefs and pointers: +// - given `type X int; *X`, returns `*int` +// - given `type X *int; *X`, returns `**int` +// - given `type X []int; X`, returns `[]int` +// - given `type X []int; *X`, returns `*[]int` +func NativeType(t *types.Type) *types.Type { + ptrs := 0 + conditionMet := false + for !conditionMet { + switch t.Kind { + case types.Alias: + t = t.Underlying + case types.Pointer: + ptrs++ + t = t.Elem + default: + conditionMet = true + } + } + for range ptrs { + t = types.PointerTo(t) + } + return t +} + +// NonPointer returns the value-type of a possibly pointer type. If type is not +// a pointer, it returns the input type. +func NonPointer(t *types.Type) *types.Type { + for t.Kind == types.Pointer { + t = t.Elem + } + return t +} + +// IsDirectComparable returns true if the type is safe to compare using "==". +// It is similar to gengo.IsComparable, but it doesn't consider Pointers to be +// comparable (we don't want shallow compare). +func IsDirectComparable(t *types.Type) bool { + switch t.Kind { + case types.Builtin: + return true + case types.Struct: + for _, f := range t.Members { + if !IsDirectComparable(f.Type) { + return false + } + } + return true + case types.Array: + return IsDirectComparable(t.Elem) + case types.Alias: + return IsDirectComparable(t.Underlying) + } + return false +} + +// ParseInt strictly parses an int from a string input, +// ensuring that when converted back to a string, the resulting +// int and the input string have the exact same representation. +// This prevents scenarios where an input like "0100" parses +// as 100 and would be re-stringed as "100". +func ParseInt(val string) (int, error) { + intVal, err := strconv.Atoi(val) + if err != nil { + return 0, fmt.Errorf("parsing %q as int: %w", val, err) + } + + strVal := strconv.Itoa(intVal) + if strVal != val { + return 0, fmt.Errorf("%q is not a valid int value", val) + } + + return intVal, nil +} + +// ParseSignedInt strictly parses a signed integer from a string input and +// validates that the result fits within the specified bit size. The bitSize +// parameter should be 8, 16, 32, or 64, corresponding to the target Go type. +// Values outside the representable range for the target type are rejected at +// parse time with a descriptive error. +func ParseSignedInt(val string, bitSize int) (int64, error) { + intVal, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return 0, fmt.Errorf("parsing %q as int: %w", val, err) + } + + // Verify canonical form: reject leading zeros, unary plus, etc. + strVal := strconv.FormatInt(intVal, 10) + if strVal != val { + return 0, fmt.Errorf("%q is not a valid int value", val) + } + + // Validate the parsed value fits in the target type's range. + var minVal, maxVal int64 + switch bitSize { + case 8: + minVal, maxVal = math.MinInt8, math.MaxInt8 + case 16: + minVal, maxVal = math.MinInt16, math.MaxInt16 + case 32: + minVal, maxVal = math.MinInt32, math.MaxInt32 + case 64: + minVal, maxVal = math.MinInt64, math.MaxInt64 + default: + return 0, fmt.Errorf("unsupported bitSize %d; must be 8, 16, 32, or 64", bitSize) + } + if intVal < minVal || intVal > maxVal { + return 0, fmt.Errorf("value %d does not fit in int%d (range [%d, %d])", intVal, bitSize, minVal, maxVal) + } + + return intVal, nil +} + +// ParseUnsignedInt strictly parses an unsigned integer from a string input and +// validates that the result fits within the specified bit size. The bitSize +// parameter should be 8, 16, 32, or 64, corresponding to the target Go type. +func ParseUnsignedInt(val string, bitSize int) (uint64, error) { + uintVal, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return 0, fmt.Errorf("parsing %q as uint: %w", val, err) + } + + // Verify canonical form: reject leading zeros, unary plus, etc. + strVal := strconv.FormatUint(uintVal, 10) + if strVal != val { + return 0, fmt.Errorf("%q is not a valid uint value", val) + } + + // Validate the parsed value fits in the target type's range. + var maxVal uint64 + switch bitSize { + case 8: + maxVal = math.MaxUint8 + case 16: + maxVal = math.MaxUint16 + case 32: + maxVal = math.MaxUint32 + case 64: + maxVal = math.MaxUint64 + default: + return 0, fmt.Errorf("unsupported bitSize %d; must be 8, 16, 32, or 64", bitSize) + } + if uintVal > maxVal { + return 0, fmt.Errorf("value %d does not fit in uint%d (range [0, %d])", uintVal, bitSize, maxVal) + } + + return uintVal, nil +} + +// ParseBool strictly parses a bool from a string input, +// ensuring that when converted back to a string, the resulting +// bool and the input string have the exact same representation. +// This prevents scenarios where an input like "TRUE" parses +// as true and would be re-stringed as "true". +func ParseBool(val string) (bool, error) { + switch val { + case "true": + return true, nil + case "false": + return false, nil + } + return false, fmt.Errorf("%q is not a valid bool value", val) +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/util/util_test.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/util/util_test.go new file mode 100644 index 0000000000..dd008cc1cb --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/util/util_test.go @@ -0,0 +1,940 @@ +/* +Copyright 2025 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 util + +import ( + "reflect" + "testing" + + "k8s.io/gengo/v2/types" +) + +func TestGetMemberByJSON(t *testing.T) { + tests := []struct { + name string + t *types.Type + jsonTag string + want *types.Member + wantBool bool + }{{ + name: "exact match", + t: &types.Type{ + Members: []types.Member{ + {Name: "Field0", Tags: `json:"field0"`}, + {Name: "Field1", Tags: `json:"field1"`}, + {Name: "Field2", Tags: `json:"field2"`}, + }, + }, + jsonTag: "field1", + want: &types.Member{Name: "Field1", Tags: `json:"field1"`}, + wantBool: true, + }, { + name: "no match", + t: &types.Type{ + Members: []types.Member{ + {Name: "Field0", Tags: `json:"field0"`}, + {Name: "Field1", Tags: `json:"field1"`}, + }, + }, + jsonTag: "field2", + want: nil, + wantBool: false, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetMemberByJSON(tt.t, tt.jsonTag) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetMemberByJSON() got %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsNilableType(t *testing.T) { + tStruct := &types.Type{ + Name: types.Name{Name: "MyStruct"}, + Kind: types.Struct, + } + + tests := []struct { + name string + t *types.Type + want bool + }{{ + name: "pointer", + t: &types.Type{ + Kind: types.Pointer, + Elem: tStruct, + }, + want: true, + }, { + name: "alias to pointer", + t: &types.Type{ + Kind: types.Alias, + Underlying: &types.Type{ + Kind: types.Pointer, + Elem: tStruct, + }, + }, + want: true, + }, { + name: "map", + t: &types.Type{ + Kind: types.Map, + }, + want: true, + }, { + name: "alias to map", + t: &types.Type{ + Kind: types.Alias, + Underlying: &types.Type{ + Kind: types.Map, + }, + }, + want: true, + }, { + name: "slice", + t: &types.Type{ + Kind: types.Slice, + }, + want: true, + }, { + name: "alias to slice", + t: &types.Type{ + Kind: types.Alias, + Underlying: &types.Type{ + Kind: types.Slice, + }, + }, + want: true, + }, { + name: "interface", + t: &types.Type{ + Kind: types.Interface, + }, + want: true, + }, { + name: "alias to interface", + t: &types.Type{ + Kind: types.Alias, + Underlying: &types.Type{ + Kind: types.Interface, + }, + }, + want: true, + }, { + name: "struct", + t: &types.Type{ + Kind: types.Struct, + }, + want: false, + }, { + name: "alias to struct", + t: &types.Type{ + Kind: types.Alias, + Underlying: &types.Type{ + Kind: types.Struct, + }, + }, + want: false, + }, { + name: "builtin", + t: &types.Type{ + Kind: types.Builtin, + }, + want: false, + }, { + name: "alias to builtin", + t: &types.Type{ + Kind: types.Alias, + Underlying: &types.Type{ + Kind: types.Builtin, + }, + }, + want: false, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsNilableType(tt.t); got != tt.want { + t.Errorf("IsNilableType() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNativeType(t *testing.T) { + tStruct := &types.Type{ + Name: types.Name{Name: "MyStruct"}, + Kind: types.Struct, + } + pStruct := &types.Type{ + Name: types.Name{Name: "*MyStruct"}, + Kind: types.Pointer, + Elem: tStruct, + } + + tests := []struct { + name string + t *types.Type + want *types.Type + }{{ + name: "struct", + t: tStruct, + want: tStruct, + }, { + name: "pointer to struct", + t: pStruct, + want: pStruct, + }, { + name: "alias to struct", + t: &types.Type{ + Name: types.Name{Name: "Alias"}, + Kind: types.Alias, + Underlying: tStruct, + }, + want: tStruct, + }, { + name: "pointer to alias to struct", + t: &types.Type{ + Name: types.Name{Name: "*Alias"}, + Kind: types.Pointer, + Elem: &types.Type{ + Kind: types.Alias, + Underlying: tStruct, + }, + }, + want: pStruct, + }, { + name: "alias of pointer to struct", + t: &types.Type{ + Name: types.Name{Name: "AliasP"}, + Kind: types.Alias, + Underlying: pStruct, + }, + want: pStruct, + }, { + name: "pointer to alias of pointer to struct", + t: &types.Type{ + Name: types.Name{Name: "*AliasP"}, + Kind: types.Pointer, + Elem: &types.Type{ + Name: types.Name{Name: "AliasP"}, + Kind: types.Alias, + Underlying: pStruct, + }, + }, + want: &types.Type{ + Name: types.Name{Name: "**MyStruct"}, + Kind: types.Pointer, + Elem: pStruct, + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if want, got := tt.want.String(), NativeType(tt.t).String(); want != got { + t.Errorf("NativeType() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestNonPointer(t *testing.T) { + tStruct := &types.Type{ + Name: types.Name{Name: "MyStruct"}, + Kind: types.Struct, + } + + tests := []struct { + name string + t *types.Type + want *types.Type + }{{ + name: "value", + t: tStruct, + want: tStruct, + }, { + name: "pointer", + t: &types.Type{ + Name: types.Name{Name: "*MyStruct"}, + Kind: types.Pointer, + Elem: tStruct, + }, + want: tStruct, + }, { + name: "pointer pointer", + t: &types.Type{ + Name: types.Name{Name: "**MyStruct"}, + Kind: types.Pointer, + Elem: &types.Type{ + Kind: types.Pointer, + Elem: tStruct, + }, + }, + want: tStruct, + }, { + name: "pointer alias pointer", + t: &types.Type{ + Name: types.Name{Name: "*AliasP"}, + Kind: types.Pointer, + Elem: &types.Type{ + Name: types.Name{Name: "AliasP"}, + Kind: types.Alias, + Underlying: &types.Type{ + Name: types.Name{Name: "*MyStruct"}, + Kind: types.Pointer, + Elem: tStruct, + }, + }, + }, + want: &types.Type{ + Name: types.Name{Name: "AliasP"}, + Kind: types.Alias, + Underlying: &types.Type{ + Name: types.Name{Name: "*MyStruct"}, + Kind: types.Pointer, + Elem: tStruct, + }, + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NonPointer(tt.t); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NonPointer() = %v, want %v", got, tt.want) + } + }) + } +} + +// gengo has `PointerTo()` but not the rest, so keep this here for consistency. +func ptrTo(t *types.Type) *types.Type { + return &types.Type{ + Name: types.Name{ + Package: "", + Name: "*" + t.Name.String(), + }, + Kind: types.Pointer, + Elem: t, + } +} + +func sliceOf(t *types.Type) *types.Type { + return &types.Type{ + Name: types.Name{ + Package: "", + Name: "[]" + t.Name.String(), + }, + Kind: types.Slice, + Elem: t, + } +} + +func mapOf(t *types.Type) *types.Type { + return &types.Type{ + Name: types.Name{ + Package: "", + Name: "map[string]" + t.Name.String(), + }, + Kind: types.Map, + Key: types.String, + Elem: t, + } +} + +func arrayOf(t *types.Type) *types.Type { + return &types.Type{ + Name: types.Name{ + Package: "", + Name: "[2]" + t.Name.String(), + }, + Kind: types.Array, + Len: 2, + Elem: t, + } +} + +func aliasOf(name string, t *types.Type) *types.Type { + return &types.Type{ + Name: types.Name{ + Package: "", + Name: "Alias_" + name, + }, + Kind: types.Alias, + Underlying: t, + } +} + +func TestIsDirectComparable(t *testing.T) { + cases := []struct { + in *types.Type + expect bool + }{ + { + in: types.String, + expect: true, + }, { + in: ptrTo(types.String), + expect: false, + }, { + in: sliceOf(types.String), + expect: false, + }, { + in: mapOf(types.String), + expect: false, + }, { + in: aliasOf("s", types.String), + expect: true, + }, { + in: &types.Type{ + Name: types.Name{ + Package: "", + Name: "struct_comparable_member", + }, + Kind: types.Struct, + Members: []types.Member{ + { + Name: "s", + Type: types.String, + }, + }, + }, + expect: true, + }, { + in: &types.Type{ + Name: types.Name{ + Package: "", + Name: "struct_uncomparable_member", + }, + Kind: types.Struct, + Members: []types.Member{ + { + Name: "s", + Type: ptrTo(types.String), + }, + }, + }, + expect: false, + }, { + in: arrayOf(types.String), + expect: true, + }, { + in: arrayOf(aliasOf("s", types.String)), + expect: true, + }, { + in: arrayOf(ptrTo(types.String)), + expect: false, + }, { + in: arrayOf(mapOf(types.String)), + expect: false, + }, + } + + for _, tc := range cases { + if got, want := IsDirectComparable(tc.in), tc.expect; got != want { + t.Errorf("%q: expected %v, got %v", tc.in, want, got) + } + } +} + +func TestParseInt(t *testing.T) { + type testcase struct { + name string + in string + expectedOut int + expectedError bool + } + + testcases := []testcase{ + { + name: "valid canonical positive integer string", + in: "100", + expectedOut: 100, + }, + { + name: "valid canonical negative integer string", + in: "-100", + expectedOut: -100, + }, + { + name: "empty string", + in: "", + expectedError: true, + }, + { + name: "invalid unary positive integer string", + in: "+100", + expectedError: true, + }, + { + name: "invalid canonical integer string, not an integer at all", + in: "notanint", + expectedError: true, + }, + { + name: "invalid canonical integer string, spurious leading zeros", + in: "00100", + expectedError: true, + }, + { + name: "invalid canonical integer string, octal value", + in: "0o123", + expectedError: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + out, err := ParseInt(tc.in) + switch { + case tc.expectedError && err == nil: + t.Error("expected an error but did not receive one") + case !tc.expectedError && err != nil: + t.Errorf("received an unexpected error: %v", err) + } + + if out != tc.expectedOut { + t.Errorf("expected an output value of %d but got %d", tc.expectedOut, out) + } + }) + } +} + +func TestParseSignedInt(t *testing.T) { + type testcase struct { + name string + in string + bitSize int + expectedOut int64 + expectedError bool + } + + testcases := []testcase{ + // --- int32 valid boundaries --- + { + name: "int32 exact minimum boundary", + in: "-2147483648", + bitSize: 32, + expectedOut: -2147483648, + }, + { + name: "int32 exact maximum boundary", + in: "2147483647", + bitSize: 32, + expectedOut: 2147483647, + }, + { + name: "int32 zero", + in: "0", + bitSize: 32, + expectedOut: 0, + }, + { + name: "int32 positive value", + in: "100", + bitSize: 32, + expectedOut: 100, + }, + { + name: "int32 negative value", + in: "-1", + bitSize: 32, + expectedOut: -1, + }, + // --- int32 overflow --- + { + name: "int32 one below minimum overflows", + in: "-2147483649", + bitSize: 32, + expectedError: true, + }, + { + name: "int32 one above maximum overflows", + in: "2147483648", + bitSize: 32, + expectedError: true, + }, + { + name: "int64 accepts value that overflows int32", + in: "2147483648", + bitSize: 64, + expectedOut: 2147483648, + }, + // --- int64 valid boundaries --- + { + name: "int64 exact minimum boundary", + in: "-9223372036854775808", + bitSize: 64, + expectedOut: -9223372036854775808, + }, + { + name: "int64 exact maximum boundary", + in: "9223372036854775807", + bitSize: 64, + expectedOut: 9223372036854775807, + }, + // --- int64 overflow --- + { + name: "int64 one above maximum overflows", + in: "9223372036854775808", + bitSize: 64, + expectedError: true, + }, + { + name: "int64 one below minimum overflows", + in: "-9223372036854775809", + bitSize: 64, + expectedError: true, + }, + // --- int16 boundaries --- + { + name: "int16 exact minimum boundary", + in: "-32768", + bitSize: 16, + expectedOut: -32768, + }, + { + name: "int16 exact maximum boundary", + in: "32767", + bitSize: 16, + expectedOut: 32767, + }, + { + name: "int16 one above maximum overflows", + in: "32768", + bitSize: 16, + expectedError: true, + }, + { + name: "int16 one below minimum overflows", + in: "-32769", + bitSize: 16, + expectedError: true, + }, + // --- int8 boundaries --- + { + name: "int8 exact minimum boundary", + in: "-128", + bitSize: 8, + expectedOut: -128, + }, + { + name: "int8 exact maximum boundary", + in: "127", + bitSize: 8, + expectedOut: 127, + }, + { + name: "int8 one above maximum overflows", + in: "128", + bitSize: 8, + expectedError: true, + }, + { + name: "int8 one below minimum overflows", + in: "-129", + bitSize: 8, + expectedError: true, + }, + // --- canonical form rejection --- + { + name: "leading zeros rejected", + in: "0100", + bitSize: 32, + expectedError: true, + }, + { + name: "unary plus rejected", + in: "+1", + bitSize: 32, + expectedError: true, + }, + { + name: "octal notation rejected", + in: "0o77", + bitSize: 32, + expectedError: true, + }, + { + name: "hex notation rejected", + in: "0xFF", + bitSize: 32, + expectedError: true, + }, + { + name: "empty string rejected", + in: "", + bitSize: 32, + expectedError: true, + }, + { + name: "non-numeric string rejected", + in: "abc", + bitSize: 32, + expectedError: true, + }, + { + name: "floating point rejected", + in: "1.5", + bitSize: 32, + expectedError: true, + }, + { + name: "whitespace rejected", + in: " 1", + bitSize: 32, + expectedError: true, + }, + { + name: "negative zero canonical form rejected", + in: "-0", + bitSize: 32, + expectedError: true, + }, + { + name: "unknown bit size returns error", + in: "1", + bitSize: 7, + expectedError: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + out, err := ParseSignedInt(tc.in, tc.bitSize) + switch { + case tc.expectedError && err == nil: + t.Errorf("expected an error for input %q with bitSize %d but did not receive one (got %d)", tc.in, tc.bitSize, out) + case !tc.expectedError && err != nil: + t.Errorf("received an unexpected error for input %q with bitSize %d: %v", tc.in, tc.bitSize, err) + } + + if out != tc.expectedOut { + t.Errorf("expected output %d but got %d", tc.expectedOut, out) + } + }) + } +} + +func TestParseUnsignedInt(t *testing.T) { + type testcase struct { + name string + in string + bitSize int + expectedOut uint64 + expectedError bool + } + + testcases := []testcase{ + // --- uint64 valid boundaries --- + { + name: "uint64 maximum boundary", + in: "18446744073709551615", + bitSize: 64, + expectedOut: 18446744073709551615, + }, + { + name: "uint64 zero", + in: "0", + bitSize: 64, + expectedOut: 0, + }, + // --- uint64 overflow --- + { + name: "uint64 one above maximum overflows", + in: "18446744073709551616", + bitSize: 64, + expectedError: true, + }, + // --- uint32 valid boundaries --- + { + name: "uint32 maximum boundary", + in: "4294967295", + bitSize: 32, + expectedOut: 4294967295, + }, + { + name: "uint32 zero", + in: "0", + bitSize: 32, + expectedOut: 0, + }, + { + name: "uint32 one above maximum overflows", + in: "4294967296", + bitSize: 32, + expectedError: true, + }, + // --- uint16 valid boundaries --- + { + name: "uint16 maximum boundary", + in: "65535", + bitSize: 16, + expectedOut: 65535, + }, + { + name: "uint16 one above maximum overflows", + in: "65536", + bitSize: 16, + expectedError: true, + }, + // --- uint8 valid boundaries --- + { + name: "uint8 maximum boundary", + in: "255", + bitSize: 8, + expectedOut: 255, + }, + { + name: "uint8 one above maximum overflows", + in: "256", + bitSize: 8, + expectedError: true, + }, + // --- negative values rejected for unsigned --- + { + name: "negative value rejected", + in: "-1", + bitSize: 64, + expectedError: true, + }, + { + name: "uint64 accepts value that overflows uint32", + in: "4294967296", + bitSize: 64, + expectedOut: 4294967296, + }, + // --- canonical form rejection --- + { + name: "leading zeros rejected", + in: "0100", + bitSize: 32, + expectedError: true, + }, + { + name: "unary plus rejected", + in: "+1", + bitSize: 32, + expectedError: true, + }, + { + name: "empty string rejected", + in: "", + bitSize: 64, + expectedError: true, + }, + { + name: "hex notation rejected", + in: "0xFF", + bitSize: 32, + expectedError: true, + }, + { + name: "floating point rejected", + in: "1.0", + bitSize: 32, + expectedError: true, + }, + { + name: "unknown bit size returns error", + in: "1", + bitSize: 7, + expectedError: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + out, err := ParseUnsignedInt(tc.in, tc.bitSize) + switch { + case tc.expectedError && err == nil: + t.Errorf("expected an error for input %q with bitSize %d but did not receive one (got %d)", tc.in, tc.bitSize, out) + case !tc.expectedError && err != nil: + t.Errorf("received an unexpected error for input %q with bitSize %d: %v", tc.in, tc.bitSize, err) + } + + if out != tc.expectedOut { + t.Errorf("expected output %d but got %d", tc.expectedOut, out) + } + }) + } +} + +func TestParseBool(t *testing.T) { + type testcase struct { + name string + in string + expectedOut bool + expectedError bool + } + + testcases := []testcase{ + { + name: "valid canonical true string", + in: "true", + expectedOut: true, + }, + { + name: "valid canonical false string", + in: "false", + expectedOut: false, + }, + { + name: "empty string", + in: "", + expectedError: true, + }, + { + name: "invalid canonical boolean string, not a bool at all", + in: "notabool", + expectedError: true, + }, + { + name: "invalid canonical boolean string, capitalized", + in: "True", + expectedError: true, + }, + { + name: "invalid canonical boolean string, numeric", + in: "1", + expectedError: true, + }, + { + name: "invalid canonical boolean string, YAML", + in: "yes", + expectedError: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + out, err := ParseBool(tc.in) + switch { + case tc.expectedError && err == nil: + t.Error("expected an error but did not receive one") + case !tc.expectedError && err != nil: + t.Errorf("received an unexpected error: %v", err) + } + + if out != tc.expectedOut { + t.Errorf("expected an output value of %v but got %v", tc.expectedOut, out) + } + }) + } +} diff --git a/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/validation.go b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/validation.go new file mode 100644 index 0000000000..a4368afa96 --- /dev/null +++ b/hack/tools/code-generator/third_party/k8s.io/code-generator/cmd/validation-gen/validation.go @@ -0,0 +1,2166 @@ +/* +Copyright 2024 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 main + +import ( + "bytes" + "cmp" + "fmt" + "io" + "reflect" + "slices" + "strconv" + "strings" + "unicode" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/code-generator/cmd/validation-gen/util" + "k8s.io/code-generator/cmd/validation-gen/validators" + "k8s.io/gengo/v2/generator" + "k8s.io/gengo/v2/namer" + "k8s.io/gengo/v2/parser/tags" + "k8s.io/gengo/v2/types" + "k8s.io/klog/v2" +) + +func mkPkgNames(pkg string, names ...string) []types.Name { + result := make([]types.Name, 0, len(names)) + for _, name := range names { + result = append(result, types.Name{Package: pkg, Name: name}) + } + return result +} + +var ( + fieldPkg = "k8s.io/apimachinery/pkg/util/validation/field" + fieldPkgSymbols = mkPkgNames(fieldPkg, "ErrorList", "InternalError", "Path") + fmtPkgSymbols = mkPkgNames("fmt", "Errorf", "Fprintln") + safePkg = "k8s.io/apimachinery/pkg/api/safe" + safePkgSymbols = mkPkgNames(safePkg, "Field", "Cast", "Value") + operationPkg = "k8s.io/apimachinery/pkg/api/operation" + operationPkgSymbols = mkPkgNames(operationPkg, "Operation", "MatchesSubresource", "Update") + contextPkg = "context" + contextPkgSymbols = mkPkgNames(contextPkg, "Context") +) + +// genValidations produces a file with autogenerated validations. +type genValidations struct { + generator.GoGenerator + outputPackage string + inputPackage string + inputToCanonicalPkg map[string]string // input package -> the generated package cross-package references resolve to + rootTypes []*types.Type + discovered *typeDiscoverer + imports namer.ImportTracker + schemeRegistry types.Name + emitRegisterFunc bool + deepEqualFunc types.Name + usedDeepEqualImpl bool +} + +// NewGenValidations creates a new generator for the specified package. +func NewGenValidations(outputFilename, outputPackage, inputPackage string, rootTypes []*types.Type, discovered *typeDiscoverer, inputToCanonicalPkg map[string]string, schemeRegistry types.Name, emitRegisterFunc bool, deepEqualFunc types.Name) generator.Generator { + return &genValidations{ + GoGenerator: generator.GoGenerator{ + OutputFilename: outputFilename, + }, + outputPackage: outputPackage, + inputPackage: inputPackage, + inputToCanonicalPkg: inputToCanonicalPkg, + rootTypes: rootTypes, + discovered: discovered, + imports: generator.NewImportTrackerForPackage(outputPackage), + schemeRegistry: schemeRegistry, + emitRegisterFunc: emitRegisterFunc, + deepEqualFunc: deepEqualFunc, + } +} + +// resolveFunc maps a validator reference to the package it should be called +// from, applying the first rule that matches (local wins over canonical): +// 1. if this generator validates the reference's input package, the +// generator's own output package (the local copy); +// 2. else the input's canonical package, if one exists; +// 3. otherwise the reference is returned unchanged. +func (g *genValidations) resolveFunc(name types.Name) types.Name { + if name.Package == g.inputPackage { + name.Package = g.outputPackage + } else if pkg, ok := g.inputToCanonicalPkg[name.Package]; ok { + name.Package = pkg + } + return name +} + +func (g *genValidations) Namers(_ *generator.Context) namer.NameSystems { + // Have the raw namer for this file track what it imports. + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.outputPackage, g.imports), + } +} + +func (g *genValidations) Filter(_ *generator.Context, t *types.Type) bool { + // We want to emit code for all root types. + if slices.Contains(g.rootTypes, t) { + return true + } + // We want to emit for any other type that is referenced by a root type's + // graph and has validations. Types that were only discovered as another + // package's root (selected there, referenced by nothing) are skipped so a + // generator does not emit validators it never calls. + n := g.discovered.typeNodes[t] + return n != nil && n.referenced && g.hasValidations(n) +} + +func (g *genValidations) Imports(_ *generator.Context) (imports []string) { + var importLines []string + for _, singleImport := range g.imports.ImportLines() { + if g.isOtherPackage(singleImport) { + importLines = append(importLines, singleImport) + } + } + return importLines +} + +func (g *genValidations) isOtherPackage(pkg string) bool { + if pkg == g.outputPackage { + return false + } + if strings.HasSuffix(pkg, `"`+g.outputPackage+`"`) { + return false + } + return true +} + +func (g *genValidations) Init(c *generator.Context, w io.Writer) error { + klog.V(5).Infof("emitting registration code") + sw := generator.NewSnippetWriter(w, c, "$", "$") + if g.emitRegisterFunc { + g.emitRegisterFunction(c, g.schemeRegistry, sw) + } + if err := sw.Error(); err != nil { + return err + } + return nil +} + +func (g *genValidations) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + klog.V(5).Infof("emitting validation code for type %v", t) + + sw := generator.NewSnippetWriter(w, c, "$", "$") + g.emitValidationVariables(c, t, sw) + g.emitValidationFunction(c, t, sw) + if err := sw.Error(); err != nil { + return err + } + return nil +} + +func (g *genValidations) Finalize(c *generator.Context, w io.Writer) error { + if g.usedDeepEqualImpl { + sw := generator.NewSnippetWriter(w, c, "$", "$") + sw.Do("\n", nil) + sw.Do("// deepEqualImpl_ is a validate.MatchFunc which allows the implementation of deep-equality to be defined at codegen time.\n", nil) + sw.Do("func deepEqualImpl_[T any](a, b T) bool {\n", nil) + if g.deepEqualFunc.Package == "" { + sw.Do(" return $.$(a, b)\n", g.deepEqualFunc.Name) + } else { + targs := generator.Args{ + "deepEqualFunc": c.Universe.Type(g.deepEqualFunc), + } + sw.Do(" return $.deepEqualFunc|raw$(a, b)\n", targs) + } + sw.Do("}\n", nil) + return sw.Error() + } + return nil +} + +// typeDiscoverer contains fields necessary to build graphs of types. +type typeDiscoverer struct { + initialized bool + validator validators.ValidationExtractor + inputToCanonicalPkg map[string]string + + // constantsByType holds a map of type to constants of that type. + constantsByType map[*types.Type][]*validators.Constant + + // typeNodes holds a map of gengo Type to typeNode for all of the types + // encountered during discovery. + typeNodes map[*types.Type]*typeNode +} + +// NewTypeDiscoverer creates a NewTypeDiscoverer. +// Init must be called before calling DiscoverType. +func NewTypeDiscoverer(validator validators.ValidationExtractor, inputToCanonicalPkg map[string]string) *typeDiscoverer { + return &typeDiscoverer{ + validator: validator, + inputToCanonicalPkg: inputToCanonicalPkg, + constantsByType: map[*types.Type][]*validators.Constant{}, + typeNodes: map[*types.Type]*typeNode{}, + } +} + +// Init uses the generator context to prepare for type discovery. +func (td *typeDiscoverer) Init(c *generator.Context) error { + packages := c.Universe + for _, pkg := range packages { + // We only care about packages we are generating for or are readonly. + if _, ok := td.inputToCanonicalPkg[pkg.Path]; !ok { + continue + } + for _, cnst := range pkg.Constants { + context := validators.Context{ + Scope: validators.ScopeConst, + Type: cnst.Underlying, + Path: nil, // NA when discovering a constant + Member: nil, // NA when discovering a constant + ParentPath: nil, // NA when discovering a constant + ListSelector: nil, // NA for constants + ParentType: nil, // NA for constants + Constants: nil, // NA for constants + StabilityLevel: "", // Default to stable unless overridden + } + tgs, err := td.validator.ExtractTags(context, cnst.CommentLines) + if err != nil { + return fmt.Errorf("constant %s: %w", cnst.Name, err) + } + if len(tgs) > 0 { + // Also check that the tgs are valid. + if _, err := td.validator.ExtractValidations(context, tgs...); err != nil { + return fmt.Errorf("constant %s: %w", cnst.Name, err) + } + } + td.constantsByType[cnst.Underlying] = append(td.constantsByType[cnst.Underlying], &validators.Constant{Constant: cnst, Tags: tgs}) + } + } + td.initialized = true + return nil +} + +// childNode represents a type which is used in another type (e.g. a struct +// field). +type childNode struct { + name string // the field name in the parent, populated when this node is a struct field + jsonName string // always populated when name is populated + childType *types.Type // the real type of the child (may be a pointer) + node *typeNode // the node of the child's value type, or nil if it is in a foreign package + + fieldValidations validators.Validations // validations on the field + + // These are not the same as fieldValidations, and are not considered in + // hasValidations. These let us emit the iteration code for list and + // map types, but we might not have enough information to know if we can + // skip them at discovery time. + fieldValIterations validators.Validations // validations on each val + fieldKeyIterations validators.Validations // validations on each key +} + +// typeNode represents a node in the type-graph, annotated with information +// about validations. Everything in this type, transitively, is assoctiated +// with the type, and not any specific instance of that type (e.g. when used as +// a field in a struct. +type typeNode struct { + valueType *types.Type // never a pointer, but may be a map, slice, struct, etc. + funcName types.Name // populated when this type is has a validation function + referenced bool // true if used as a field/element/key/underlying of another type (set by discoverChild) + + fields []*childNode // populated when this type is a struct + key *childNode // populated when this type is a map + elem *childNode // populated when this type is a map or slice + underlying *childNode // populated when this type is an alias + + typeValidations validators.Validations // validations on the type + + // These are not the same as typeValidations, and are not considered in + // hasValidations. These let us emit the iteration code for list and + // map types, but we might not have enough information to know if we can + // skip them at discovery time. + typeValIterations validators.Validations // validations on each val + typeKeyIterations validators.Validations // validations on each key +} + +// resolveElemNode traverses underlying alias nodes to find the concrete element node (for slices/maps). +func (n *typeNode) resolveElemNode() *typeNode { + if n.elem != nil { + return n.elem.node + } + if n.underlying != nil && n.underlying.node != nil && n.underlying.node.elem != nil { + return n.underlying.node.elem.node + } + return nil +} + +// resolveKeyNode traverses underlying alias nodes to find the concrete key node (for maps). +func (n *typeNode) resolveKeyNode() *typeNode { + if n.key != nil { + return n.key.node + } + if n.underlying != nil && n.underlying.node != nil && n.underlying.node.key != nil { + return n.underlying.node.key.node + } + return nil +} + +// DiscoverType walks the given type recursively, building a type-graph in this +// typeDiscoverer. If this is called multiple times for different types, the +// graphs will be will be merged. +func (td *typeDiscoverer) DiscoverType(t *types.Type) error { + if !td.initialized { + return fmt.Errorf("typeDiscoverer not initialized") + } + if t.Kind == types.Pointer { + return fmt.Errorf("type %v: pointer root-types are not supported", t) + } + fldPath := field.NewPath(t.Name.String()) + if node, err := td.discoverType(t, fldPath); err != nil { + return err + } else if node == nil { + panic(fmt.Sprintf("discovered a nil node for type %v", t)) + } + return nil +} + +// discoverType walks the given type recursively and returns a typeNode +// representing it. This does not distinguish between discovering a type +// definition and discovering a field of a struct. The first time it +// encounters a type it has not seen before, it will explore that type. If it +// finds a type it has already processed, it will return the existing node. +func (td *typeDiscoverer) discoverType(t *types.Type, fldPath *field.Path) (*typeNode, error) { + // With the exception of builtins (which gengo puts in package ""), we + // can't traverse into packages which are not being processed by this tool. + if t.Name.Package != "" { + _, ok := td.inputToCanonicalPkg[t.Name.Package] + if !ok { + return nil, nil + } + } + + // Catch some cases that we don't want to handle (yet?). This happens + // as early as possible to make all the other code simpler. + if err := td.verifySupportedType(t); err != nil { + return nil, fmt.Errorf("field %s (%s): %w", fldPath.String(), t, err) + } + + // Discovery applies to values, not pointers. + if t.Kind == types.Pointer { + return td.discoverType(t.Elem, fldPath) + } + + // If we have done this type already, we can stop here and break any + // recursion. + if node, found := td.typeNodes[t]; found { + return node, nil + } + klog.V(4).InfoS("discoverType", "type", t, "kind", t.Kind, "path", fldPath.String()) + + // This is the type-node being assembled in the rest of this function. + thisNode := &typeNode{ + valueType: t, + } + td.typeNodes[t] = thisNode + + // If we are descending into a named type... + switch t.Kind { + case types.Alias, types.Struct: + // Reboot the field path for better logging. Otherwise the field path + // might come in as something like .. which is + // true, but not super useful. + fldPath = field.NewPath(t.Name.String()) + + // Find its validation function for later use. + if fn, ok := td.getValidationFunctionName(t); ok { + thisNode.funcName = fn + } + } + + // Discover into this type before extracting type validations. + switch t.Kind { + case types.Builtin, types.Interface: + // Nothing more to do. + case types.Alias: + // Discover the underlying type. + // + // Note: By the language definition, what gengo calls "Aliases" (really + // just "type definitions") have underlying types of the type literal. + // In other words, if we define `type T1 string` and `type T2 T1`, the + // underlying type of T2 is string, not T1. This means that: + // 1) We will emit code for both underlying types. If the underlying + // type is a struct with many fields, we will emit two identical + // functions. + // 2) Validating a field of type T2 will NOT call any validation + // defined on the type T1. + // 3) In the case of a type definition whose RHS is a struct which + // has fields with validation tags, the validation for those fields + // WILL be called from the generated for for the new type. + if node, err := td.discoverChild(t.Underlying, fldPath); err != nil { + return nil, err + } else { + thisNode.underlying = &childNode{ + childType: t.Underlying, + node: node, + } + } + case types.Struct: + // Discover into this struct, recursively. + if err := td.discoverStruct(thisNode, fldPath); err != nil { + return nil, err + } + case types.Slice: + // Discover the element type. + if node, err := td.discoverChild(t.Elem, fldPath.Key("vals")); err != nil { + return nil, err + } else { + thisNode.elem = &childNode{ + childType: t.Elem, + node: node, + } + } + case types.Map: + // Discover the key type. + if node, err := td.discoverChild(t.Key, fldPath.Key("keys")); err != nil { + return nil, err + } else { + thisNode.key = &childNode{ + childType: t.Key, + node: node, + } + } + + // Discover the element type. + if node, err := td.discoverChild(t.Elem, fldPath.Key("vals")); err != nil { + return nil, err + } else { + thisNode.elem = &childNode{ + childType: t.Elem, + node: node, + } + } + } + + // Extract any type-attached validation rules. We do this AFTER descending + // into the type, so that these validators have access to the full type. + // For example, all struct field validators get called before the type + // validators. This does not influence the order in which the validations + // are called in emitted code, just how we evaluate what to emit. + switch t.Kind { + case types.Alias, types.Struct: + if fldPath.String() != t.String() { + panic(fmt.Sprintf("path for type != the type name: %s, %s", t.String(), fldPath.String())) + } + consts := td.constantsByType[t] + context := validators.Context{ + Scope: validators.ScopeType, + Type: t, + Path: fldPath, + Member: nil, // NA when discovering a type + ParentPath: nil, // NA when discovering a type + Constants: consts, + ListSelector: nil, // NA for type scope + ParentType: nil, // NA for type scope + StabilityLevel: "", // Default to stable unless overridden + } + extractedTags, err := td.validator.ExtractTags(context, t.CommentLines) + if err != nil { + return nil, fmt.Errorf("%v: %w", fldPath, err) + } + if validations, err := td.validator.ExtractValidations(context, extractedTags...); err != nil { + return nil, fmt.Errorf("%v: %w", fldPath, err) + } else if validations.Empty() { + klog.V(6).InfoS("no type-attached validations", "type", t) + } else { + if util.NonPointer(util.NativeType(t)).Kind == types.Map && util.NonPointer(util.NativeType(t)).Elem.Kind == types.Slice { + return nil, fmt.Errorf("field %s: validation for map of slices is not supported", fldPath) + } + klog.V(5).InfoS("found type-attached validations", "n", len(validations.Functions), "type", t) + thisNode.typeValidations.Add(validations) + } + + // Handle type definitions whose output depends on the rest of type + // discovery being complete. In particular, aliases to lists and maps need + // iteration, but we don't want to iterate them if the key or value types + // don't actually have validations. We also want to handle non-included + // types and make users tell us what they intended. Lastly, we want to + // handle recursive types, but we need to finish discovering the type + // before we know if there are other validations, again so we don't emit + // empty functions. + if t.Kind == types.Alias { + switch util.NonPointer(util.NativeType(t)).Kind { + case types.Slice: + // Validate each value. + elemNode := thisNode.resolveElemNode() + if elemNode == nil { + if !thisNode.typeValidations.OpaqueValType { + return nil, fmt.Errorf("%v: value type %v is in a non-included package; "+ + "either add this package to validation-gen's --readonly-pkg flag, "+ + "or add +k8s:eachVal=+k8s:opaqueType to the field to skip validation", + fldPath, util.NativeType(t).Elem) + } + } else if thisNode.typeValidations.OpaqueValType { + // If the type is marked as opaque, we can treat it as it is + // were in a non-included package. + } else { + // If the value type is a named type, call the validation + // function for each element. + if funcName := elemNode.funcName; funcName.Name != "" { + // Save the iteration validation while we have all the + // information we need. Later we can check if we + // actually need it. + // + // Note: the first argument to Function() is really + // only for debugging. + v, err := validators.ForEachVal(fldPath, thisNode.valueType, + validators.Function("iterateListValues", validators.DefaultFlags, funcName). + WithComment("iterate the list and call the type's validation function")) + if err != nil { + return nil, fmt.Errorf("generating list iteration: %w", err) + } else { + thisNode.typeValIterations.Add(v) + } + } + } + case types.Map: + // Validate each key. + keyNode := thisNode.resolveKeyNode() + if keyNode == nil { + if !thisNode.typeValidations.OpaqueKeyType { + return nil, fmt.Errorf("%v: key type %v is in a non-included package; "+ + "either add this package to validation-gen's --readonly-pkg flag, "+ + "or add +k8s:eachKey=+k8s:opaqueType to the field to skip validation", + fldPath, util.NativeType(t).Key) + } + } else if thisNode.typeValidations.OpaqueKeyType { + // If the type is marked as opaque, we can treat it as it is + // were in a non-included package. + } else { + // If the key type is a named type, call the validation + // function for each key. + if funcName := keyNode.funcName; funcName.Name != "" { + // Save the iteration validation while we have all the + // information we need. Later we can check if we + // actually need it. + // + // Note: the first argument to Function() is really + // only for debugging. + v, err := validators.ForEachKey(fldPath, thisNode.valueType, + validators.Function("iterateMapKeys", validators.DefaultFlags, funcName). + WithComment("iterate the map and call the key type's validation function")) + if err != nil { + return nil, fmt.Errorf("generating map key iteration: %w", err) + } else { + thisNode.typeKeyIterations.Add(v) + } + } + } + // Validate each value. + elemNode := thisNode.resolveElemNode() + if elemNode == nil { + if !thisNode.typeValidations.OpaqueValType { + return nil, fmt.Errorf("%v: value type %v is in a non-included package; "+ + "either add this package to validation-gen's --readonly-pkg flag, "+ + "or add +k8s:eachVal=+k8s:opaqueType to the field to skip validation", + fldPath, util.NativeType(t).Elem) + } + } else if thisNode.typeValidations.OpaqueValType { + // If the type is marked as opaque, we can treat it as it is + // were in a non-included package. + } else { + // If the value type is a named type, call the validation + // function for each element. + if funcName := elemNode.funcName; funcName.Name != "" { + // Save the iteration validation while we have all the + // information we need. Later we can check if we + // actually need it. + // + // Note: the first argument to Function() is really + // only for debugging. + v, err := validators.ForEachVal(fldPath, thisNode.valueType, + validators.Function("iterateMapValues", validators.DefaultFlags, funcName). + WithComment("iterate the map and call the value type's validation function")) + if err != nil { + return nil, fmt.Errorf("generating map value iteration: %w", err) + } else { + thisNode.typeValIterations.Add(v) + } + } + } + } + } + } + + // These are validations that could not be fully resolved during tag extraction + // (e.g., because they need to wrap inner validations or depend on the full + // type graph being discovered). We resolve them iteratively because a + // deferred validation may yield further deferred validations. + deferred := thisNode.typeValidations.Deferred + thisNode.typeValidations.Deferred = nil + depth := 0 + for len(deferred) > 0 { + depth++ + if depth > 10 { + return nil, fmt.Errorf("deferred validation recursion depth exceeded "+ + "10 for type %s at path %s", thisNode.valueType.String(), fldPath.String()) + } + var nextDeferred []validators.DeferredGen + for _, def := range deferred { + res, err := def.Callback() + if err != nil { + return nil, err + } + if len(res.Deferred) > 0 { + nextDeferred = append(nextDeferred, res.Deferred...) + res.Deferred = nil + } + // Deferred validations can originate from fields with ParentContext scope (e.g., UnionValidations) + // or from validations on type definitions with ThisContext scope (e.g., eachVal on a slice type). + if def.Scope == validators.ThisContext || def.Scope == validators.ParentContext { + thisNode.typeValidations.Add(res) + } else { + return nil, fmt.Errorf("unexpected scope %v", def.Scope) + } + } + deferred = nextDeferred + } + + return thisNode, nil +} + +// discoverChild is discoverType for a type reached as a child (a field, +// element, key, or underlying type) of another type. It additionally marks the +// discovered node as referenced. See typeNode.referenced. +func (td *typeDiscoverer) discoverChild(t *types.Type, fldPath *field.Path) (*typeNode, error) { + node, err := td.discoverType(t, fldPath) + if node != nil { + node.referenced = true + } + return node, err +} + +// verifySupportedType checks whether the given type is supported. +func (td *typeDiscoverer) verifySupportedType(t *types.Type) error { + switch t.Kind { + case types.Builtin, types.Struct: + // Allowed + case types.Interface: + // We can't do much with interfaces, but they pop up in some places + // like RawExtension. + case types.Alias: + if t.Underlying.Kind == types.Pointer { + return fmt.Errorf("typedefs to pointers are not supported") + } + case types.Pointer: + pointee := util.NativeType(t.Elem) + switch pointee.Kind { + case types.Pointer: + return fmt.Errorf("pointers to pointers are not supported") + case types.Slice, types.Array: + return fmt.Errorf("pointers to lists are not supported") + case types.Map: + return fmt.Errorf("pointers to maps are not supported") + } + case types.Array: + return fmt.Errorf("fixed-size arrays are not supported") + case types.Slice: + elem := util.NativeType(t.Elem) + switch elem.Kind { + case types.Slice: + if util.NativeType(elem.Elem) != types.Byte { + return fmt.Errorf("lists of lists are not supported") + } + case types.Map: + return fmt.Errorf("lists of maps are not supported") + } + case types.Map: + key := util.NativeType(t.Key) + if key != types.String { + return fmt.Errorf("maps with non-string keys are not supported") + } + elem := util.NativeType(t.Elem) + switch elem.Kind { + case types.Map: + return fmt.Errorf("maps of maps are not supported") + } + default: + return fmt.Errorf("kind %v is not supported", t.Kind) + } + + return nil +} + +// resolveFieldElemNode returns the element node for a slice/map field, falling +// back to the type cache when the field type's elem child is not yet wired up +// (a recursive type still under construction). +func (td *typeDiscoverer) resolveFieldElemNode(child *childNode, elemType *types.Type) *typeNode { + if child.node != nil && child.node.elem != nil { + return child.node.elem.node + } + return td.typeNodes[elemType] +} + +// resolveFieldKeyNode is resolveFieldElemNode for a map's key. +func (td *typeDiscoverer) resolveFieldKeyNode(child *childNode, keyType *types.Type) *typeNode { + if child.node != nil && child.node.key != nil { + return child.node.key.node + } + return td.typeNodes[keyType] +} + +// discoverStruct walks a struct type recursively. +func (td *typeDiscoverer) discoverStruct(thisNode *typeNode, fldPath *field.Path) error { + var fields []*childNode + + klog.V(5).InfoS("discoverStruct", "type", thisNode.valueType) + + // Discover into each field of this struct. + for _, memb := range thisNode.valueType.Members { + name := memb.Name + + // Only do exported fields. + if unicode.IsLower([]rune(name)[0]) { + continue + } + + // If we try to emit code for this field and find no JSON name, we + // will abort. + jsonName := "" + if commentTags, ok := tags.LookupJSON(memb); ok { + jsonName = commentTags.Name + } + + var childPath *field.Path + if jsonName != "" { + childPath = fldPath.Child(jsonName) + } else { + childPath = fldPath.Child(name) + } + + // Discover the field type. + klog.V(5).InfoS("field", "name", name, "jsonName", jsonName, "type", memb.Type, "path", childPath) + childType := memb.Type + var child *childNode + if node, err := td.discoverChild(childType, childPath); err != nil { + return err + } else { + child = &childNode{ + name: name, + jsonName: jsonName, + childType: childType, + node: node, + } + } + + // Extract any field-attached validation rules. + context := validators.Context{ + Scope: validators.ScopeField, + Type: childType, + Path: childPath, + Member: &memb, + ParentPath: fldPath, + ParentType: thisNode.valueType, + ListSelector: nil, // NA for fields + Constants: nil, // NA for fields + StabilityLevel: "", // Inherited or default + } + + tags, err := td.validator.ExtractTags(context, memb.CommentLines) + if err != nil { + return fmt.Errorf("field %s: %w", childPath.String(), err) + } + if validations, err := td.validator.ExtractValidations(context, tags...); err != nil { + return fmt.Errorf("field %s: %w", childPath.String(), err) + } else if validations.Empty() { + klog.V(6).InfoS("no field-attached validations", "field", childPath) + } else { + klog.V(5).InfoS("found field-attached validations", "n", len(validations.Functions), "field", childPath) + if util.NonPointer(util.NativeType(childType)).Kind == types.Map && util.NonPointer(util.NativeType(childType)).Elem.Kind == types.Slice { + return fmt.Errorf("field %s: validation for map of slices is not supported", childPath) + } + child.fieldValidations.Add(validations) + // TODO: re-visit erroring on specific cases where variable generation is not supported for field validations + // currently there are some cases where we want variable generation for field validations + } + + // Handle non-included types. + switch util.NonPointer(childType).Kind { + case types.Struct, types.Alias: + if child.node == nil { // a non-included type + if !child.fieldValidations.OpaqueType { + return fmt.Errorf("%v: type %v is in a non-included package; "+ + "either add this package to validation-gen's --readonly-pkg flag, "+ + "or add +k8s:opaqueType to the field to skip validation", + childPath, childType.String()) + } + } else if child.fieldValidations.OpaqueType { + // If the field is marked as opaque, we can treat it as it is + // were in a non-included package. + child.node = nil + } + } + + // Add any other field-attached "special" validators. We need to do + // this after all the other field validation has been processed, + // because some of this is conditional on whether other validations + // were emitted (to avoid emitting empty functions). + // + // We do this here, rather than in discoverType() because we need to + // know information about the field, not just the type. + switch childType.Kind { + case types.Slice: + // Validate each value of a list field. + if elemNode := td.resolveFieldElemNode(child, childType.Elem); elemNode == nil { + if !child.fieldValidations.OpaqueValType { + return fmt.Errorf("%v: value type %v is in a non-included package; "+ + "either add this package to validation-gen's --readonly-pkg flag, "+ + "or add +k8s:eachVal=+k8s:opaqueType to the field to skip validation", + childPath, childType.Elem.String()) + } + } else if child.fieldValidations.OpaqueValType { + // If the field is marked as opaque, we can treat it as it is + // were in a non-included package. + } else { + // If the list's value type is a named type, call the validation + // function for each element. + if funcName := elemNode.funcName; funcName.Name != "" { + // Save the iteration validation while we have all the + // information we need. Later we can check if we + // actually need it. + // + // Note: the first argument to Function() is really + // only for debugging. + v, err := validators.ForEachVal(childPath, childType, + validators.Function("iterateListValues", validators.DefaultFlags, funcName). + WithComment("iterate the list and call the type's validation function")) + if err != nil { + return fmt.Errorf("generating list iteration: %w", err) + } else { + child.fieldValIterations.Add(v) + } + } + } + case types.Map: + // Validate each key of a map field. + if keyNode := td.resolveFieldKeyNode(child, childType.Key); keyNode == nil { + if !child.fieldValidations.OpaqueKeyType { + return fmt.Errorf("%v: key type %v is in a non-included package; "+ + "either add this package to validation-gen's --readonly-pkg flag, "+ + "or add +k8s:eachKey=+k8s:opaqueType to the field to skip validation", + childPath, childType.Key.String()) + } + } else if child.fieldValidations.OpaqueKeyType { + // If the field is marked as opaque, we can treat it as it is + // were in a non-included package. + } else { + // If the map's key type is a named type, call the validation + // function for each key. + if funcName := keyNode.funcName; funcName.Name != "" { + // Save the iteration validation while we have all the + // information we need. Later we can check if we + // actually need it. + // + // Note: the first argument to Function() is really + // only for debugging. + v, err := validators.ForEachKey(childPath, childType, + validators.Function("iterateMapKeys", validators.DefaultFlags, funcName). + WithComment("iterate the map and call the key type's validation function")) + if err != nil { + return fmt.Errorf("generating map key iteration: %w", err) + } else { + child.fieldKeyIterations.Add(v) + } + } + } + // Validate each value of a map field. + if elemNode := td.resolveFieldElemNode(child, childType.Elem); elemNode == nil { + if !child.fieldValidations.OpaqueValType { + return fmt.Errorf("%v: value type %v is in a non-included package; "+ + "either add this package to validation-gen's --readonly-pkg flag, "+ + "or add +k8s:eachVal=+k8s:opaqueType to the field to skip validation", + childPath, childType.Elem.String()) + } + } else if child.fieldValidations.OpaqueValType { + // If the field is marked as opaque, we can treat it as it is + // were in a non-included package. + } else { + // If the map's value type is a named type, call the validation + // function for each element. + if funcName := elemNode.funcName; funcName.Name != "" { + // Save the iteration validation while we have all the + // information we need. Later we can check if we + // actually need it. + // + // Note: the first argument to Function() is really + // only for debugging. + v, err := validators.ForEachVal(childPath, childType, + validators.Function("iterateMapValues", validators.DefaultFlags, funcName). + WithComment("iterate the map and call the value type's validation function")) + if err != nil { + return fmt.Errorf("generating map value iteration: %w", err) + } else { + child.fieldValIterations.Add(v) + } + } + } + } + + fields = append(fields, child) + } + + for _, child := range fields { + // Process deferred validations for the field. Similar to type-level + // deferred validations, these are resolved iteratively until no more + // deferred validations are produced. + deferred := child.fieldValidations.Deferred + child.fieldValidations.Deferred = nil + depth := 0 + for len(deferred) > 0 { + depth++ + if depth > 10 { + return fmt.Errorf("deferred validation recursion depth exceeded "+ + "10 for field %s of type %s", child.name, thisNode.valueType.String()) + } + var nextDeferred []validators.DeferredGen + for _, def := range deferred { + res, err := def.Callback() + if err != nil { + return fmt.Errorf("deferred validation callback failed for field %s of type %s: %w", + child.name, thisNode.valueType.String(), err) + } + if len(res.Deferred) > 0 { + nextDeferred = append(nextDeferred, res.Deferred...) + res.Deferred = nil + } + // Map the resolved validations to the appropriate context: + // - ThisContext maps to the field's validations. + // - ParentContext maps to the containing type's validations. + // This occurs when a validation specified on a field actually applies to the + // entire struct (e.g., union validations that enforce rules across multiple fields). + switch def.Scope { + case validators.ThisContext: + child.fieldValidations.Add(res) + case validators.ParentContext: + thisNode.typeValidations.Add(res) + default: + return fmt.Errorf("unexpected scope %v", def.Scope) + } + } + deferred = nextDeferred + } + } + + thisNode.fields = fields + return nil +} + +// getValidationFunctionName returns a type's validator identity: (input +// package, Validate_T). Which package a call actually targets is output- +// dependent, so it's resolved at emit time (resolveFunc), not baked in here. +// +// TODO: Currently this is a "blind" call - we hope that the expected function +// exists, but we don't verify that, and we only emit calls into packages which +// are being processed by this generator. For cross-package calls we will need +// to verify the target, either by naming convention + fingerprint or by +// explicit comment-tags or something. +func (td *typeDiscoverer) getValidationFunctionName(t *types.Type) (types.Name, bool) { + if _, ok := td.inputToCanonicalPkg[t.Name.Package]; !ok { + return types.Name{}, false + } + return types.Name{Package: t.Name.Package, Name: "Validate_" + t.Name.Name}, true +} + +func mkSymbolArgs(c *generator.Context, names []types.Name) generator.Args { + args := generator.Args{} + for _, name := range names { + args[name.Name] = c.Universe.Type(name) + } + return args +} + +// hasValidations checks whether the given typeNode has any +// validations, transitively. +func (g *genValidations) hasValidations(n *typeNode) bool { + seen := map[*typeNode]bool{} + return g.hasValidationsImpl(n, seen) +} + +// hasValidationsImpl implements hasValidations without risk of infinite +// recursion. +func (g *genValidations) hasValidationsImpl(n *typeNode, seen map[*typeNode]bool) bool { + if n == nil { + return false + } + + if seen[n] { + return false + } + seen[n] = true + + if n.typeValidations.HasEmitable() { + return true + } + + if n.typeValidations.OpaqueType { + return false + } + + if n.underlying != nil { + if n.typeKeyIterations.HasEmitable() { + if keyNode := n.resolveKeyNode(); keyNode != nil && g.hasValidationsImpl(keyNode, seen) { + return true + } + } + if n.typeValIterations.HasEmitable() { + if elemNode := n.resolveElemNode(); elemNode != nil && g.hasValidationsImpl(elemNode, seen) { + return true + } + } + if g.hasValidationsImpl(n.underlying.node, seen) { + return true + } + } + + for _, c := range n.fields { + if c.fieldValidations.HasEmitable() { + return true + } + if c.fieldKeyIterations.HasEmitable() { + if keyNode := c.node.resolveKeyNode(); keyNode != nil && g.hasValidationsImpl(keyNode, seen) { + return true + } + } + if c.fieldValIterations.HasEmitable() { + if elemNode := c.node.resolveElemNode(); elemNode != nil && g.hasValidationsImpl(elemNode, seen) { + return true + } + } + if g.hasValidationsImpl(c.node, seen) { + return true + } + } + + return false +} + +// emitRegisterFunction emits the type-registration logic for validation +// functions. +func (g *genValidations) emitRegisterFunction(c *generator.Context, schemeRegistry types.Name, sw *generator.SnippetWriter) { + var targetTypes []*types.Type + for _, rootType := range g.rootTypes { + if g.hasValidations(g.discovered.typeNodes[rootType]) { + targetTypes = append(targetTypes, rootType) + } + } + if len(targetTypes) == 0 { + return + } + + scheme := c.Universe.Type(schemeRegistry) + schemePtr := &types.Type{ + Kind: types.Pointer, + Elem: scheme, + } + + sw.Do("func init() { localSchemeBuilder.Register(RegisterValidations)}\n\n", nil) + + sw.Do("// RegisterValidations adds validation functions to the given scheme.\n", nil) + sw.Do("// Public to allow building arbitrary schemes.\n", nil) + sw.Do("func RegisterValidations(scheme $.|raw$) error {\n", schemePtr) + for _, rootType := range targetTypes { + node := g.discovered.typeNodes[rootType] + if node == nil { + panic(fmt.Sprintf("found nil node for root-type %v", rootType)) + } + + targs := generator.Args{ + "rootType": rootType, + "typePfx": "", + "field": mkSymbolArgs(c, fieldPkgSymbols), + "fmt": mkSymbolArgs(c, fmtPkgSymbols), + "operation": mkSymbolArgs(c, operationPkgSymbols), + "safe": mkSymbolArgs(c, safePkgSymbols), + "context": mkSymbolArgs(c, contextPkgSymbols), + } + if !util.IsNilableType(rootType) { + targs["typePfx"] = "*" + } + + // This uses a typed nil pointer, rather than a real instance because + // we need the type information, but not an instance of the type. + sw.Do("// type $.rootType|name$\n", targs) + sw.Do("scheme.AddValidationFunc(\n", targs) + sw.Do(" ($.typePfx$$.rootType|raw$)(nil),\n", targs) + sw.Do(" func(ctx $.context.Context$, op $.operation.Operation|raw$, obj, oldObj interface{}) $.field.ErrorList|raw$ {\n", targs) + + sw.Do("switch op.Request.SubresourcePath() {\n", nil) + sw.Do("case ", nil) + for i, s := range g.toResourceList(rootType) { + if i > 0 { + sw.Do(", ", nil) + } + sw.Do("$.$", s) + } + sw.Do(":\n", nil) + sw.Do(" return $.rootType|objectvalidationfn$(\n", targs) + sw.Do(" ctx, ", targs) + sw.Do(" op, ", targs) + sw.Do(" nil /* fldPath */,\n", targs) + sw.Do(" obj.($.typePfx$$.rootType|raw$),\n", targs) + sw.Do(" $.safe.Cast|raw$[$.typePfx$$.rootType|raw$](oldObj))\n", targs) + sw.Do(" }\n", targs) + sw.Do(" return $.field.ErrorList|raw${\n", targs) + sw.Do(" $.field.InternalError|raw$(", targs) + sw.Do(" nil, ", targs) + sw.Do(" $.fmt.Errorf|raw$(\"no validation found for %T, subresource: %v\", obj, op.Request.SubresourcePath())),\n", targs) + sw.Do(" }\n", targs) + sw.Do("})\n", targs) + } + sw.Do("return nil\n", nil) + sw.Do("}\n\n", nil) +} + +// toResourceList returns a list of resources that are supported by a kind. +func (g *genValidations) toResourceList(rootType *types.Type) []string { + supportedSubresources := supportedSubresourceTags(rootType) + + if subresource, isSubresource := isSubresourceTag(rootType); isSubresource { + supportedSubresources.Insert(subresource) + } else { + supportedSubresources.Insert("/") + } + supported := supportedSubresources.UnsortedList() + slices.Sort(supported) + for i, subresource := range supported { + supported[i] = strconv.Quote(subresource) + } + return supported +} + +// emitValidationFunction emits a validation function for the specified type. +func (g *genValidations) emitValidationFunction(c *generator.Context, t *types.Type, sw *generator.SnippetWriter) { + if !g.hasValidations(g.discovered.typeNodes[t]) { + return + } + + targs := generator.Args{ + "inType": t, + "field": mkSymbolArgs(c, fieldPkgSymbols), + "operation": mkSymbolArgs(c, operationPkgSymbols), + "context": mkSymbolArgs(c, contextPkgSymbols), + "objTypePfx": "*", + } + if util.IsNilableType(t) { + targs["objTypePfx"] = "" + } + + node := g.discovered.typeNodes[t] + if node == nil { + panic(fmt.Sprintf("found nil node for root-type %v", t)) + } + sw.Do("// $.inType|objectvalidationfn$ validates an instance of $.inType|name$ according\n", targs) + sw.Do("// to declarative validation rules in the API schema.\n", targs) + sw.Do("func $.inType|objectvalidationfn$(\n", targs) + sw.Do(" ctx $.context.Context|raw$, ", targs) + sw.Do(" op $.operation.Operation|raw$, ", targs) + sw.Do(" fldPath *$.field.Path|raw$,\n", targs) + sw.Do(" obj, oldObj $.objTypePfx$$.inType|raw$) ", targs) + sw.Do("(errs $.field.ErrorList|raw$) {\n\n", targs) + fakeChild := &childNode{ + node: node, + childType: t, + } + g.emitValidationForChild(c, fakeChild, sw) + sw.Do("return errs\n", nil) + sw.Do("}\n\n", nil) +} + +// emitValidationForChild emits code for the specified childNode, calling +// type-attached validations and then descending into the type (e.g. struct +// fields). +// +// Emitted code assumes that the value in question is always a pair of nilable +// variables named "obj" and "oldObj", and the field path to this value is +// named "fldPath". +// +// This function assumes that thisChild.node is not nil. +func (g *genValidations) emitValidationForChild(c *generator.Context, thisChild *childNode, sw *generator.SnippetWriter) { + thisNode := thisChild.node + inType := thisNode.valueType + + targs := generator.Args{ + "inType": inType, + "field": mkSymbolArgs(c, fieldPkgSymbols), + "safe": mkSymbolArgs(c, safePkgSymbols), + } + + didSome := false // for prettier output later + + // Emit code for type-attached validations. + if validations := thisNode.typeValidations; !validations.Empty() { + switch thisNode.valueType.Kind { + case types.Struct, types.Alias: // OK + default: + panic(fmt.Sprintf("unexpected type-validations on type %v, kind %s", thisNode.valueType, thisNode.valueType.Kind)) + } + emitComments(validations.Comments, sw) + g.emitCallsToValidators(c, validations.Functions, sw) + sw.Do("\n", nil) + didSome = true + } + + if validations := thisNode.typeKeyIterations; !validations.Empty() { + keyNode := thisNode.resolveKeyNode() + if keyNode != nil && g.hasValidations(keyNode) { + emitComments(validations.Comments, sw) + g.emitCallsToValidators(c, validations.Functions, sw) + sw.Do("\n", nil) + didSome = true + } + } + + if validations := thisNode.typeValIterations; !validations.Empty() { + elemNode := thisNode.resolveElemNode() + if elemNode != nil && g.hasValidations(elemNode) { + emitComments(validations.Comments, sw) + g.emitCallsToValidators(c, validations.Functions, sw) + sw.Do("\n", nil) + didSome = true + } + } + + if thisNode.typeValidations.OpaqueType { + return + } + + // Descend into the type. + switch inType.Kind { + case types.Builtin: + // Nothing further. + case types.Slice: + // Nothing further + case types.Map: + // Nothing further + case types.Alias: + g.emitValidationForChild(c, thisNode.underlying, sw) + case types.Struct: + for _, fld := range thisNode.fields { + if len(fld.name) == 0 { + panic(fmt.Sprintf("missing field name in type %s (field-type %s)", thisNode.valueType, fld.childType)) + } + // Missing JSON name is checked iff we have code to emit. + + // Accumulate into a buffer so we don't emit empty functions. + buf := bytes.NewBuffer(nil) + bufsw := sw.Dup(buf) + + // On ratcheting checks: + // + // We emit ratcheting checks ONLY for struct fields and ONLY when + // that field has some validations to call. + // + // We DO NOT emit ratchet checks inside type-specific validation + // functions, because that leads to repeated ratchet checking which + // is almost never useful work (keep reading). + // + // The consequence of this is that a caller of a type's validation + // function is assumed to have already done a ratchet check (which + // is true for all generated code (except root types, keep + // reading)). For struct types (our most common case), the type's + // function will do ratchet checks on each sub-field anyway. + // + // This leaves one case where validation is executed unilaterally: + // non-pre-checked calls of validation functions for types which have + // type-attached validations. This can happen in two cases: + // 1. an external caller of a type's validation function + // 2. the generated register function for a package which calls a + // root-type's validation function + // + // TODO: We are leaving this as a problem for the future. If we + // find that we have a root type which has type-attached validation + // AND that validation is being ratcheted, then we will need to + // address this. Some options: + // 1. emit a ratchet check in the package's register function + // 2. emit a ratchet check in the type's validation function + // (IFF it has type-attached validation, perhaps only for root + // types) + // 3. emit both "safe" (ratchet check the whole object) and + // "fast" (assume the object was already ratchet checked) + // forms of each type's validation function, so that the + // generated code can call the "fast" form while external code + // calls the "safe" form. + // 4. implement depth-first traversal of validation, where each + // function returns an additional bool indicating "something + // changed", which gets propagated up the caller to decide if + // it needs to do higher-level validations (e.g. if any field + // in a struct changes, the struct's type-attached validations + // need to be executed, but if no fields changed they can be + // skipped). + // + // For the same reasons, in the specific case of lists and maps of pointers, + // we do not emit nil-checks inside type-specific validation + // functions. If we solve for the duplicative ratcheting, then we + // should also solve for nil-checks. + + if nt := util.NativeType(fld.childType); nt.Kind == types.Slice && nt.Elem.Kind == types.Pointer { + hasFieldValidations := len(fld.fieldValidations.Functions) > 0 + hasPropagation := fld.node != nil && g.hasValidations(fld.node) + hasKeyIterations := len(fld.fieldKeyIterations.Functions) > 0 && hasPropagation + hasValIterations := len(fld.fieldValIterations.Functions) > 0 && hasPropagation + + if hasFieldValidations || hasPropagation || hasKeyIterations || hasValIterations { + // Prepend the nil-check so it runs before any other + // validations. This is important because the other + // validations may assume that the slice has no nil values. + nilCheck := validators.PtrSliceNoNils(nt.Elem.Elem.Name) + fld.fieldValidations.Functions = append([]validators.FunctionGen{nilCheck}, fld.fieldValidations.Functions...) + } + } else if nt := util.NativeType(fld.childType); nt.Kind == types.Map && nt.Elem.Kind == types.Pointer { + hasFieldValidations := len(fld.fieldValidations.Functions) > 0 + hasPropagation := fld.node != nil && g.hasValidations(fld.node) + hasKeyIterations := len(fld.fieldKeyIterations.Functions) > 0 && hasPropagation + hasValIterations := len(fld.fieldValIterations.Functions) > 0 && hasPropagation + + if hasFieldValidations || hasPropagation || hasKeyIterations || hasValIterations { + // Prepend the nil-check so it runs before any other + // validations. This is important because the other + // validations may assume that the map has no nil values. + nilCheck := validators.PtrMapNoNils(nt.Key.Name, nt.Elem.Elem.Name) + fld.fieldValidations.Functions = append([]validators.FunctionGen{nilCheck}, fld.fieldValidations.Functions...) + } + } + + validations := fld.fieldValidations + fldRatchetingChecked := false + if !validations.Empty() { + emitComments(validations.Comments, bufsw) + if len(validations.Functions) > 0 { + g.emitRatchetingCheck(c, fld.childType, bufsw) + fldRatchetingChecked = true + bufsw.Do("// call field-attached validations\n", nil) + g.emitCallsToValidators(c, validations.Functions, bufsw) + } + } + + // If the node is nil, this must be a type in a package we are not + // handling - it's effectively opaque to us. + if fld.node != nil { + // Get to the real type. + switch fld.node.valueType.Kind { + case types.Alias, types.Struct: + // If this field is another type, we may need to call its + // validation function. If it has no validations + // (transitively) then we don't need to do anything. + if g.hasValidations(fld.node) { + if !fldRatchetingChecked { + g.emitRatchetingCheck(c, fld.childType, bufsw) + fldRatchetingChecked = true + } + g.emitCallToOtherTypeFunc(c, fld.node, bufsw) + } + } + + emitIterations := func(iterations validators.Validations, node *typeNode) { + if iterations.Empty() { + return + } + if node != nil && g.hasValidations(node) { + emitComments(iterations.Comments, bufsw) + if len(iterations.Functions) > 0 { + if !fldRatchetingChecked { + g.emitRatchetingCheck(c, fld.childType, bufsw) + fldRatchetingChecked = true + } + g.emitCallsToValidators(c, iterations.Functions, bufsw) + } + } + } + + emitIterations(fld.fieldKeyIterations, fld.node.resolveKeyNode()) + emitIterations(fld.fieldValIterations, fld.node.resolveElemNode()) + + if fld.node.valueType.Kind == types.Slice || fld.node.valueType.Kind == types.Map { + // Descend into this field. + g.emitValidationForChild(c, fld, bufsw) + } + } + + if buf.Len() > 0 { + leafType, typePfx, exprPfx := getLeafTypeAndPrefixes(fld.childType) + targs := targs.WithArgs(generator.Args{ + "fieldName": fld.name, + "fieldJSON": fld.jsonName, + "fieldType": leafType, + "fieldTypePfx": typePfx, + "fieldExprPfx": exprPfx, + }) + + if didSome { + sw.Do("\n", nil) + } + sw.Do("{ // field $.inType|raw$.$.fieldName$\n", targs) + sw.Do(" fn := func(\n", targs) + sw.Do(" fldPath *$.field.Path|raw$,\n", targs) + sw.Do(" obj, oldObj $.fieldTypePfx$$.fieldType|raw$,\n", targs) + sw.Do(" oldValueCorrelated bool) (errs $.field.ErrorList|raw$) {\n", targs) + if err := sw.Merge(buf, bufsw); err != nil { + panic(fmt.Sprintf("failed to merge buffer: %v", err)) + } + sw.Do(" return\n", targs) + sw.Do(" }\n", targs) + // safe.Field returns a nil if the old object does not have a correlatable + // value, such as a map. + // This is ambiguous with the case where the field exists and is nil. + // This ambiguity is a problem for ratcheting, which needs to distinguish + // these cases. For example, if a required field is removed from a map, + // safe.Field will return nil for the old value, and the new value is also + // nil (because it doesn't exist). Ratcheting would normally allow this, + // but it's a validation failure because a required field is missing. + // + // To solve this, we pass an extra boolean parameter to the validation + // function, indicating whether the old value was correlated. If the old + // value was uncorrelated, it means the field was not present in the old + // object, and we should not apply ratcheting logic. `oldObj != nil` + // provides this bit of information. + // + // This bit is not currently propagated down to deeper levels of + // validation, but since the code generator only ever looks one level + // down, this is sufficient for now. + sw.Do(" oldVal := $.safe.Field|raw$(oldObj,\n", targs) + sw.Do(" func(oldObj *$.inType|raw$) $.fieldTypePfx$$.fieldType|raw$ {\n", targs) + sw.Do(" return $.fieldExprPfx$oldObj.$.fieldName$\n", targs) + sw.Do(" })\n", targs) + sw.Do(" errs = append(errs, fn(", targs) + if len(fld.jsonName) > 0 { + sw.Do("fldPath.Child(\"$.fieldJSON$\"), ", targs) + } else { + // If there is an embedded field in a root-type, fldPath + // will be nil, and we need SOMETHING for the field path. + sw.Do("$.safe.Value|raw$(fldPath, func() *$.field.Path|raw$ { return fldPath.Child(\"$.fieldType|raw$\") }), ", targs) + } + sw.Do(" $.fieldExprPfx$obj.$.fieldName$, oldVal, oldObj != nil)...)\n", targs) + sw.Do("}\n", targs) + sw.Do("\n", nil) + } else { + targs := targs.WithArgs(generator.Args{ + "fieldName": fld.name, + }) + sw.Do("// field $.inType|raw$.$.fieldName$ has no validation\n", targs) + } + didSome = true + } + default: + panic(fmt.Sprintf("unhandled type: %v (kind %s)", inType, inType.Kind)) + } +} + +// emitCallToOtherTypeFunc generates a call to the specified node's generated +// validation function for a field in some parent context. +// +// Emitted code assumes that the value in question is always a pair of nilable +// variables named "obj" and "oldObj", and the field path to this value is +// named "fldPath". +func (g *genValidations) emitCallToOtherTypeFunc(c *generator.Context, node *typeNode, sw *generator.SnippetWriter) { + targs := generator.Args{ + "funcName": c.Universe.Type(g.resolveFunc(node.funcName)), + } + sw.Do("// call the type's validation function\n", nil) + sw.Do("errs = append(errs, $.funcName|raw$(ctx, op, fldPath, obj, oldObj)...)\n", targs) +} + +// emitRatchetingCheck emits an equivalence check for default ratcheting. +func (g *genValidations) emitRatchetingCheck(c *generator.Context, t *types.Type, sw *generator.SnippetWriter) { + // Emit equivalence check for default ratcheting. + targs := generator.Args{ + "operation": mkSymbolArgs(c, operationPkgSymbols), + } + sw.Do("// don't revalidate unchanged data\n", nil) + sw.Do("if oldValueCorrelated && op.Type == $.operation.Update|raw$ {\n", targs) + // If the type is a builtin, we can use a simpler equality check when they are not nil. + if util.IsDirectComparable(util.NonPointer(util.NativeType(t))) { + // We should never get anything but pointers here, since every other + // nilable type is not Comparable. + // + // This condition looks overly complex, but each case is needed: + // - obj == oldObj : handle pointers which are nil in old and new + // - obj != nil : handle optional fields which are updated to nil + // - oldObj != nil : handle optional fields which are updated from nil + // - *obj == *oldObj : compare values + sw.Do(" if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) {\n", targs) + } else { + if g.deepEqualFunc.Package == "" { + sw.Do(" if $.$(obj, oldObj) {\n", g.deepEqualFunc.Name) + } else { + targs["deepEqualFunc"] = c.Universe.Type(g.deepEqualFunc) + sw.Do(" if $.deepEqualFunc|raw$(obj, oldObj) {\n", targs) + } + } + sw.Do(" return nil\n", nil) + sw.Do(" }\n", nil) + sw.Do("}\n", nil) +} + +// emitCallsToValidators emits calls to a list of validation functions for +// a single field or type. validations is a list of functions to call, with +// arguments. +// +// When calling registered validators, we always pass a nilable type. E.g. if +// the field's type is string, we pass *string, and if the field's type is +// *string, we also pass *string. This means that validators need to do +// nil-checks themselves, if they intend to dereference the pointer. This +// makes updates more consistent. +// +// Emitted code assumes that the value in question is always a pair of nilable +// variables named "obj" and "oldObj", and the field path to this value is +// named "fldPath". +func (g *genValidations) emitCallsToValidators(c *generator.Context, validations []validators.FunctionGen, sw *generator.SnippetWriter) { + // Group and sort the inputs. + cohorts := sortIntoCohorts(validations) + + for _, validations := range cohorts { + cohortName := validations[0].Cohort + if cohortName != "" { + sw.Do("func() { // cohort = \"$.$\"\n", cohortName) + } + + hasShortCircuits := false + lastShortCircuitIdx := -1 + for i, v := range validations { + if v.Flags.IsSet(validators.ShortCircuit) { + hasShortCircuits = true + lastShortCircuitIdx = i + } + } + + if hasShortCircuits { + sw.Do("earlyReturn := false\n", nil) + } + + for i, v := range validations { + isShortCircuit := v.Flags.IsSet(validators.ShortCircuit) + isNonError := v.Flags.IsSet(validators.NonError) + + targs := generator.Args{ + "funcName": c.Universe.Type(g.resolveFunc(v.Function)), + "field": mkSymbolArgs(c, fieldPkgSymbols), + "fmt": mkSymbolArgs(c, fmtPkgSymbols), + } + + emitCall := func() { + sw.Do("$.funcName|raw$", targs) + if typeArgs := v.TypeArgs; len(typeArgs) > 0 { + sw.Do("[", nil) + for i, typeArg := range typeArgs { + sw.Do("$.|raw$", c.Universe.Type(typeArg)) + if i < len(typeArgs)-1 { + sw.Do(",", nil) + } + } + sw.Do("]", nil) + } + sw.Do("(ctx, op, fldPath, obj, oldObj", targs) + for _, arg := range v.Args { + sw.Do(", ", nil) + g.toGolangSourceDataLiteral(sw, c, arg, flNewlineOK) + } + sw.Do(")", targs) + switch v.StabilityLevel { + case validators.ValidationStabilityLevelAlpha: + sw.Do(".MarkAlpha()", nil) + case validators.ValidationStabilityLevelBeta: + sw.Do(".MarkBeta()", nil) + } + if isShortCircuit { + sw.Do(".MarkShortCircuit()", nil) + } + } + + // If validation is conditional, wrap the validation function with a conditions check. + if !v.Conditions.Empty() { + emitBaseFunction := emitCall + emitCall = func() { + // emitOptionLookup emits ", defined := op.HasOption(